Files
skills/docs/api-stack/api/api-plugin-webhook/src/store/endpoint-store.md
2026-05-29 08:20:56 +01:00

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.
  • 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

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 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.