3.5 KiB
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. listaccepts an optionalListEndpointsFilterwith tri-statetenantId(string = single tenant,null= global bucket only,undefined= every endpoint).- Returns are typed via the shared
WebhookEndpointshape fromtypes/types.tsso swap-in backends inherit field-level type safety automatically.
Usage: Basic
import type { EndpointStore } from "@suaveplan/api-plugin-webhook/store/endpoint-store";
declare const store: EndpointStore;
const endpoint = await store.get("ep-1");
Usage: Advanced
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 ornullif no row exists for the supplied id.EndpointStore.create(input)— derivesid,createdAt,updatedAt, defaultsalgorithm: "sha256"andactive: truewhen absent, returns the inserted row.EndpointStore.update(id, patch)— applies the patch to the existing row, refreshesupdatedAt, 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.