first commit
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# `endpoint-store.ts` — `EndpointStore` interface
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the pluggable persistence seam for webhook endpoints. Every method is async-only so downstream packages can plug in SurrealDB, Postgres, Redis, or any other backend without forcing the in-memory implementation that ships with the plugin to bend its synchronous semantics. The interface is intentionally small (five methods) so implementers can author conforming stores with minimal surface area, and matches the shape consumed directly by the plugin's `WebhookService` and the CRUD routes.
|
||||
|
||||
## Features
|
||||
|
||||
- Five methods: `list`, `get`, `create`, `update`, `delete`.
|
||||
- `list` accepts an optional `ListEndpointsFilter` with tri-state `tenantId` (string = single tenant, `null` = global bucket only, `undefined` = every endpoint).
|
||||
- Returns are typed via the shared `WebhookEndpoint` shape from `types/types.ts` so swap-in backends inherit field-level type safety automatically.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import type { EndpointStore } from "@suaveplan/api-plugin-webhook/store/endpoint-store";
|
||||
|
||||
declare const store: EndpointStore;
|
||||
const endpoint = await store.get("ep-1");
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import type {
|
||||
EndpointStore,
|
||||
ListEndpointsFilter,
|
||||
} from "@suaveplan/api-plugin-webhook/store/endpoint-store";
|
||||
|
||||
class SurrealEndpointStore implements EndpointStore {
|
||||
constructor(private readonly db: SurrealAdapter) {}
|
||||
|
||||
async list(filter: ListEndpointsFilter = {}): Promise<readonly WebhookEndpoint[]> {
|
||||
const where =
|
||||
filter.tenantId === undefined
|
||||
? ""
|
||||
: "WHERE tenant_id = $tenantId";
|
||||
return this.db.query(`SELECT * FROM webhook_endpoint ${where}`, filter);
|
||||
}
|
||||
async get(id: string) {
|
||||
const [row] = await this.db.query(`SELECT * FROM webhook_endpoint WHERE id = $id`, { id });
|
||||
return row ?? null;
|
||||
}
|
||||
async create(input: CreateEndpointInput): Promise<WebhookEndpoint> { /* … */ }
|
||||
async update(id: string, patch: UpdateEndpointInput): Promise<WebhookEndpoint> { /* … */ }
|
||||
async delete(id: string): Promise<void> { /* … */ }
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `EndpointStore.list(filter?)` — returns every endpoint matching the optional filter. With no filter, returns every endpoint regardless of tenant.
|
||||
- `EndpointStore.get(id)` — returns the endpoint or `null` if no row exists for the supplied id.
|
||||
- `EndpointStore.create(input)` — derives `id`, `createdAt`, `updatedAt`, defaults `algorithm: "sha256"` and `active: true` when absent, returns the inserted row.
|
||||
- `EndpointStore.update(id, patch)` — applies the patch to the existing row, refreshes `updatedAt`, returns the new row. SHOULD throw if the id is unknown.
|
||||
- `EndpointStore.delete(id)` — idempotent. Removes the row if present; resolves regardless.
|
||||
- `ListEndpointsFilter` — `{ tenantId?: string | null }`. Undefined = no filter, null = global bucket only, string = single tenant.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The interface intentionally omits a `count` method — the plugin's CRUD routes use `list.length`, and operators that need true `count(*)` semantics either implement a richer store type or extend the interface in a downstream package via TypeScript declaration merging. The async return contract is forward-compatible with any backend; the in-memory default is synchronous internally but wraps its returns in `Promise.resolve(...)`. Implementers are responsible for tenant isolation within `list`; the calling code (routes / service) re-filters defensively against the principal's tenant.
|
||||
Reference in New Issue
Block a user