cvx-kit 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +11 -10
  3. package/ROADMAP.md +126 -0
  4. package/dist/agent-tools.d.mts +45 -0
  5. package/dist/agent-tools.mjs +2 -0
  6. package/dist/agent-tools.mjs.map +1 -0
  7. package/dist/auth.d.mts +9 -1
  8. package/dist/auth.mjs +1 -1
  9. package/dist/auth.mjs.map +1 -1
  10. package/dist/components/foundation/client.d.mts +44 -3
  11. package/dist/components/foundation/client.mjs +1 -1
  12. package/dist/components/foundation/client.mjs.map +1 -1
  13. package/dist/components/foundation/modules/command/command.d.mts +2 -1
  14. package/dist/components/foundation/modules/command/command.mjs +1 -1
  15. package/dist/components/foundation/modules/command/command.mjs.map +1 -1
  16. package/dist/components/foundation/modules/query/query.d.mts +29 -2
  17. package/dist/components/foundation/modules/query/query.mjs +1 -1
  18. package/dist/components/foundation/modules/query/query.mjs.map +1 -1
  19. package/dist/crud.d.mts +79 -0
  20. package/dist/crud.mjs +2 -0
  21. package/dist/crud.mjs.map +1 -0
  22. package/dist/index.d.mts +9 -3
  23. package/dist/index.mjs +1 -1
  24. package/dist/middleware.d.mts +46 -0
  25. package/dist/middleware.mjs +2 -0
  26. package/dist/middleware.mjs.map +1 -0
  27. package/dist/state-machine.d.mts +18 -0
  28. package/dist/state-machine.mjs +2 -0
  29. package/dist/state-machine.mjs.map +1 -0
  30. package/dist/tenancy.d.mts +1 -1
  31. package/dist/webhooks.d.mts +164 -0
  32. package/dist/webhooks.mjs +2 -0
  33. package/dist/webhooks.mjs.map +1 -0
  34. package/dist/zod-table.d.mts +34 -3
  35. package/dist/zod-table.mjs +1 -1
  36. package/dist/zod-table.mjs.map +1 -1
  37. package/docs/agent-tools.md +41 -0
  38. package/docs/commands.md +48 -0
  39. package/docs/crud.md +62 -0
  40. package/docs/llms.md +16 -3
  41. package/docs/plans/2026-08-14-001-feat-v0.1.0-feature-set-plan.md +461 -0
  42. package/docs/upgrading.md +152 -0
  43. package/docs/webhooks.md +58 -0
  44. package/package.json +21 -1
  45. package/scripts/extract-changelog.mjs +29 -0
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":["#kernel","#observability","#writeAudit","#checkPermission","#defaults","CommandKernelClass","#execute"],"sources":["../../../src/components/foundation/client.ts"],"sourcesContent":["import {\n\tCommand as CommandKernelClass,\n\ttype CommandExecution,\n\ttype CommandInput,\n\ttype CommandRegistry,\n\ttype CommandResult,\n} from './modules/command/command'\nimport {\n\tObservability,\n\ttype ObservabilityOptions,\n} from './modules/observability/observability'\nimport { Query } from './modules/query/query'\nimport { executeResultBoundary, projectResult } from './result'\nimport { emitSemanticEvent } from './telemetry'\n\n/** Shape handed to the injected audit writer; classification comes from the operation. */\nexport type AuditEntryInput = {\n\toperation: string\n\tactorId: string\n\taggregate: { type: string; id: string }\n\tmetadata?: Record<string, unknown>\n\tclassification: string\n}\n\nexport type AuditWriter = (\n\tcontext: never,\n\tentry: AuditEntryInput,\n) => Promise<unknown> | unknown\n\ntype MaybePromise<Value> = Value | Promise<Value>\ntype OperationKey<Operations extends CommandRegistry> = Extract<\n\tkeyof Operations,\n\tstring\n>\n\nexport type AuditedOperation = Readonly<{\n\tcommand: Readonly<{ parse: (value: unknown) => unknown }>\n\tresult: Readonly<{ parse: (value: unknown) => unknown }>\n\tclassification: string\n\t/**\n\t * Permission slug required to execute this operation. Checked through the\n\t * Foundation's injected checkPermission BEFORE guards and the handler;\n\t * declaring a permission without injecting a checker fails closed.\n\t */\n\tpermission?: string\n\t/**\n\t * Per-operation precondition, after the permission check and the registry\n\t * default guard, before the handler. Throw to deny — nothing has run yet.\n\t */\n\tguard?: (context: never, command: never) => MaybePromise<void>\n\t/**\n\t * Allowlist of aggregate types this operation's audit may reference. When\n\t * declared, an audit entry whose aggregate.type is not listed throws —\n\t * the audit vocabulary is enforced, not advisory.\n\t */\n\taggregates?: readonly string[]\n\taudit: (\n\t\tresolution: Readonly<{ command: never; result: never }>,\n\t\tcontext: never,\n\t) => MaybePromise<Omit<AuditEntryInput, 'classification'> | null>\n}>\n\n/** Registry-wide defaults applied to every operation of one Command. */\nexport type CommandDefaults = Readonly<{\n\t/** Runs before every operation's own guard. Throw to deny. */\n\tguard?: (context: never) => MaybePromise<void>\n}>\n\n/** Injected permission policy: throw to deny. Host semantics, kit ordering. */\nexport type PermissionChecker = (\n\tcontext: never,\n\tinput: Readonly<{ permission: string; operation: string }>,\n) => MaybePromise<void>\n\nexport type AuditedRegistry = Readonly<Record<string, AuditedOperation>>\n\nexport type FoundationOptions = Readonly<{\n\tobservability: ObservabilityOptions &\n\t\tReadonly<{\n\t\t\t/**\n\t\t\t * In-transaction audit writer used by Command. Operations that\n\t\t\t * return an audit record get it written here automatically.\n\t\t\t */\n\t\t\twriteAudit?: AuditWriter\n\t\t}>\n\t/**\n\t * Permission policy for operations that declare a `permission`. Runs\n\t * before guards and the handler; throw to deny. An operation with a\n\t * permission but no injected checker fails closed.\n\t */\n\tcheckPermission?: PermissionChecker\n}>\n\ntype FoundationComponentApi = Readonly<{\n\tfunctions: Readonly<{ status: unknown }>\n}>\n\n/**\n * Application command protocol bound to a Foundation instance:\n * validate, observe, execute, and audit. `classification` and `audit()`\n * are mandatory per operation — auditing is type-enforced, not opt-in.\n * Obtain it by destructuring the Foundation: `const { Command } = new Foundation(...)`.\n */\nclass BoundCommand<Context, const Operations extends AuditedRegistry> {\n\treadonly #kernel: CommandKernelClass<Context, Operations>\n\treadonly #observability: Observability\n\treadonly #writeAudit: AuditWriter\n\treadonly #checkPermission: PermissionChecker | undefined\n\treadonly #defaults: CommandDefaults\n\n\t/** Declared aggregate types per operation — introspectable for tests. */\n\treadonly aggregates: Readonly<{\n\t\t[Key in OperationKey<Operations>]: Operations[Key]['aggregates']\n\t}>\n\n\tstatic operation<const Definition extends AuditedOperation>(\n\t\tdefinition: Definition,\n\t): Definition {\n\t\treturn definition\n\t}\n\n\tconstructor(\n\t\toperations: Operations,\n\t\tdeps: {\n\t\t\tobservability: Observability\n\t\t\twriteAudit: AuditWriter\n\t\t\tcheckPermission?: PermissionChecker\n\t\t\tdefaults?: CommandDefaults\n\t\t},\n\t) {\n\t\tthis.#observability = deps.observability\n\t\tthis.#writeAudit = deps.writeAudit\n\t\tthis.#checkPermission = deps.checkPermission\n\t\tthis.#defaults = deps.defaults ?? {}\n\t\tthis.aggregates = Object.freeze(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.entries(operations).map(([operation, definition]) => [\n\t\t\t\t\toperation,\n\t\t\t\t\tdefinition.aggregates,\n\t\t\t\t]),\n\t\t\t),\n\t\t) as this['aggregates']\n\t\tthis.#kernel = new CommandKernelClass<Context, Operations>({\n\t\t\toperations,\n\t\t\texecute: (execution) => this.#execute(execution),\n\t\t})\n\t}\n\n\texec<const Key extends OperationKey<Operations>>(executor: {\n\t\toperation: Key\n\t\thandler: (\n\t\t\tcontext: Context,\n\t\t\tcommand: CommandInput<Operations, Key>,\n\t\t) => MaybePromise<CommandResult<Operations, Key>>\n\t}) {\n\t\treturn this.#kernel.exec(executor)\n\t}\n\n\tasync #execute<Key extends OperationKey<Operations>>(\n\t\texecution: CommandExecution<Context, Operations, Key>,\n\t): Promise<CommandResult<Operations, Key>> {\n\t\treturn this.#observability.observe(\n\t\t\t{\n\t\t\t\toperation: execution.operation,\n\t\t\t\tclassification: execution.definition.classification,\n\t\t\t},\n\t\t\tasync () => {\n\t\t\t\t// Guards run before the handler: permission → default → operation.\n\t\t\t\tconst permission = execution.definition.permission\n\t\t\t\tif (permission !== undefined) {\n\t\t\t\t\tif (!this.#checkPermission) {\n\t\t\t\t\t\tthrow new CommandPermissionError(execution.operation)\n\t\t\t\t\t}\n\t\t\t\t\tawait this.#checkPermission(execution.context as never, {\n\t\t\t\t\t\tpermission,\n\t\t\t\t\t\toperation: execution.operation,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tawait this.#defaults.guard?.(execution.context as never)\n\t\t\t\tawait execution.definition.guard?.(\n\t\t\t\t\texecution.context as never,\n\t\t\t\t\texecution.command as never,\n\t\t\t\t)\n\t\t\t\tconst result = await execution.run()\n\t\t\t\tconst audit = await execution.definition.audit(\n\t\t\t\t\t{ command: execution.command, result } as never,\n\t\t\t\t\texecution.context as never,\n\t\t\t\t)\n\t\t\t\tif (audit) {\n\t\t\t\t\tconst allowed = execution.definition.aggregates\n\t\t\t\t\tif (allowed && !allowed.includes(audit.aggregate.type)) {\n\t\t\t\t\t\tthrow new CommandAggregateError(\n\t\t\t\t\t\t\texecution.operation,\n\t\t\t\t\t\t\taudit.aggregate.type,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tawait this.#writeAudit(execution.context as never, {\n\t\t\t\t\t\t...audit,\n\t\t\t\t\t\tclassification: execution.definition.classification,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\t\t)\n\t}\n}\n\n/** The audit referenced an aggregate type outside the operation's allowlist. */\nclass CommandAggregateError extends Error {\n\treadonly code = 'COMMAND_AGGREGATE_NOT_DECLARED'\n\treadonly name = 'CommandAggregateError'\n\n\tconstructor(operation: string, aggregateType: string) {\n\t\tsuper(\n\t\t\t`Operation \"${operation}\" audited aggregate type \"${aggregateType}\" outside its declared aggregates`,\n\t\t)\n\t}\n}\n\n/** Fails closed: an operation declared a permission but no checker exists. */\nclass CommandPermissionError extends Error {\n\treadonly code = 'COMMAND_PERMISSION_NOT_CONFIGURED'\n\treadonly name = 'CommandPermissionError'\n\n\tconstructor(operation: string) {\n\t\tsuper(\n\t\t\t`Operation \"${operation}\" declares a permission but the Foundation has no checkPermission`,\n\t\t)\n\t}\n}\n\nexport type ApplicationCommand<\n\tContext,\n\tOperations extends AuditedRegistry,\n> = BoundCommand<Context, Operations>\n\ntype CommandConstructor = {\n\tnew <Context, const Operations extends AuditedRegistry>(\n\t\toperations: Operations,\n\t\tdefaults?: CommandDefaults,\n\t): BoundCommand<Context, Operations>\n\toperation: (typeof BoundCommand)['operation']\n}\n\n/**\n * Host facade for a component that owns no application execution capability.\n * Declared once; the sole source of the command protocol, query kernel, and\n * observability: `const { Command, Query, observability } = new Foundation(...)`.\n */\nexport class Foundation<\n\tComponent extends FoundationComponentApi = FoundationComponentApi,\n> {\n\treadonly status: Component['functions']['status']\n\treadonly Command: CommandConstructor\n\treadonly Query = Query\n\treadonly observability: Observability\n\t/** Typed-failure boundary — see result.ts. Facade-bound like the kernels. */\n\treadonly executeResultBoundary = executeResultBoundary\n\treadonly projectResult = projectResult\n\treadonly emitSemanticEvent = emitSemanticEvent\n\n\tconstructor(component: Component, options: FoundationOptions) {\n\t\tthis.status = component.functions.status\n\t\tconst observability = new Observability(options.observability)\n\t\tthis.observability = observability\n\t\tconst writeAudit = options.observability.writeAudit ?? (() => undefined)\n\t\tconst checkPermission = options.checkPermission\n\t\tthis.Command = class <\n\t\t\tContext,\n\t\t\tconst Operations extends AuditedRegistry,\n\t\t> extends BoundCommand<Context, Operations> {\n\t\t\tconstructor(operations: Operations, defaults?: CommandDefaults) {\n\t\t\t\tsuper(operations, {\n\t\t\t\t\tobservability,\n\t\t\t\t\twriteAudit,\n\t\t\t\t\tcheckPermission,\n\t\t\t\t\tdefaults,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Kernel CLASSES are deliberately NOT exported: Command, Query, and\n// Observability exist only as capabilities of a Foundation instance —\n// `const { Command, Query, observability } = new Foundation(...)`. A loose\n// import would construct kernels without the injected observability, audit\n// writer, and permission checker. Types stay exported for signatures.\nexport type {\n\tCommandExecution,\n\tCommandInput,\n\tCommandRegistry,\n\tCommandResult,\n\tParseable,\n} from './modules/command/command'\nexport type {\n\tCommandObservation,\n\tObservabilityOptions,\n} from './modules/observability/observability'\nexport type { QueryExecution } from './modules/query/query'\nexport type {\n\tResult,\n\tResultBoundary,\n\tTransactionMetricsContext,\n} from './result'\n\n// The component definition is deliberately NOT re-exported here: Convex's\n// CLI only discovers a component whose resolved file is convex.config.js,\n// so hosts must mount via the dedicated subpath:\n// import foundation from 'cvx-kit/components/foundation/convex.config'\n"],"mappings":"gTAuGA,IAAM,EAAN,KAAsE,CACrE,GACA,GACA,GACA,GACA,GAGA,WAIA,OAAO,UACN,EACa,CACb,OAAO,CACR,CAEA,YACC,EACA,EAMC,CACD,KAAKC,GAAiB,EAAK,cAC3B,KAAKC,GAAc,EAAK,WACxB,KAAKC,GAAmB,EAAK,gBAC7B,KAAKC,GAAY,EAAK,UAAY,CAAC,EACnC,KAAK,WAAa,OAAO,OACxB,OAAO,YACN,OAAO,QAAQ,CAAU,CAAC,CAAC,KAAK,CAAC,EAAW,KAAgB,CAC3D,EACA,EAAW,UACZ,CAAC,CACF,CACD,EACA,KAAKJ,GAAU,IAAIK,EAAwC,CAC1D,aACA,QAAU,GAAc,KAAKC,GAAS,CAAS,CAChD,CAAC,CACF,CAEA,KAAiD,EAM9C,CACF,OAAO,KAAKN,GAAQ,KAAK,CAAQ,CAClC,CAEA,KAAMM,GACL,EAC0C,CAC1C,OAAO,KAAKL,GAAe,QAC1B,CACC,UAAW,EAAU,UACrB,eAAgB,EAAU,WAAW,cACtC,EACA,SAAY,CAEX,IAAM,EAAa,EAAU,WAAW,WACxC,GAAI,IAAe,IAAA,GAAW,CAC7B,GAAI,CAAC,KAAKE,GACT,MAAM,IAAI,EAAuB,EAAU,SAAS,EAErD,MAAM,KAAKA,GAAiB,EAAU,QAAkB,CACvD,aACA,UAAW,EAAU,SACtB,CAAC,CACF,CACA,MAAM,KAAKC,GAAU,QAAQ,EAAU,OAAgB,EACvD,MAAM,EAAU,WAAW,QAC1B,EAAU,QACV,EAAU,OACX,EACA,IAAM,EAAS,MAAM,EAAU,IAAI,EAC7B,EAAQ,MAAM,EAAU,WAAW,MACxC,CAAE,QAAS,EAAU,QAAS,QAAO,EACrC,EAAU,OACX,EACA,GAAI,EAAO,CACV,IAAM,EAAU,EAAU,WAAW,WACrC,GAAI,GAAW,CAAC,EAAQ,SAAS,EAAM,UAAU,IAAI,EACpD,MAAM,IAAI,EACT,EAAU,UACV,EAAM,UAAU,IACjB,EAED,MAAM,KAAKF,GAAY,EAAU,QAAkB,CAClD,GAAG,EACH,eAAgB,EAAU,WAAW,cACtC,CAAC,CACF,CACA,OAAO,CACR,CACD,CACD,CACD,EAGM,EAAN,cAAoC,KAAM,CACzC,KAAgB,iCAChB,KAAgB,wBAEhB,YAAY,EAAmB,EAAuB,CACrD,MACC,cAAc,EAAU,4BAA4B,EAAc,kCACnE,CACD,CACD,EAGM,EAAN,cAAqC,KAAM,CAC1C,KAAgB,oCAChB,KAAgB,yBAEhB,YAAY,EAAmB,CAC9B,MACC,cAAc,EAAU,kEACzB,CACD,CACD,EAoBa,EAAb,KAEE,CACD,OACA,QACA,MAAiB,EACjB,cAEA,sBAAiC,EACjC,cAAyB,EACzB,kBAA6B,EAE7B,YAAY,EAAsB,EAA4B,CAC7D,KAAK,OAAS,EAAU,UAAU,OAClC,IAAM,EAAgB,IAAI,EAAc,EAAQ,aAAa,EAC7D,KAAK,cAAgB,EACrB,IAAM,EAAa,EAAQ,cAAc,iBAAqB,IAAA,IACxD,EAAkB,EAAQ,gBAChC,KAAK,QAAU,cAGL,CAAkC,CAC3C,YAAY,EAAwB,EAA4B,CAC/D,MAAM,EAAY,CACjB,gBACA,aACA,kBACA,UACD,CAAC,CACF,CACD,CACD,CACD"}
1
+ {"version":3,"file":"client.mjs","names":["#kernel","#observability","#writeAudit","#checkPermission","#defaults","CommandKernelClass","#execute"],"sources":["../../../src/components/foundation/client.ts"],"sourcesContent":["import {\n\tCommand as CommandKernelClass,\n\ttype CommandExecution,\n\ttype CommandInput,\n\ttype CommandRegistry,\n\ttype CommandResult,\n} from './modules/command/command'\nimport {\n\tObservability,\n\ttype ObservabilityOptions,\n} from './modules/observability/observability'\nimport { Query } from './modules/query/query'\nimport { executeResultBoundary, projectResult } from './result'\nimport { emitSemanticEvent } from './telemetry'\n\n/** Shape handed to the injected audit writer; classification comes from the operation. */\nexport type AuditEntryInput = {\n\toperation: string\n\tactorId: string\n\taggregate: { type: string; id: string }\n\tmetadata?: Record<string, unknown>\n\tclassification: string\n}\n\nexport type AuditWriter = (\n\tcontext: never,\n\tentry: AuditEntryInput,\n) => Promise<unknown> | unknown\n\ntype MaybePromise<Value> = Value | Promise<Value>\ntype OperationKey<Operations extends CommandRegistry> = Extract<\n\tkeyof Operations,\n\tstring\n>\n\nexport type AuditedOperation = Readonly<{\n\tcommand: Readonly<{ parse: (value: unknown) => unknown }>\n\tresult: Readonly<{ parse: (value: unknown) => unknown }>\n\tclassification: string\n\t/**\n\t * Permission slug required to execute this operation. Checked through the\n\t * Foundation's injected checkPermission BEFORE guards and the handler;\n\t * declaring a permission without injecting a checker fails closed.\n\t */\n\tpermission?: string\n\t/**\n\t * Per-operation precondition, after the permission check and the registry\n\t * default guard, before the handler. Throw to deny — nothing has run yet.\n\t */\n\tguard?: (context: never, command: never) => MaybePromise<void>\n\t/**\n\t * Allowlist of aggregate types this operation's audit may reference. When\n\t * declared, an audit entry whose aggregate.type is not listed throws —\n\t * the audit vocabulary is enforced, not advisory.\n\t */\n\taggregates?: readonly string[]\n\t/** Per-operation middleware, inside the registry-wide chain. */\n\tmiddleware?: readonly AnyCommandMiddleware[]\n\taudit: (\n\t\tresolution: Readonly<{ command: never; result: never }>,\n\t\tcontext: never,\n\t) => MaybePromise<Omit<AuditEntryInput, 'classification'> | null>\n}>\n\n/**\n * Composable, next()-based middleware wrapping [guards → handler]. Runs\n * INSIDE the pipeline's invariants: after the permission check, before the\n * result-schema parse, aggregate allowlist, and audit — so middleware can\n * time, trace, enrich context (`next({ context })`), short-circuit, or\n * transform results, but can never skip authorization, return an invalid\n * result, or desynchronize audit from effects.\n */\nexport type CommandMiddleware<\n\tContext = never,\n\tExtension extends Record<string, unknown> = Record<string, unknown>,\n> = (input: {\n\toperation: string\n\tcommand: unknown\n\tcontext: Context\n\tnext: (options?: {\n\t\t/** Merged into the context handed to inner middleware, guards, handler. */\n\t\tcontext?: Extension\n\t}) => Promise<unknown>\n}) => Promise<unknown>\n\n/** Any-context middleware — what registries and operations accept. */\n// Contravariant seam: the supertype every typed CommandMiddleware<C, E>\n// assigns to (context contra-narrows to never; next's options widen to\n// unknown). Definition sites stay typed via Command.middleware.\nexport type AnyCommandMiddleware = (input: {\n\toperation: string\n\tcommand: unknown\n\tcontext: never\n\tnext: (options?: { context?: unknown }) => Promise<unknown>\n}) => Promise<unknown>\n\n/** Registry-wide defaults applied to every operation of one Command. */\nexport type CommandDefaults = Readonly<{\n\t/** Runs before every operation's own guard. Throw to deny. */\n\tguard?: (context: never) => MaybePromise<void>\n\t/** Outermost middleware, in array order, around every operation. */\n\tmiddleware?: readonly AnyCommandMiddleware[]\n}>\n\n/** Injected permission policy: throw to deny. Host semantics, kit ordering. */\nexport type PermissionChecker = (\n\tcontext: never,\n\tinput: Readonly<{ permission: string; operation: string }>,\n) => MaybePromise<void>\n\nexport type AuditedRegistry = Readonly<Record<string, AuditedOperation>>\n\nexport type FoundationOptions = Readonly<{\n\tobservability: ObservabilityOptions &\n\t\tReadonly<{\n\t\t\t/**\n\t\t\t * In-transaction audit writer used by Command. Operations that\n\t\t\t * return an audit record get it written here automatically.\n\t\t\t */\n\t\t\twriteAudit?: AuditWriter\n\t\t}>\n\t/**\n\t * Permission policy for operations that declare a `permission`. Runs\n\t * before guards and the handler; throw to deny. An operation with a\n\t * permission but no injected checker fails closed.\n\t */\n\tcheckPermission?: PermissionChecker\n}>\n\ntype FoundationComponentApi = Readonly<{\n\tfunctions: Readonly<{ status: unknown }>\n}>\n\n/**\n * Application command protocol bound to a Foundation instance:\n * validate, observe, execute, and audit. `classification` and `audit()`\n * are mandatory per operation — auditing is type-enforced, not opt-in.\n * Obtain it by destructuring the Foundation: `const { Command } = new Foundation(...)`.\n */\nclass BoundCommand<Context, const Operations extends AuditedRegistry> {\n\treadonly #kernel: CommandKernelClass<Context, Operations>\n\treadonly #observability: Observability\n\treadonly #writeAudit: AuditWriter\n\treadonly #checkPermission: PermissionChecker | undefined\n\treadonly #defaults: CommandDefaults\n\n\t/** Declared aggregate types per operation — introspectable for tests. */\n\treadonly aggregates: Readonly<{\n\t\t[Key in OperationKey<Operations>]: Operations[Key]['aggregates']\n\t}>\n\n\tstatic operation<const Definition extends AuditedOperation>(\n\t\tdefinition: Definition,\n\t): Definition {\n\t\treturn definition\n\t}\n\n\t/**\n\t * Identity helper that types a middleware against its context and the\n\t * context extension it passes downstream via next({ context }).\n\t */\n\tstatic middleware<\n\t\tContext = never,\n\t\tconst Extension extends Record<string, unknown> = Record<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>,\n\t>(\n\t\tmiddleware: CommandMiddleware<Context, Extension>,\n\t): CommandMiddleware<Context, Extension> {\n\t\treturn middleware\n\t}\n\n\tconstructor(\n\t\toperations: Operations,\n\t\tdeps: {\n\t\t\tobservability: Observability\n\t\t\twriteAudit: AuditWriter\n\t\t\tcheckPermission?: PermissionChecker\n\t\t\tdefaults?: CommandDefaults\n\t\t},\n\t) {\n\t\tthis.#observability = deps.observability\n\t\tthis.#writeAudit = deps.writeAudit\n\t\tthis.#checkPermission = deps.checkPermission\n\t\tthis.#defaults = deps.defaults ?? {}\n\t\tthis.aggregates = Object.freeze(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.entries(operations).map(([operation, definition]) => [\n\t\t\t\t\toperation,\n\t\t\t\t\tdefinition.aggregates,\n\t\t\t\t]),\n\t\t\t),\n\t\t) as this['aggregates']\n\t\tthis.#kernel = new CommandKernelClass<Context, Operations>({\n\t\t\toperations,\n\t\t\texecute: (execution) => this.#execute(execution),\n\t\t})\n\t}\n\n\texec<const Key extends OperationKey<Operations>>(executor: {\n\t\toperation: Key\n\t\thandler: (\n\t\t\tcontext: Context,\n\t\t\tcommand: CommandInput<Operations, Key>,\n\t\t) => MaybePromise<CommandResult<Operations, Key>>\n\t}) {\n\t\treturn this.#kernel.exec(executor)\n\t}\n\n\tasync #execute<Key extends OperationKey<Operations>>(\n\t\texecution: CommandExecution<Context, Operations, Key>,\n\t): Promise<CommandResult<Operations, Key>> {\n\t\treturn this.#observability.observe(\n\t\t\t{\n\t\t\t\toperation: execution.operation,\n\t\t\t\tclassification: execution.definition.classification,\n\t\t\t},\n\t\t\tasync () => {\n\t\t\t\t// Guards run before the handler: permission → default → operation.\n\t\t\t\tconst permission = execution.definition.permission\n\t\t\t\tif (permission !== undefined) {\n\t\t\t\t\tif (!this.#checkPermission) {\n\t\t\t\t\t\tthrow new CommandPermissionError(execution.operation)\n\t\t\t\t\t}\n\t\t\t\t\tawait this.#checkPermission(execution.context as never, {\n\t\t\t\t\t\tpermission,\n\t\t\t\t\t\toperation: execution.operation,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tconst middleware = [\n\t\t\t\t\t...(this.#defaults.middleware ?? []),\n\t\t\t\t\t...(execution.definition.middleware ?? []),\n\t\t\t\t]\n\t\t\t\tconst terminal = async (context: unknown) => {\n\t\t\t\t\tawait this.#defaults.guard?.(context as never)\n\t\t\t\t\tawait execution.definition.guard?.(\n\t\t\t\t\t\tcontext as never,\n\t\t\t\t\t\texecution.command as never,\n\t\t\t\t\t)\n\t\t\t\t\treturn execution.run(context as never)\n\t\t\t\t}\n\t\t\t\tlet deepest = -1\n\t\t\t\tconst dispatch = async (\n\t\t\t\t\tindex: number,\n\t\t\t\t\tcontext: unknown,\n\t\t\t\t): Promise<unknown> => {\n\t\t\t\t\tif (index <= deepest) {\n\t\t\t\t\t\tthrow new CommandMiddlewareError(execution.operation)\n\t\t\t\t\t}\n\t\t\t\t\tdeepest = index\n\t\t\t\t\tconst layer = middleware[index]\n\t\t\t\t\tif (!layer) return terminal(context)\n\t\t\t\t\treturn layer({\n\t\t\t\t\t\toperation: execution.operation,\n\t\t\t\t\t\tcommand: execution.command,\n\t\t\t\t\t\tcontext: context as never,\n\t\t\t\t\t\tnext: (options) =>\n\t\t\t\t\t\t\tdispatch(\n\t\t\t\t\t\t\t\tindex + 1,\n\t\t\t\t\t\t\t\toptions?.context\n\t\t\t\t\t\t\t\t\t? { ...(context as object), ...(options.context as object) }\n\t\t\t\t\t\t\t\t\t: context,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Middleware may transform the return value, so the strict\n\t\t\t\t// result schema re-parses whatever leaves the chain.\n\t\t\t\tconst result = execution.parseResult(\n\t\t\t\t\tawait dispatch(0, execution.context),\n\t\t\t\t)\n\t\t\t\tconst audit = await execution.definition.audit(\n\t\t\t\t\t{ command: execution.command, result } as never,\n\t\t\t\t\texecution.context as never,\n\t\t\t\t)\n\t\t\t\tif (audit) {\n\t\t\t\t\tconst allowed = execution.definition.aggregates\n\t\t\t\t\tif (allowed && !allowed.includes(audit.aggregate.type)) {\n\t\t\t\t\t\tthrow new CommandAggregateError(\n\t\t\t\t\t\t\texecution.operation,\n\t\t\t\t\t\t\taudit.aggregate.type,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tawait this.#writeAudit(execution.context as never, {\n\t\t\t\t\t\t...audit,\n\t\t\t\t\t\tclassification: execution.definition.classification,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t},\n\t\t)\n\t}\n}\n\n/** A middleware called next() more than once. */\nclass CommandMiddlewareError extends Error {\n\treadonly code = 'COMMAND_MIDDLEWARE_NEXT_REUSED'\n\treadonly name = 'CommandMiddlewareError'\n\n\tconstructor(operation: string) {\n\t\tsuper(\n\t\t\t`A middleware for operation \"${operation}\" called next() more than once`,\n\t\t)\n\t}\n}\n\n/** The audit referenced an aggregate type outside the operation's allowlist. */\nclass CommandAggregateError extends Error {\n\treadonly code = 'COMMAND_AGGREGATE_NOT_DECLARED'\n\treadonly name = 'CommandAggregateError'\n\n\tconstructor(operation: string, aggregateType: string) {\n\t\tsuper(\n\t\t\t`Operation \"${operation}\" audited aggregate type \"${aggregateType}\" outside its declared aggregates`,\n\t\t)\n\t}\n}\n\n/** Fails closed: an operation declared a permission but no checker exists. */\nclass CommandPermissionError extends Error {\n\treadonly code = 'COMMAND_PERMISSION_NOT_CONFIGURED'\n\treadonly name = 'CommandPermissionError'\n\n\tconstructor(operation: string) {\n\t\tsuper(\n\t\t\t`Operation \"${operation}\" declares a permission but the Foundation has no checkPermission`,\n\t\t)\n\t}\n}\n\nexport type ApplicationCommand<\n\tContext,\n\tOperations extends AuditedRegistry,\n> = BoundCommand<Context, Operations>\n\n/**\n * The constructor shape a Foundation instance exposes as `Command`.\n * Exported as a TYPE ONLY so factories (e.g. cvx-kit/crud) can accept the\n * destructured facade class as input without importing any runtime kernel.\n */\nexport type CommandConstructor = {\n\tnew <Context, const Operations extends AuditedRegistry>(\n\t\toperations: Operations,\n\t\tdefaults?: CommandDefaults,\n\t): BoundCommand<Context, Operations>\n\toperation: (typeof BoundCommand)['operation']\n\tmiddleware: (typeof BoundCommand)['middleware']\n}\n\n/**\n * Host facade for a component that owns no application execution capability.\n * Declared once; the sole source of the command protocol, query kernel, and\n * observability: `const { Command, Query, observability } = new Foundation(...)`.\n */\nexport class Foundation<\n\tComponent extends FoundationComponentApi = FoundationComponentApi,\n> {\n\treadonly status: Component['functions']['status']\n\treadonly Command: CommandConstructor\n\treadonly Query = Query\n\treadonly observability: Observability\n\t/** Typed-failure boundary — see result.ts. Facade-bound like the kernels. */\n\treadonly executeResultBoundary = executeResultBoundary\n\treadonly projectResult = projectResult\n\treadonly emitSemanticEvent = emitSemanticEvent\n\n\tconstructor(component: Component, options: FoundationOptions) {\n\t\tthis.status = component.functions.status\n\t\tconst observability = new Observability(options.observability)\n\t\tthis.observability = observability\n\t\tconst writeAudit = options.observability.writeAudit ?? (() => undefined)\n\t\tconst checkPermission = options.checkPermission\n\t\tthis.Command = class <\n\t\t\tContext,\n\t\t\tconst Operations extends AuditedRegistry,\n\t\t> extends BoundCommand<Context, Operations> {\n\t\t\tconstructor(operations: Operations, defaults?: CommandDefaults) {\n\t\t\t\tsuper(operations, {\n\t\t\t\t\tobservability,\n\t\t\t\t\twriteAudit,\n\t\t\t\t\tcheckPermission,\n\t\t\t\t\tdefaults,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Kernel CLASSES are deliberately NOT exported: Command, Query, and\n// Observability exist only as capabilities of a Foundation instance —\n// `const { Command, Query, observability } = new Foundation(...)`. A loose\n// import would construct kernels without the injected observability, audit\n// writer, and permission checker. Types stay exported for signatures.\nexport type {\n\tCommandExecution,\n\tCommandInput,\n\tCommandRegistry,\n\tCommandResult,\n\tParseable,\n} from './modules/command/command'\nexport type {\n\tCommandObservation,\n\tObservabilityOptions,\n} from './modules/observability/observability'\nexport type {\n\tAnyQueryMiddleware,\n\tQueryExecution,\n\tQueryMiddleware,\n} from './modules/query/query'\nexport type {\n\tResult,\n\tResultBoundary,\n\tTransactionMetricsContext,\n} from './result'\n\n// The component definition is deliberately NOT re-exported here: Convex's\n// CLI only discovers a component whose resolved file is convex.config.js,\n// so hosts must mount via the dedicated subpath:\n// import foundation from 'cvx-kit/components/foundation/convex.config'\n"],"mappings":"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"}
@@ -13,7 +13,8 @@ type CommandExecution<Context, Registry extends CommandRegistry, Key extends Ope
13
13
  definition: Registry[Key];
14
14
  command: CommandInput<Registry, Key>;
15
15
  parseResult: (value: unknown) => CommandResult<Registry, Key>;
16
- run: () => Promise<CommandResult<Registry, Key>>;
16
+ /** Runs the handler (result-parsed). Accepts a middleware-enriched context. */
17
+ run: (context?: Context) => Promise<CommandResult<Registry, Key>>;
17
18
  }>;
18
19
  type CommandDefinition = Readonly<{
19
20
  command: Parseable<unknown>;
@@ -1,2 +1,2 @@
1
- var e=class extends Error{code=`COMMAND_OPERATION_NOT_CONFIGURED`;name=`CommandConfigurationError`;constructor(){super(`The selected command operation is not configured`)}},t=class{#e;#t;constructor(e){this.#e=e.operations,this.#t=e.execute}exec(t){return async(n,r)=>{let i=`operation`in t?{operation:t.operation,value:r}:(()=>{let e=t.dispatcher.parse(r);return{operation:t.select(e),value:e}})();if(!Object.prototype.hasOwnProperty.call(this.#e,i.operation))throw new e;let a=i.operation,o=this.#e[a],s=o.command.parse(i.value),c=e=>o.result.parse(e),l=t.handler;return this.#t({context:n,operation:a,definition:o,command:s,parseResult:c,run:async()=>c(await l(n,s))})}}};export{t as Command};
1
+ var e=class extends Error{code=`COMMAND_OPERATION_NOT_CONFIGURED`;name=`CommandConfigurationError`;constructor(){super(`The selected command operation is not configured`)}},t=class{#e;#t;constructor(e){this.#e=e.operations,this.#t=e.execute}exec(t){return async(n,r)=>{let i=`operation`in t?{operation:t.operation,value:r}:(()=>{let e=t.dispatcher.parse(r);return{operation:t.select(e),value:e}})();if(!Object.prototype.hasOwnProperty.call(this.#e,i.operation))throw new e;let a=i.operation,o=this.#e[a],s=o.command.parse(i.value),c=e=>o.result.parse(e),l=t.handler;return this.#t({context:n,operation:a,definition:o,command:s,parseResult:c,run:async e=>c(await l(e??n,s))})}}};export{t as Command};
2
2
  //# sourceMappingURL=command.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"command.mjs","names":["#operations","#execute"],"sources":["../../../../../src/components/foundation/modules/command/command.ts"],"sourcesContent":["export type Parseable<Output> = Readonly<{ parse: (value: unknown) => Output }>\n\nexport type CommandRegistry = Readonly<Record<string, CommandDefinition>>\n\nexport type CommandInput<\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = Parsed<Registry[Key]['command']>\n\nexport type CommandResult<\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = Parsed<Registry[Key]['result']>\n\nexport type CommandExecution<\n\tContext,\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = Readonly<{\n\tcontext: Context\n\toperation: Key\n\tdefinition: Registry[Key]\n\tcommand: CommandInput<Registry, Key>\n\tparseResult: (value: unknown) => CommandResult<Registry, Key>\n\trun: () => Promise<CommandResult<Registry, Key>>\n}>\n\ntype CommandDefinition = Readonly<{\n\tcommand: Parseable<unknown>\n\tresult: Parseable<unknown>\n}>\n\ntype Operation<Registry extends CommandRegistry> = Extract<\n\tkeyof Registry,\n\tstring\n>\ntype Parsed<Schema> = Schema extends Parseable<infer Output> ? Output : never\ntype MaybePromise<Value> = Value | Promise<Value>\ntype Execute<Context, Registry extends CommandRegistry> = <\n\tKey extends Operation<Registry>,\n>(\n\texecution: CommandExecution<Context, Registry, Key>,\n) => Promise<CommandResult<Registry, Key>>\n\ntype FixedExecutor<\n\tContext,\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = Readonly<{\n\toperation: Key\n\thandler: (\n\t\tcontext: Context,\n\t\tcommand: CommandInput<Registry, Key>,\n\t) => MaybePromise<CommandResult<Registry, Key>>\n}>\n\ntype DynamicExecutor<\n\tContext,\n\tRegistry extends CommandRegistry,\n\tDispatcher,\n\tKey extends Operation<Registry>,\n> = Readonly<{\n\tdispatcher: Parseable<Dispatcher>\n\tselect: (dispatcher: Dispatcher) => Key\n\thandler: (\n\t\tcontext: Context,\n\t\tcommand: CommandInput<Registry, Key>,\n\t) => MaybePromise<CommandResult<Registry, Key>>\n}>\n\nclass CommandConfigurationError extends Error {\n\treadonly code = 'COMMAND_OPERATION_NOT_CONFIGURED'\n\treadonly name = 'CommandConfigurationError'\n\n\tconstructor() {\n\t\tsuper('The selected command operation is not configured')\n\t}\n}\n\n/** Generic command kernel. Host policy is injected by the owning domain. */\nexport class Command<Context, const Registry extends CommandRegistry> {\n\treadonly #operations: Registry\n\treadonly #execute: Execute<Context, Registry>\n\n\tconstructor(configuration: {\n\t\treadonly operations: Registry\n\t\treadonly execute: Execute<Context, Registry>\n\t}) {\n\t\tthis.#operations = configuration.operations\n\t\tthis.#execute = configuration.execute\n\t}\n\n\texec<const Key extends Operation<Registry>>(\n\t\texecutor: FixedExecutor<Context, Registry, Key>,\n\t): (\n\t\tcontext: Context,\n\t\tcommand: CommandInput<Registry, Key>,\n\t) => Promise<CommandResult<Registry, Key>>\n\texec<Dispatcher, const Key extends Operation<Registry>>(\n\t\texecutor: DynamicExecutor<Context, Registry, Dispatcher, Key>,\n\t): (\n\t\tcontext: Context,\n\t\tcommand: Dispatcher,\n\t) => Promise<CommandResult<Registry, Key>>\n\texec(\n\t\texecutor:\n\t\t\t| FixedExecutor<Context, Registry, Operation<Registry>>\n\t\t\t| DynamicExecutor<Context, Registry, unknown, Operation<Registry>>,\n\t) {\n\t\treturn async (context: Context, value: unknown) => {\n\t\t\tconst selected =\n\t\t\t\t'operation' in executor\n\t\t\t\t\t? { operation: executor.operation, value }\n\t\t\t\t\t: (() => {\n\t\t\t\t\t\t\tconst dispatcher = executor.dispatcher.parse(value)\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\toperation: executor.select(dispatcher),\n\t\t\t\t\t\t\t\tvalue: dispatcher,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})()\n\t\t\tif (\n\t\t\t\t!Object.prototype.hasOwnProperty.call(\n\t\t\t\t\tthis.#operations,\n\t\t\t\t\tselected.operation,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tthrow new CommandConfigurationError()\n\t\t\t}\n\t\t\tconst operation = selected.operation as Operation<Registry>\n\t\t\tconst definition = this.#operations[operation]\n\t\t\tconst command = definition.command.parse(selected.value) as CommandInput<\n\t\t\t\tRegistry,\n\t\t\t\tOperation<Registry>\n\t\t\t>\n\t\t\tconst parseResult = (result: unknown) =>\n\t\t\t\tdefinition.result.parse(result) as CommandResult<\n\t\t\t\t\tRegistry,\n\t\t\t\t\tOperation<Registry>\n\t\t\t\t>\n\t\t\tconst handler = executor.handler as (\n\t\t\t\tcontext: Context,\n\t\t\t\tcommand: CommandInput<Registry, Operation<Registry>>,\n\t\t\t) => MaybePromise<CommandResult<Registry, Operation<Registry>>>\n\t\t\treturn this.#execute({\n\t\t\t\tcontext,\n\t\t\t\toperation,\n\t\t\t\tdefinition,\n\t\t\t\tcommand,\n\t\t\t\tparseResult,\n\t\t\t\trun: async () => parseResult(await handler(context, command)),\n\t\t\t})\n\t\t}\n\t}\n}\n"],"mappings":"AAsEA,IAAM,EAAN,cAAwC,KAAM,CAC7C,KAAgB,mCAChB,KAAgB,4BAEhB,aAAc,CACb,MAAM,kDAAkD,CACzD,CACD,EAGa,EAAb,KAAsE,CACrE,GACA,GAEA,YAAY,EAGT,CACF,KAAKA,GAAc,EAAc,WACjC,KAAKC,GAAW,EAAc,OAC/B,CAcA,KACC,EAGC,CACD,OAAO,MAAO,EAAkB,IAAmB,CAClD,IAAM,EACL,cAAe,EACZ,CAAE,UAAW,EAAS,UAAW,OAAM,OAChC,CACP,IAAM,EAAa,EAAS,WAAW,MAAM,CAAK,EAClD,MAAO,CACN,UAAW,EAAS,OAAO,CAAU,EACrC,MAAO,CACR,CACD,EAAA,CAAG,EACN,GACC,CAAC,OAAO,UAAU,eAAe,KAChC,KAAKD,GACL,EAAS,SACV,EAEA,MAAM,IAAI,EAEX,IAAM,EAAY,EAAS,UACrB,EAAa,KAAKA,GAAY,GAC9B,EAAU,EAAW,QAAQ,MAAM,EAAS,KAAK,EAIjD,EAAe,GACpB,EAAW,OAAO,MAAM,CAAM,EAIzB,EAAU,EAAS,QAIzB,OAAO,KAAKC,GAAS,CACpB,UACA,YACA,aACA,UACA,cACA,IAAK,SAAY,EAAY,MAAM,EAAQ,EAAS,CAAO,CAAC,CAC7D,CAAC,CACF,CACD,CACD"}
1
+ {"version":3,"file":"command.mjs","names":["#operations","#execute"],"sources":["../../../../../src/components/foundation/modules/command/command.ts"],"sourcesContent":["export type Parseable<Output> = Readonly<{ parse: (value: unknown) => Output }>\n\nexport type CommandRegistry = Readonly<Record<string, CommandDefinition>>\n\nexport type CommandInput<\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = Parsed<Registry[Key]['command']>\n\nexport type CommandResult<\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = Parsed<Registry[Key]['result']>\n\nexport type CommandExecution<\n\tContext,\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = Readonly<{\n\tcontext: Context\n\toperation: Key\n\tdefinition: Registry[Key]\n\tcommand: CommandInput<Registry, Key>\n\tparseResult: (value: unknown) => CommandResult<Registry, Key>\n\t/** Runs the handler (result-parsed). Accepts a middleware-enriched context. */\n\trun: (context?: Context) => Promise<CommandResult<Registry, Key>>\n}>\n\ntype CommandDefinition = Readonly<{\n\tcommand: Parseable<unknown>\n\tresult: Parseable<unknown>\n}>\n\ntype Operation<Registry extends CommandRegistry> = Extract<\n\tkeyof Registry,\n\tstring\n>\ntype Parsed<Schema> = Schema extends Parseable<infer Output> ? Output : never\ntype MaybePromise<Value> = Value | Promise<Value>\ntype Execute<Context, Registry extends CommandRegistry> = <\n\tKey extends Operation<Registry>,\n>(\n\texecution: CommandExecution<Context, Registry, Key>,\n) => Promise<CommandResult<Registry, Key>>\n\ntype FixedExecutor<\n\tContext,\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = Readonly<{\n\toperation: Key\n\thandler: (\n\t\tcontext: Context,\n\t\tcommand: CommandInput<Registry, Key>,\n\t) => MaybePromise<CommandResult<Registry, Key>>\n}>\n\ntype DynamicExecutor<\n\tContext,\n\tRegistry extends CommandRegistry,\n\tDispatcher,\n\tKey extends Operation<Registry>,\n> = Readonly<{\n\tdispatcher: Parseable<Dispatcher>\n\tselect: (dispatcher: Dispatcher) => Key\n\thandler: (\n\t\tcontext: Context,\n\t\tcommand: CommandInput<Registry, Key>,\n\t) => MaybePromise<CommandResult<Registry, Key>>\n}>\n\nclass CommandConfigurationError extends Error {\n\treadonly code = 'COMMAND_OPERATION_NOT_CONFIGURED'\n\treadonly name = 'CommandConfigurationError'\n\n\tconstructor() {\n\t\tsuper('The selected command operation is not configured')\n\t}\n}\n\n/** Generic command kernel. Host policy is injected by the owning domain. */\nexport class Command<Context, const Registry extends CommandRegistry> {\n\treadonly #operations: Registry\n\treadonly #execute: Execute<Context, Registry>\n\n\tconstructor(configuration: {\n\t\treadonly operations: Registry\n\t\treadonly execute: Execute<Context, Registry>\n\t}) {\n\t\tthis.#operations = configuration.operations\n\t\tthis.#execute = configuration.execute\n\t}\n\n\texec<const Key extends Operation<Registry>>(\n\t\texecutor: FixedExecutor<Context, Registry, Key>,\n\t): (\n\t\tcontext: Context,\n\t\tcommand: CommandInput<Registry, Key>,\n\t) => Promise<CommandResult<Registry, Key>>\n\texec<Dispatcher, const Key extends Operation<Registry>>(\n\t\texecutor: DynamicExecutor<Context, Registry, Dispatcher, Key>,\n\t): (\n\t\tcontext: Context,\n\t\tcommand: Dispatcher,\n\t) => Promise<CommandResult<Registry, Key>>\n\texec(\n\t\texecutor:\n\t\t\t| FixedExecutor<Context, Registry, Operation<Registry>>\n\t\t\t| DynamicExecutor<Context, Registry, unknown, Operation<Registry>>,\n\t) {\n\t\treturn async (context: Context, value: unknown) => {\n\t\t\tconst selected =\n\t\t\t\t'operation' in executor\n\t\t\t\t\t? { operation: executor.operation, value }\n\t\t\t\t\t: (() => {\n\t\t\t\t\t\t\tconst dispatcher = executor.dispatcher.parse(value)\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\toperation: executor.select(dispatcher),\n\t\t\t\t\t\t\t\tvalue: dispatcher,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})()\n\t\t\tif (\n\t\t\t\t!Object.prototype.hasOwnProperty.call(\n\t\t\t\t\tthis.#operations,\n\t\t\t\t\tselected.operation,\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tthrow new CommandConfigurationError()\n\t\t\t}\n\t\t\tconst operation = selected.operation as Operation<Registry>\n\t\t\tconst definition = this.#operations[operation]\n\t\t\tconst command = definition.command.parse(selected.value) as CommandInput<\n\t\t\t\tRegistry,\n\t\t\t\tOperation<Registry>\n\t\t\t>\n\t\t\tconst parseResult = (result: unknown) =>\n\t\t\t\tdefinition.result.parse(result) as CommandResult<\n\t\t\t\t\tRegistry,\n\t\t\t\t\tOperation<Registry>\n\t\t\t\t>\n\t\t\tconst handler = executor.handler as (\n\t\t\t\tcontext: Context,\n\t\t\t\tcommand: CommandInput<Registry, Operation<Registry>>,\n\t\t\t) => MaybePromise<CommandResult<Registry, Operation<Registry>>>\n\t\t\treturn this.#execute({\n\t\t\t\tcontext,\n\t\t\t\toperation,\n\t\t\t\tdefinition,\n\t\t\t\tcommand,\n\t\t\t\tparseResult,\n\t\t\t\trun: async (contextOverride?: Context) =>\n\t\t\t\t\tparseResult(await handler(contextOverride ?? context, command)),\n\t\t\t})\n\t\t}\n\t}\n}\n"],"mappings":"AAuEA,IAAM,EAAN,cAAwC,KAAM,CAC7C,KAAgB,mCAChB,KAAgB,4BAEhB,aAAc,CACb,MAAM,kDAAkD,CACzD,CACD,EAGa,EAAb,KAAsE,CACrE,GACA,GAEA,YAAY,EAGT,CACF,KAAKA,GAAc,EAAc,WACjC,KAAKC,GAAW,EAAc,OAC/B,CAcA,KACC,EAGC,CACD,OAAO,MAAO,EAAkB,IAAmB,CAClD,IAAM,EACL,cAAe,EACZ,CAAE,UAAW,EAAS,UAAW,OAAM,OAChC,CACP,IAAM,EAAa,EAAS,WAAW,MAAM,CAAK,EAClD,MAAO,CACN,UAAW,EAAS,OAAO,CAAU,EACrC,MAAO,CACR,CACD,EAAA,CAAG,EACN,GACC,CAAC,OAAO,UAAU,eAAe,KAChC,KAAKD,GACL,EAAS,SACV,EAEA,MAAM,IAAI,EAEX,IAAM,EAAY,EAAS,UACrB,EAAa,KAAKA,GAAY,GAC9B,EAAU,EAAW,QAAQ,MAAM,EAAS,KAAK,EAIjD,EAAe,GACpB,EAAW,OAAO,MAAM,CAAM,EAIzB,EAAU,EAAS,QAIzB,OAAO,KAAKC,GAAS,CACpB,UACA,YACA,aACA,UACA,cACA,IAAK,KAAO,IACX,EAAY,MAAM,EAAQ,GAAmB,EAAS,CAAO,CAAC,CAChE,CAAC,CACF,CACD,CACD"}
@@ -1,5 +1,5 @@
1
1
  declare namespace query_d_exports {
2
- export { Query, QueryExecution };
2
+ export { AnyQueryMiddleware, Query, QueryExecution, QueryMiddleware };
3
3
  }
4
4
  type QueryExecution<Context, Metadata, Result> = Readonly<{
5
5
  context: Context;
@@ -7,18 +7,45 @@ type QueryExecution<Context, Metadata, Result> = Readonly<{
7
7
  run: () => Promise<Result>;
8
8
  }>;
9
9
  type MaybePromise<Value> = Value | Promise<Value>;
10
+ /**
11
+ * Composable, next()-based middleware around every query handler. Runs
12
+ * inside the host's injected execute policy: `next({ context })` merges
13
+ * enrichment into the context handed to inner middleware and the handler.
14
+ */
15
+ type QueryMiddleware<Context = never, Extension extends Record<string, unknown> = Record<string, unknown>> = (input: {
16
+ metadata: object;
17
+ context: Context;
18
+ next: (options?: {
19
+ context?: Extension;
20
+ }) => Promise<unknown>;
21
+ }) => Promise<unknown>;
22
+ /** Any-context middleware — what kernels and executors accept. */
23
+ type AnyQueryMiddleware = (input: {
24
+ metadata: object;
25
+ context: never;
26
+ next: (options?: {
27
+ context?: unknown;
28
+ }) => Promise<unknown>;
29
+ }) => Promise<unknown>;
10
30
  /** Generic query kernel. Authorization remains an injected host policy. */
11
31
  declare class Query<Context, Defaults extends object> {
12
32
  #private;
33
+ /**
34
+ * Identity helper that types a middleware against its context and the
35
+ * extension it passes downstream via next({ context }).
36
+ */
37
+ static middleware<Context = never, const Extension extends Record<string, unknown> = Record<string, unknown>>(middleware: QueryMiddleware<Context, Extension>): QueryMiddleware<Context, Extension>;
13
38
  constructor(configuration: {
14
39
  readonly defaults: Defaults;
40
+ readonly middleware?: readonly AnyQueryMiddleware[];
15
41
  readonly execute: <Metadata extends object, Result>(execution: QueryExecution<Context, Defaults & Metadata, Result>) => Promise<Result>;
16
42
  });
17
43
  exec<Arguments extends readonly unknown[], Result, Metadata extends object = object>(executor: {
18
44
  readonly metadata?: Metadata;
45
+ readonly middleware?: readonly AnyQueryMiddleware[];
19
46
  readonly handler: (context: Context, ...args: Arguments) => MaybePromise<Result>;
20
47
  }): (context: Context, ...args: Arguments) => Promise<Result>;
21
48
  }
22
49
  //#endregion
23
- export { Query, QueryExecution, query_d_exports };
50
+ export { AnyQueryMiddleware, Query, QueryExecution, QueryMiddleware, query_d_exports };
24
51
  //# sourceMappingURL=query.d.mts.map
@@ -1,2 +1,2 @@
1
- var e=class{#e;#t;constructor(e){this.#e=e.defaults,this.#t=e.execute}exec(e){return(t,...n)=>this.#t({context:t,metadata:{...this.#e,...e.metadata??{}},run:async()=>e.handler(t,...n)})}};export{e as Query};
1
+ var e=class extends Error{code=`QUERY_MIDDLEWARE_NEXT_REUSED`;name=`QueryMiddlewareError`;constructor(){super(`A query middleware called next() more than once`)}},t=class{#e;#t;#n;static middleware(e){return e}constructor(e){this.#e=e.defaults,this.#t=e.middleware??[],this.#n=e.execute}exec(t){return(n,...r)=>{let i={...this.#e,...t.metadata??{}},a=[...this.#t,...t.middleware??[]];return this.#n({context:n,metadata:i,run:async()=>{if(a.length===0)return t.handler(n,...r);let o=-1,s=async(n,c)=>{if(n<=o)throw new e;o=n;let l=a[n];return l?l({metadata:i,context:c,next:e=>s(n+1,e?.context?{...c,...e.context}:c)}):t.handler(c,...r)};return await s(0,n)}})}}};export{t as Query};
2
2
  //# sourceMappingURL=query.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"query.mjs","names":["#defaults","#execute"],"sources":["../../../../../src/components/foundation/modules/query/query.ts"],"sourcesContent":["export type QueryExecution<Context, Metadata, Result> = Readonly<{\n\tcontext: Context\n\tmetadata: Metadata\n\trun: () => Promise<Result>\n}>\n\ntype MaybePromise<Value> = Value | Promise<Value>\n\n/** Generic query kernel. Authorization remains an injected host policy. */\nexport class Query<Context, Defaults extends object> {\n\treadonly #defaults: Defaults\n\treadonly #execute: <Metadata extends object, Result>(\n\t\texecution: QueryExecution<Context, Defaults & Metadata, Result>,\n\t) => Promise<Result>\n\n\tconstructor(configuration: {\n\t\treadonly defaults: Defaults\n\t\treadonly execute: <Metadata extends object, Result>(\n\t\t\texecution: QueryExecution<Context, Defaults & Metadata, Result>,\n\t\t) => Promise<Result>\n\t}) {\n\t\tthis.#defaults = configuration.defaults\n\t\tthis.#execute = configuration.execute\n\t}\n\n\texec<\n\t\tArguments extends readonly unknown[],\n\t\tResult,\n\t\tMetadata extends object = object,\n\t>(executor: {\n\t\treadonly metadata?: Metadata\n\t\treadonly handler: (\n\t\t\tcontext: Context,\n\t\t\t...args: Arguments\n\t\t) => MaybePromise<Result>\n\t}): (context: Context, ...args: Arguments) => Promise<Result> {\n\t\treturn (context, ...args) =>\n\t\t\tthis.#execute({\n\t\t\t\tcontext,\n\t\t\t\tmetadata: {\n\t\t\t\t\t...this.#defaults,\n\t\t\t\t\t...(executor.metadata ?? ({} as Metadata)),\n\t\t\t\t},\n\t\t\t\trun: async () => executor.handler(context, ...args),\n\t\t\t})\n\t}\n}\n"],"mappings":"AASA,IAAa,EAAb,KAAqD,CACpD,GACA,GAIA,YAAY,EAKT,CACF,KAAKA,GAAY,EAAc,SAC/B,KAAKC,GAAW,EAAc,OAC/B,CAEA,KAIE,EAM4D,CAC7D,OAAQ,EAAS,GAAG,IACnB,KAAKA,GAAS,CACb,UACA,SAAU,CACT,GAAG,KAAKD,GACR,GAAI,EAAS,UAAa,CAAC,CAC5B,EACA,IAAK,SAAY,EAAS,QAAQ,EAAS,GAAG,CAAI,CACnD,CAAC,CACH,CACD"}
1
+ {"version":3,"file":"query.mjs","names":["#defaults","#middleware","#execute"],"sources":["../../../../../src/components/foundation/modules/query/query.ts"],"sourcesContent":["export type QueryExecution<Context, Metadata, Result> = Readonly<{\n\tcontext: Context\n\tmetadata: Metadata\n\trun: () => Promise<Result>\n}>\n\ntype MaybePromise<Value> = Value | Promise<Value>\n\n/**\n * Composable, next()-based middleware around every query handler. Runs\n * inside the host's injected execute policy: `next({ context })` merges\n * enrichment into the context handed to inner middleware and the handler.\n */\nexport type QueryMiddleware<\n\tContext = never,\n\tExtension extends Record<string, unknown> = Record<string, unknown>,\n> = (input: {\n\tmetadata: object\n\tcontext: Context\n\tnext: (options?: {\n\t\tcontext?: Extension\n\t}) => Promise<unknown>\n}) => Promise<unknown>\n\n/** Any-context middleware — what kernels and executors accept. */\n// Contravariant seam: the supertype every typed QueryMiddleware<C, E>\n// assigns to (context contra-narrows to never; next's options widen to\n// unknown). Definition sites stay typed via Query.middleware.\nexport type AnyQueryMiddleware = (input: {\n\tmetadata: object\n\tcontext: never\n\tnext: (options?: { context?: unknown }) => Promise<unknown>\n}) => Promise<unknown>\n\nclass QueryMiddlewareError extends Error {\n\treadonly code = 'QUERY_MIDDLEWARE_NEXT_REUSED'\n\treadonly name = 'QueryMiddlewareError'\n\n\tconstructor() {\n\t\tsuper('A query middleware called next() more than once')\n\t}\n}\n\n/** Generic query kernel. Authorization remains an injected host policy. */\nexport class Query<Context, Defaults extends object> {\n\treadonly #defaults: Defaults\n\treadonly #middleware: readonly AnyQueryMiddleware[]\n\treadonly #execute: <Metadata extends object, Result>(\n\t\texecution: QueryExecution<Context, Defaults & Metadata, Result>,\n\t) => Promise<Result>\n\n\t/**\n\t * Identity helper that types a middleware against its context and the\n\t * extension it passes downstream via next({ context }).\n\t */\n\tstatic middleware<\n\t\tContext = never,\n\t\tconst Extension extends Record<string, unknown> = Record<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>,\n\t>(\n\t\tmiddleware: QueryMiddleware<Context, Extension>,\n\t): QueryMiddleware<Context, Extension> {\n\t\treturn middleware\n\t}\n\n\tconstructor(configuration: {\n\t\treadonly defaults: Defaults\n\t\treadonly middleware?: readonly AnyQueryMiddleware[]\n\t\treadonly execute: <Metadata extends object, Result>(\n\t\t\texecution: QueryExecution<Context, Defaults & Metadata, Result>,\n\t\t) => Promise<Result>\n\t}) {\n\t\tthis.#defaults = configuration.defaults\n\t\tthis.#middleware = configuration.middleware ?? []\n\t\tthis.#execute = configuration.execute\n\t}\n\n\texec<\n\t\tArguments extends readonly unknown[],\n\t\tResult,\n\t\tMetadata extends object = object,\n\t>(executor: {\n\t\treadonly metadata?: Metadata\n\t\treadonly middleware?: readonly AnyQueryMiddleware[]\n\t\treadonly handler: (\n\t\t\tcontext: Context,\n\t\t\t...args: Arguments\n\t\t) => MaybePromise<Result>\n\t}): (context: Context, ...args: Arguments) => Promise<Result> {\n\t\treturn (context, ...args) => {\n\t\t\tconst metadata = {\n\t\t\t\t...this.#defaults,\n\t\t\t\t...(executor.metadata ?? ({} as Metadata)),\n\t\t\t}\n\t\t\tconst middleware = [\n\t\t\t\t...this.#middleware,\n\t\t\t\t...(executor.middleware ?? []),\n\t\t\t]\n\t\t\tconst run = async (): Promise<Result> => {\n\t\t\t\tif (middleware.length === 0) {\n\t\t\t\t\treturn executor.handler(context, ...args)\n\t\t\t\t}\n\t\t\t\tlet deepest = -1\n\t\t\t\tconst dispatch = async (\n\t\t\t\t\tindex: number,\n\t\t\t\t\tcurrent: Context,\n\t\t\t\t): Promise<unknown> => {\n\t\t\t\t\tif (index <= deepest) throw new QueryMiddlewareError()\n\t\t\t\t\tdeepest = index\n\t\t\t\t\tconst layer = middleware[index]\n\t\t\t\t\tif (!layer) return executor.handler(current, ...args)\n\t\t\t\t\treturn layer({\n\t\t\t\t\t\tmetadata,\n\t\t\t\t\t\tcontext: current as never,\n\t\t\t\t\t\tnext: (options) =>\n\t\t\t\t\t\t\tdispatch(\n\t\t\t\t\t\t\t\tindex + 1,\n\t\t\t\t\t\t\t\toptions?.context\n\t\t\t\t\t\t\t\t\t? ({\n\t\t\t\t\t\t\t\t\t\t\t...(current as object),\n\t\t\t\t\t\t\t\t\t\t\t...(options.context as object),\n\t\t\t\t\t\t\t\t\t\t} as Context)\n\t\t\t\t\t\t\t\t\t: current,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treturn (await dispatch(0, context)) as Result\n\t\t\t}\n\t\t\treturn this.#execute({ context, metadata, run })\n\t\t}\n\t}\n}\n"],"mappings":"AAkCA,IAAM,EAAN,cAAmC,KAAM,CACxC,KAAgB,+BAChB,KAAgB,uBAEhB,aAAc,CACb,MAAM,iDAAiD,CACxD,CACD,EAGa,EAAb,KAAqD,CACpD,GACA,GACA,GAQA,OAAO,WAON,EACsC,CACtC,OAAO,CACR,CAEA,YAAY,EAMT,CACF,KAAKA,GAAY,EAAc,SAC/B,KAAKC,GAAc,EAAc,YAAc,CAAC,EAChD,KAAKC,GAAW,EAAc,OAC/B,CAEA,KAIE,EAO4D,CAC7D,OAAQ,EAAS,GAAG,IAAS,CAC5B,IAAM,EAAW,CAChB,GAAG,KAAKF,GACR,GAAI,EAAS,UAAa,CAAC,CAC5B,EACM,EAAa,CAClB,GAAG,KAAKC,GACR,GAAI,EAAS,YAAc,CAAC,CAC7B,EA+BA,OAAO,KAAKC,GAAS,CAAE,UAAS,WAAU,aA9BD,CACxC,GAAI,EAAW,SAAW,EACzB,OAAO,EAAS,QAAQ,EAAS,GAAG,CAAI,EAEzC,IAAI,EAAU,GACR,EAAW,MAChB,EACA,IACsB,CACtB,GAAI,GAAS,EAAS,MAAM,IAAI,EAChC,EAAU,EACV,IAAM,EAAQ,EAAW,GAEzB,OADK,EACE,EAAM,CACZ,WACA,QAAS,EACT,KAAO,GACN,EACC,EAAQ,EACR,GAAS,QACL,CACD,GAAI,EACJ,GAAI,EAAQ,OACb,EACC,CACJ,CACF,CAAC,EAdkB,EAAS,QAAQ,EAAS,GAAG,CAAI,CAerD,EACA,OAAQ,MAAM,EAAS,EAAG,CAAO,CAClC,CAC8C,CAAC,CAChD,CACD,CACD"}
@@ -0,0 +1,79 @@
1
+ import { ErrorFactory } from "./errors.mjs";
2
+ import { AnyCommandMiddleware, ApplicationCommand, AuditedRegistry, CommandConstructor } from "./components/foundation/client.mjs";
3
+ import { z } from "zod";
4
+ //#region src/crud.d.ts
5
+ /**
6
+ * The zodTable surface the factory consumes — structural, so any zodTable /
7
+ * tenantTable return satisfies it.
8
+ */
9
+ type CrudTable = {
10
+ tableName: string;
11
+ commandInput: z.ZodObject<Record<string, z.ZodType>>;
12
+ storage: z.ZodObject<Record<string, z.ZodType>>;
13
+ };
14
+ type CrudContext = {
15
+ db: {
16
+ insert: (table: never, value: never) => Promise<unknown>;
17
+ patch: (id: never, value: never) => Promise<unknown>;
18
+ };
19
+ };
20
+ type MaybePromise<Value> = Value | Promise<Value>;
21
+ type CrudConfig<Context extends CrudContext> = {
22
+ /** The Foundation-bound Command class (destructured from the facade). */
23
+ Command: CommandConstructor;
24
+ table: CrudTable;
25
+ /** Audit aggregate type; enforced by the aggregates allowlist. */
26
+ aggregateType: string;
27
+ /** Observability/audit classification. Default 'business'. */
28
+ classification?: string;
29
+ /** Derives the audited actor id from the ctx (usually ctx.actor.userId). */
30
+ actor: (context: Context) => string;
31
+ /**
32
+ * Stamps server-owned fields onto creates (tenant, denormalized ids...).
33
+ * REQUIRED when the table is a tenantTable — forgetting it would insert
34
+ * untenanted rows; the factory fails fast at construction instead.
35
+ */
36
+ enrich?: (context: Context, command: Record<string, unknown>) => Record<string, unknown> | Promise<Record<string, unknown>>;
37
+ /** Per-operation preconditions; run before the handlers. */
38
+ guards?: {
39
+ create?: (context: Context, command: never) => MaybePromise<void>;
40
+ update?: (context: Context, command: never) => MaybePromise<void>;
41
+ archive?: (context: Context, command: never) => MaybePromise<void>;
42
+ };
43
+ /** Per-operation middleware applied to all three generated operations. */
44
+ middleware?: readonly AnyCommandMiddleware[];
45
+ errors?: ErrorFactory;
46
+ };
47
+ /**
48
+ * Generates the standard create/update/archive operations from one zodTable
49
+ * declaration, fully inside the audited command pipeline: strict command
50
+ * inputs from `commandFields`, mandatory audit with the declared aggregate
51
+ * type, soft delete only (archive sets `archivedAt` — no hard-delete
52
+ * operation is generated, per kit doctrine).
53
+ *
54
+ * convex-helpers ships a same-named `crud` helper; it was deliberately not
55
+ * used — it bypasses the command pipeline (no audits, no guards, and hard
56
+ * deletes). See docs/crud.md.
57
+ */
58
+ type CrudCommands<Context extends CrudContext> = {
59
+ commands: ApplicationCommand<Context, AuditedRegistry>;
60
+ operations: AuditedRegistry;
61
+ executeCreate: (context: Context, command: Record<string, unknown>) => Promise<{
62
+ id: string;
63
+ }>;
64
+ executeUpdate: (context: Context, command: {
65
+ id: string;
66
+ data: Record<string, unknown>;
67
+ }) => Promise<{
68
+ ok: true;
69
+ }>;
70
+ executeArchive: (context: Context, command: {
71
+ id: string;
72
+ }) => Promise<{
73
+ ok: true;
74
+ }>;
75
+ };
76
+ declare function createCrudCommands<Context extends CrudContext>(config: CrudConfig<Context>): CrudCommands<Context>;
77
+ //#endregion
78
+ export { CrudCommands, CrudConfig, createCrudCommands };
79
+ //# sourceMappingURL=crud.d.mts.map
package/dist/crud.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{defaultErrors as e}from"./errors.mjs";import{zid as t}from"convex-helpers/server/zod4";import{z as n}from"zod";function r(r){let i=r.errors??e,a=r.table.tableName,o=r.classification??`business`;if(`tenant`in r.table.storage.shape&&!r.enrich)return i.throw({code:`CRUD_ENRICH_REQUIRED`,message:`Table "${a}" is a tenantTable: createCrudCommands requires an enrich callback that stamps ctx.tenant`});let s=t(a),c=n.object({id:s}).strict(),l=n.object({ok:n.literal(!0)}).strict(),u=n.object({id:s,data:r.table.commandInput.partial().strict()}).strict(),d=n.object({id:s}).strict(),f={[`${a}.create`]:{command:r.table.commandInput,result:c,classification:o,aggregates:[r.aggregateType],...r.middleware?{middleware:r.middleware}:{},...r.guards?.create?{guard:r.guards.create}:{},audit:(e,t)=>({operation:`${a}.create`,actorId:r.actor(t),aggregate:{type:r.aggregateType,id:e.result.id}})},[`${a}.update`]:{command:u,result:l,classification:o,aggregates:[r.aggregateType],...r.middleware?{middleware:r.middleware}:{},...r.guards?.update?{guard:r.guards.update}:{},audit:(e,t)=>({operation:`${a}.update`,actorId:r.actor(t),aggregate:{type:r.aggregateType,id:e.command.id}})},[`${a}.archive`]:{command:d,result:l,classification:o,aggregates:[r.aggregateType],...r.middleware?{middleware:r.middleware}:{},...r.guards?.archive?{guard:r.guards.archive}:{},audit:(e,t)=>({operation:`${a}.archive`,actorId:r.actor(t),aggregate:{type:r.aggregateType,id:e.command.id}})}},p=new r.Command(f);return{commands:p,operations:f,executeCreate:p.exec({operation:`${a}.create`,handler:(async(e,t)=>{let n=r.enrich?await r.enrich(e,t):{};return{id:await e.db.insert(a,{...t,...n})}})}),executeUpdate:p.exec({operation:`${a}.update`,handler:(async(e,t)=>(await e.db.patch(t.id,t.data),{ok:!0}))}),executeArchive:p.exec({operation:`${a}.archive`,handler:(async(e,t)=>(await e.db.patch(t.id,{archivedAt:Date.now()}),{ok:!0}))})}}export{r as createCrudCommands};
2
+ //# sourceMappingURL=crud.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crud.mjs","names":[],"sources":["../src/crud.ts"],"sourcesContent":["import { zid } from 'convex-helpers/server/zod4'\nimport { z } from 'zod'\nimport type {\n\tAnyCommandMiddleware,\n\tApplicationCommand,\n\tAuditedRegistry,\n\tCommandConstructor,\n} from './components/foundation/client'\nimport { defaultErrors, type ErrorFactory } from './errors'\n\n/**\n * The zodTable surface the factory consumes — structural, so any zodTable /\n * tenantTable return satisfies it.\n */\ntype CrudTable = {\n\ttableName: string\n\t// biome-ignore format: structural zod surface\n\tcommandInput: z.ZodObject<Record<string, z.ZodType>>\n\tstorage: z.ZodObject<Record<string, z.ZodType>>\n}\n\ntype CrudContext = {\n\tdb: {\n\t\tinsert: (table: never, value: never) => Promise<unknown>\n\t\tpatch: (id: never, value: never) => Promise<unknown>\n\t}\n}\n\ntype MaybePromise<Value> = Value | Promise<Value>\n\nexport type CrudConfig<Context extends CrudContext> = {\n\t/** The Foundation-bound Command class (destructured from the facade). */\n\tCommand: CommandConstructor\n\ttable: CrudTable\n\t/** Audit aggregate type; enforced by the aggregates allowlist. */\n\taggregateType: string\n\t/** Observability/audit classification. Default 'business'. */\n\tclassification?: string\n\t/** Derives the audited actor id from the ctx (usually ctx.actor.userId). */\n\tactor: (context: Context) => string\n\t/**\n\t * Stamps server-owned fields onto creates (tenant, denormalized ids...).\n\t * REQUIRED when the table is a tenantTable — forgetting it would insert\n\t * untenanted rows; the factory fails fast at construction instead.\n\t */\n\tenrich?: (\n\t\tcontext: Context,\n\t\tcommand: Record<string, unknown>,\n\t) => Record<string, unknown> | Promise<Record<string, unknown>>\n\t/** Per-operation preconditions; run before the handlers. */\n\tguards?: {\n\t\tcreate?: (context: Context, command: never) => MaybePromise<void>\n\t\tupdate?: (context: Context, command: never) => MaybePromise<void>\n\t\tarchive?: (context: Context, command: never) => MaybePromise<void>\n\t}\n\t/** Per-operation middleware applied to all three generated operations. */\n\tmiddleware?: readonly AnyCommandMiddleware[]\n\terrors?: ErrorFactory\n}\n\n/**\n * Generates the standard create/update/archive operations from one zodTable\n * declaration, fully inside the audited command pipeline: strict command\n * inputs from `commandFields`, mandatory audit with the declared aggregate\n * type, soft delete only (archive sets `archivedAt` — no hard-delete\n * operation is generated, per kit doctrine).\n *\n * convex-helpers ships a same-named `crud` helper; it was deliberately not\n * used — it bypasses the command pipeline (no audits, no guards, and hard\n * deletes). See docs/crud.md.\n */\nexport type CrudCommands<Context extends CrudContext> = {\n\tcommands: ApplicationCommand<Context, AuditedRegistry>\n\toperations: AuditedRegistry\n\texecuteCreate: (\n\t\tcontext: Context,\n\t\tcommand: Record<string, unknown>,\n\t) => Promise<{ id: string }>\n\texecuteUpdate: (\n\t\tcontext: Context,\n\t\tcommand: { id: string; data: Record<string, unknown> },\n\t) => Promise<{ ok: true }>\n\texecuteArchive: (\n\t\tcontext: Context,\n\t\tcommand: { id: string },\n\t) => Promise<{ ok: true }>\n}\n\nexport function createCrudCommands<Context extends CrudContext>(\n\tconfig: CrudConfig<Context>,\n): CrudCommands<Context> {\n\tconst errors = config.errors ?? defaultErrors\n\tconst name = config.table.tableName\n\tconst classification = config.classification ?? 'business'\n\tconst isTenantTable = 'tenant' in config.table.storage.shape\n\tif (isTenantTable && !config.enrich) {\n\t\treturn errors.throw({\n\t\t\tcode: 'CRUD_ENRICH_REQUIRED',\n\t\t\tmessage: `Table \"${name}\" is a tenantTable: createCrudCommands requires an enrich callback that stamps ctx.tenant`,\n\t\t})\n\t}\n\n\tconst id = zid(name)\n\tconst createResult = z.object({ id }).strict()\n\tconst okResult = z.object({ ok: z.literal(true) }).strict()\n\tconst updateInput = z\n\t\t.object({ id, data: config.table.commandInput.partial().strict() })\n\t\t.strict()\n\tconst archiveInput = z.object({ id }).strict()\n\n\tconst operations = {\n\t\t[`${name}.create`]: {\n\t\t\tcommand: config.table.commandInput,\n\t\t\tresult: createResult,\n\t\t\tclassification,\n\t\t\taggregates: [config.aggregateType],\n\t\t\t...(config.middleware ? { middleware: config.middleware } : {}),\n\t\t\t...(config.guards?.create ? { guard: config.guards.create } : {}),\n\t\t\taudit: (\n\t\t\t\tresolution: { command: unknown; result: { id: string } },\n\t\t\t\tcontext: Context,\n\t\t\t) => ({\n\t\t\t\toperation: `${name}.create`,\n\t\t\t\tactorId: config.actor(context),\n\t\t\t\taggregate: { type: config.aggregateType, id: resolution.result.id },\n\t\t\t}),\n\t\t},\n\t\t[`${name}.update`]: {\n\t\t\tcommand: updateInput,\n\t\t\tresult: okResult,\n\t\t\tclassification,\n\t\t\taggregates: [config.aggregateType],\n\t\t\t...(config.middleware ? { middleware: config.middleware } : {}),\n\t\t\t...(config.guards?.update ? { guard: config.guards.update } : {}),\n\t\t\taudit: (\n\t\t\t\tresolution: { command: { id: string }; result: unknown },\n\t\t\t\tcontext: Context,\n\t\t\t) => ({\n\t\t\t\toperation: `${name}.update`,\n\t\t\t\tactorId: config.actor(context),\n\t\t\t\taggregate: { type: config.aggregateType, id: resolution.command.id },\n\t\t\t}),\n\t\t},\n\t\t[`${name}.archive`]: {\n\t\t\tcommand: archiveInput,\n\t\t\tresult: okResult,\n\t\t\tclassification,\n\t\t\taggregates: [config.aggregateType],\n\t\t\t...(config.middleware ? { middleware: config.middleware } : {}),\n\t\t\t...(config.guards?.archive ? { guard: config.guards.archive } : {}),\n\t\t\taudit: (\n\t\t\t\tresolution: { command: { id: string }; result: unknown },\n\t\t\t\tcontext: Context,\n\t\t\t) => ({\n\t\t\t\toperation: `${name}.archive`,\n\t\t\t\tactorId: config.actor(context),\n\t\t\t\taggregate: { type: config.aggregateType, id: resolution.command.id },\n\t\t\t}),\n\t\t},\n\t} as never\n\n\tconst commands = new config.Command<Context, AuditedRegistry>(operations)\n\n\tconst executeCreate = commands.exec({\n\t\toperation: `${name}.create` as never,\n\t\thandler: (async (context: Context, command: Record<string, unknown>) => {\n\t\t\tconst enrichment = config.enrich\n\t\t\t\t? await config.enrich(context, command)\n\t\t\t\t: {}\n\t\t\tconst inserted = await context.db.insert(\n\t\t\t\tname as never,\n\t\t\t\t{ ...command, ...enrichment } as never,\n\t\t\t)\n\t\t\treturn { id: inserted }\n\t\t}) as never,\n\t})\n\n\tconst executeUpdate = commands.exec({\n\t\toperation: `${name}.update` as never,\n\t\thandler: (async (\n\t\t\tcontext: Context,\n\t\t\tcommand: { id: string; data: Record<string, unknown> },\n\t\t) => {\n\t\t\tawait context.db.patch(command.id as never, command.data as never)\n\t\t\treturn { ok: true }\n\t\t}) as never,\n\t})\n\n\tconst executeArchive = commands.exec({\n\t\toperation: `${name}.archive` as never,\n\t\thandler: (async (context: Context, command: { id: string }) => {\n\t\t\tawait context.db.patch(\n\t\t\t\tcommand.id as never,\n\t\t\t\t{ archivedAt: Date.now() } as never,\n\t\t\t)\n\t\t\treturn { ok: true }\n\t\t}) as never,\n\t})\n\n\treturn {\n\t\tcommands,\n\t\toperations: operations as AuditedRegistry,\n\t\texecuteCreate,\n\t\texecuteUpdate,\n\t\texecuteArchive,\n\t} as unknown as CrudCommands<Context>\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,7 +1,13 @@
1
+ import { AgentToolHandlers, AgentToolRecord, createAgentTools } from "./agent-tools.mjs";
1
2
  import { ErrorFactory, KitError, defaultErrors } from "./errors.mjs";
2
3
  import { RLSConfig, Rules, TENANT_FIELD, assertTenantOwned, composeRules, createTenantRules, requireTenantReference } from "./tenancy.mjs";
3
4
  import { Actor, AuthBundle, AuthFunctionsConfig, DefaultRole, Include, IncludedQuery, SecurityConfig, createAuthFunctions, createInclude, defaultRoleMap } from "./auth.mjs";
4
- import { ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandDefaults, Foundation, FoundationOptions, PermissionChecker } from "./components/foundation/client.mjs";
5
- import { TIMESTAMP_FIELDS, TableBoundaryOptions, createModule, jsonSafeZid, tenantTable, zodTable, zodVariantTable } from "./zod-table.mjs";
5
+ import { AnyQueryMiddleware, QueryMiddleware } from "./components/foundation/modules/query/query.mjs";
6
+ import { AnyCommandMiddleware, ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandConstructor, CommandDefaults, CommandMiddleware, Foundation, FoundationOptions, PermissionChecker } from "./components/foundation/client.mjs";
7
+ import { CrudConfig, createCrudCommands } from "./crud.mjs";
8
+ import { TIMESTAMP_FIELDS, TableBoundaryOptions, createModule, jsonSafeZid, paginated, tenantTable, zodTable, zodVariantTable } from "./zod-table.mjs";
9
+ import { RateLimiterLike, rateLimit } from "./middleware.mjs";
10
+ import { WebhookBoundaryConfig, createWebhookBoundary, recordWebhookEvent, webhookEventsTable } from "./webhooks.mjs";
11
+ import { createStateMachine } from "./state-machine.mjs";
6
12
  import { Change, Trigger, Triggers, appendOnly, createTriggers, noDelete, tenantOwnership, timestamps } from "./triggers.mjs";
7
- export { type Actor, type ApplicationCommand, type AuditEntryInput, type AuditWriter, type AuditedOperation, type AuditedRegistry, type AuthBundle, type AuthFunctionsConfig, type Change, type CommandDefaults, type DefaultRole, type ErrorFactory, Foundation, type FoundationOptions, type Include, type IncludedQuery, KitError, type PermissionChecker, type RLSConfig, type Rules, type SecurityConfig, TENANT_FIELD, TIMESTAMP_FIELDS, type TableBoundaryOptions, type Trigger, Triggers, appendOnly, assertTenantOwned, composeRules, createAuthFunctions, createInclude, createModule, createTenantRules, createTriggers, defaultErrors, defaultRoleMap, jsonSafeZid, noDelete, requireTenantReference, tenantOwnership, tenantTable, timestamps, zodTable, zodVariantTable };
13
+ export { type Actor, type AgentToolHandlers, type AgentToolRecord, type AnyCommandMiddleware, type AnyQueryMiddleware, type ApplicationCommand, type AuditEntryInput, type AuditWriter, type AuditedOperation, type AuditedRegistry, type AuthBundle, type AuthFunctionsConfig, type Change, type CommandConstructor, type CommandDefaults, type CommandMiddleware, type CrudConfig, type DefaultRole, type ErrorFactory, Foundation, type FoundationOptions, type Include, type IncludedQuery, KitError, type PermissionChecker, type QueryMiddleware, type RLSConfig, type RateLimiterLike, type Rules, type SecurityConfig, TENANT_FIELD, TIMESTAMP_FIELDS, type TableBoundaryOptions, type Trigger, Triggers, type WebhookBoundaryConfig, appendOnly, assertTenantOwned, composeRules, createAgentTools, createAuthFunctions, createCrudCommands, createInclude, createModule, createStateMachine, createTenantRules, createTriggers, createWebhookBoundary, defaultErrors, defaultRoleMap, jsonSafeZid, noDelete, paginated, rateLimit, recordWebhookEvent, requireTenantReference, tenantOwnership, tenantTable, timestamps, webhookEventsTable, zodTable, zodVariantTable };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{KitError as e,defaultErrors as t}from"./errors.mjs";import{TENANT_FIELD as n,assertTenantOwned as r,composeRules as i,createTenantRules as a,requireTenantReference as o}from"./tenancy.mjs";import{createAuthFunctions as s,createInclude as c,defaultRoleMap as l}from"./auth.mjs";import{TIMESTAMP_FIELDS as u,createModule as d,jsonSafeZid as f,tenantTable as p,zodTable as m,zodVariantTable as h}from"./zod-table.mjs";import{Triggers as g,appendOnly as _,createTriggers as v,noDelete as y,tenantOwnership as b,timestamps as x}from"./triggers.mjs";import{Foundation as S}from"./components/foundation/client.mjs";export{S as Foundation,e as KitError,n as TENANT_FIELD,u as TIMESTAMP_FIELDS,g as Triggers,_ as appendOnly,r as assertTenantOwned,i as composeRules,s as createAuthFunctions,c as createInclude,d as createModule,a as createTenantRules,v as createTriggers,t as defaultErrors,l as defaultRoleMap,f as jsonSafeZid,y as noDelete,o as requireTenantReference,b as tenantOwnership,p as tenantTable,x as timestamps,m as zodTable,h as zodVariantTable};
1
+ import{createAgentTools as e}from"./agent-tools.mjs";import{KitError as t,defaultErrors as n}from"./errors.mjs";import{TENANT_FIELD as r,assertTenantOwned as i,composeRules as a,createTenantRules as o,requireTenantReference as s}from"./tenancy.mjs";import{createAuthFunctions as c,createInclude as l,defaultRoleMap as u}from"./auth.mjs";import{createCrudCommands as d}from"./crud.mjs";import{TIMESTAMP_FIELDS as f,createModule as p,jsonSafeZid as m,paginated as h,tenantTable as g,zodTable as _,zodVariantTable as v}from"./zod-table.mjs";import{rateLimit as y}from"./middleware.mjs";import{createWebhookBoundary as b,recordWebhookEvent as x,webhookEventsTable as S}from"./webhooks.mjs";import{createStateMachine as C}from"./state-machine.mjs";import{Triggers as w,appendOnly as T,createTriggers as E,noDelete as D,tenantOwnership as O,timestamps as k}from"./triggers.mjs";import{Foundation as A}from"./components/foundation/client.mjs";export{A as Foundation,t as KitError,r as TENANT_FIELD,f as TIMESTAMP_FIELDS,w as Triggers,T as appendOnly,i as assertTenantOwned,a as composeRules,e as createAgentTools,c as createAuthFunctions,d as createCrudCommands,l as createInclude,p as createModule,C as createStateMachine,o as createTenantRules,E as createTriggers,b as createWebhookBoundary,n as defaultErrors,u as defaultRoleMap,m as jsonSafeZid,D as noDelete,h as paginated,y as rateLimit,x as recordWebhookEvent,s as requireTenantReference,O as tenantOwnership,g as tenantTable,k as timestamps,S as webhookEventsTable,_ as zodTable,v as zodVariantTable};
@@ -0,0 +1,46 @@
1
+ import { ErrorFactory } from "./errors.mjs";
2
+ //#region src/middleware.d.ts
3
+ /**
4
+ * Structural surface of @convex-dev/rate-limiter's RateLimiter (checked
5
+ * against 0.3.x; re-verify at install). The host mounts the component and
6
+ * constructs the limiter; the kit only ever receives the instance —
7
+ * injection keeps the kit dependency-free.
8
+ */
9
+ type RateLimiterLike = {
10
+ limit: (ctx: never, name: string, options?: {
11
+ key?: string;
12
+ }) => Promise<{
13
+ ok: boolean;
14
+ retryAfter?: number;
15
+ }>;
16
+ };
17
+ /**
18
+ * The first packaged middleware: rate limiting on the Command/Query
19
+ * middleware seams. Runs before guards and the handler; a rejected call
20
+ * throws RATE_LIMITED (audit and handler never run). Keyed by ctx.tenant by
21
+ * default — when no key fn is given and ctx.tenant is undefined, this is a
22
+ * configuration error, not a silent shared bucket.
23
+ *
24
+ * The returned middleware is shape-compatible with both AnyCommandMiddleware
25
+ * and AnyQueryMiddleware.
26
+ */
27
+ declare function rateLimit(config: {
28
+ limiter: RateLimiterLike;
29
+ name: string;
30
+ /** Defaults to ctx.tenant. */
31
+ key?: (context: never) => string;
32
+ /** Observe a rejection before the throw (metrics, headers). */
33
+ onLimit?: (status: {
34
+ ok: boolean;
35
+ retryAfter?: number;
36
+ }, context: never) => void | Promise<void>;
37
+ errors?: ErrorFactory;
38
+ }): (input: {
39
+ context: never;
40
+ next: (options?: {
41
+ context?: unknown;
42
+ }) => Promise<unknown>;
43
+ }) => Promise<unknown>;
44
+ //#endregion
45
+ export { RateLimiterLike, rateLimit };
46
+ //# sourceMappingURL=middleware.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{defaultErrors as e}from"./errors.mjs";function t(t){let n=t.errors??e;return async e=>{let r=t.key?t.key(e.context):e.context.tenant;if(r===void 0)return n.throw({code:`RATE_LIMIT_KEY_MISSING`,message:`rateLimit("${t.name}") has no key: ctx.tenant is undefined and no key fn was configured`});let i=await t.limiter.limit(e.context,t.name,{key:r});return i.ok?e.next():(await t.onLimit?.(i,e.context),n.throw({code:`RATE_LIMITED`,message:`Rate limit "${t.name}" exceeded${i.retryAfter===void 0?``:`; retry after ${Math.ceil(i.retryAfter)}ms`}`}))}}export{t as rateLimit};
2
+ //# sourceMappingURL=middleware.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.mjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import { defaultErrors, type ErrorFactory } from './errors'\n\n/**\n * Structural surface of @convex-dev/rate-limiter's RateLimiter (checked\n * against 0.3.x; re-verify at install). The host mounts the component and\n * constructs the limiter; the kit only ever receives the instance —\n * injection keeps the kit dependency-free.\n */\nexport type RateLimiterLike = {\n\tlimit: (\n\t\tctx: never,\n\t\tname: string,\n\t\toptions?: { key?: string },\n\t) => Promise<{ ok: boolean; retryAfter?: number }>\n}\n\n/**\n * The first packaged middleware: rate limiting on the Command/Query\n * middleware seams. Runs before guards and the handler; a rejected call\n * throws RATE_LIMITED (audit and handler never run). Keyed by ctx.tenant by\n * default — when no key fn is given and ctx.tenant is undefined, this is a\n * configuration error, not a silent shared bucket.\n *\n * The returned middleware is shape-compatible with both AnyCommandMiddleware\n * and AnyQueryMiddleware.\n */\nexport function rateLimit(config: {\n\tlimiter: RateLimiterLike\n\tname: string\n\t/** Defaults to ctx.tenant. */\n\tkey?: (context: never) => string\n\t/** Observe a rejection before the throw (metrics, headers). */\n\tonLimit?: (\n\t\tstatus: { ok: boolean; retryAfter?: number },\n\t\tcontext: never,\n\t) => void | Promise<void>\n\terrors?: ErrorFactory\n}) {\n\tconst errors = config.errors ?? defaultErrors\n\treturn async (input: {\n\t\tcontext: never\n\t\tnext: (options?: { context?: unknown }) => Promise<unknown>\n\t}): Promise<unknown> => {\n\t\tconst key = config.key\n\t\t\t? config.key(input.context)\n\t\t\t: (input.context as { tenant?: string }).tenant\n\t\tif (key === undefined) {\n\t\t\treturn errors.throw({\n\t\t\t\tcode: 'RATE_LIMIT_KEY_MISSING',\n\t\t\t\tmessage: `rateLimit(\"${config.name}\") has no key: ctx.tenant is undefined and no key fn was configured`,\n\t\t\t})\n\t\t}\n\t\tconst status = await config.limiter.limit(input.context, config.name, {\n\t\t\tkey,\n\t\t})\n\t\tif (!status.ok) {\n\t\t\tawait config.onLimit?.(status, input.context)\n\t\t\treturn errors.throw({\n\t\t\t\tcode: 'RATE_LIMITED',\n\t\t\t\tmessage: `Rate limit \"${config.name}\" exceeded${\n\t\t\t\t\tstatus.retryAfter === undefined\n\t\t\t\t\t\t? ''\n\t\t\t\t\t\t: `; retry after ${Math.ceil(status.retryAfter)}ms`\n\t\t\t\t}`,\n\t\t\t})\n\t\t}\n\t\treturn input.next()\n\t}\n}\n"],"mappings":"6CA0BA,SAAgB,EAAU,EAWvB,CACF,IAAM,EAAS,EAAO,QAAU,EAChC,OAAO,KAAO,IAGU,CACvB,IAAM,EAAM,EAAO,IAChB,EAAO,IAAI,EAAM,OAAO,EACvB,EAAM,QAAgC,OAC1C,GAAI,IAAQ,IAAA,GACX,OAAO,EAAO,MAAM,CACnB,KAAM,yBACN,QAAS,cAAc,EAAO,KAAK,oEACpC,CAAC,EAEF,IAAM,EAAS,MAAM,EAAO,QAAQ,MAAM,EAAM,QAAS,EAAO,KAAM,CACrE,KACD,CAAC,EAYD,OAXK,EAAO,GAWL,EAAM,KAAK,GAVjB,MAAM,EAAO,UAAU,EAAQ,EAAM,OAAO,EACrC,EAAO,MAAM,CACnB,KAAM,eACN,QAAS,eAAe,EAAO,KAAK,YACnC,EAAO,aAAe,IAAA,GACnB,GACA,iBAAiB,KAAK,KAAK,EAAO,UAAU,EAAE,KAEnD,CAAC,EAGH,CACD"}
@@ -0,0 +1,18 @@
1
+ import { ErrorFactory } from "./errors.mjs";
2
+ //#region src/state-machine.d.ts
3
+ /**
4
+ * Typed transition legality from a constants tuple (the conventions.md
5
+ * vocabulary pattern). The transition map is typed against the tuple, so an
6
+ * invalid literal cannot compile; `assert` throws via the injected
7
+ * ErrorFactory so it drops straight into a command guard.
8
+ */
9
+ declare function createStateMachine<const State extends string>(states: readonly State[], transitions: Partial<Record<NoInfer<State>, readonly NoInfer<State>[]>>, options?: {
10
+ errors?: ErrorFactory;
11
+ }): Readonly<{
12
+ states: readonly State[];
13
+ can: (from: State, to: State) => boolean;
14
+ assert: (from: State, to: State) => void;
15
+ }>;
16
+ //#endregion
17
+ export { createStateMachine };
18
+ //# sourceMappingURL=state-machine.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{defaultErrors as e}from"./errors.mjs";function t(t,n,r){let i=r?.errors??e,a=new Map(t.map(e=>[e,new Set(n[e]??[])]));function o(e,t){return a.get(e)?.has(t)??!1}function s(e,t){o(e,t)||i.throw({code:`INVALID_TRANSITION`,message:`Transition "${e}" → "${t}" is not legal`})}return Object.freeze({states:t,can:o,assert:s})}export{t as createStateMachine};
2
+ //# sourceMappingURL=state-machine.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-machine.mjs","names":[],"sources":["../src/state-machine.ts"],"sourcesContent":["import { defaultErrors, type ErrorFactory } from './errors'\n\n/**\n * Typed transition legality from a constants tuple (the conventions.md\n * vocabulary pattern). The transition map is typed against the tuple, so an\n * invalid literal cannot compile; `assert` throws via the injected\n * ErrorFactory so it drops straight into a command guard.\n */\nexport function createStateMachine<const State extends string>(\n\tstates: readonly State[],\n\t// NoInfer: the vocabulary comes from the tuple; a stray literal in the\n\t// transition map must fail to compile, not widen the state union.\n\ttransitions: Partial<Record<NoInfer<State>, readonly NoInfer<State>[]>>,\n\toptions?: { errors?: ErrorFactory },\n) {\n\tconst errors = options?.errors ?? defaultErrors\n\tconst legal = new Map<State, ReadonlySet<State>>(\n\t\tstates.map((state) => [state, new Set(transitions[state] ?? [])]),\n\t)\n\n\tfunction can(from: State, to: State): boolean {\n\t\treturn legal.get(from)?.has(to) ?? false\n\t}\n\n\tfunction assert(from: State, to: State): void {\n\t\tif (!can(from, to)) {\n\t\t\terrors.throw({\n\t\t\t\tcode: 'INVALID_TRANSITION',\n\t\t\t\tmessage: `Transition \"${from}\" → \"${to}\" is not legal`,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn Object.freeze({ states, can, assert })\n}\n"],"mappings":"6CAQA,SAAgB,EACf,EAGA,EACA,EACC,CACD,IAAM,EAAS,GAAS,QAAU,EAC5B,EAAQ,IAAI,IACjB,EAAO,IAAK,GAAU,CAAC,EAAO,IAAI,IAAI,EAAY,IAAU,CAAC,CAAC,CAAC,CAAC,CACjE,EAEA,SAAS,EAAI,EAAa,EAAoB,CAC7C,OAAO,EAAM,IAAI,CAAI,CAAC,EAAE,IAAI,CAAE,GAAK,EACpC,CAEA,SAAS,EAAO,EAAa,EAAiB,CACxC,EAAI,EAAM,CAAE,GAChB,EAAO,MAAM,CACZ,KAAM,qBACN,QAAS,eAAe,EAAK,OAAO,EAAG,eACxC,CAAC,CAEH,CAEA,OAAO,OAAO,OAAO,CAAE,SAAQ,MAAK,QAAO,CAAC,CAC7C"}
@@ -1,6 +1,6 @@
1
1
  import { ErrorFactory } from "./errors.mjs";
2
- import { RLSConfig, Rules, wrapDatabaseReader, wrapDatabaseWriter } from "convex-helpers/server/rowLevelSecurity";
3
2
  import { GenericDataModel, TableNamesInDataModel } from "convex/server";
3
+ import { RLSConfig, Rules, wrapDatabaseReader, wrapDatabaseWriter } from "convex-helpers/server/rowLevelSecurity";
4
4
  //#region src/tenancy.d.ts
5
5
  /** The server-owned tenancy field stamped on every tenant table row. */
6
6
  declare const TENANT_FIELD: 'tenant';
@@ -0,0 +1,164 @@
1
+ import { ErrorFactory } from "./errors.mjs";
2
+ import { z } from "zod";
3
+ //#region src/webhooks.d.ts
4
+ /**
5
+ * The host-owned dedup table (the kit gains no tables). Register it in the
6
+ * schema with the by_eventKey index:
7
+ *
8
+ * webhookEvents: webhookEventsTable().table.index('by_eventKey', ['eventKey'])
9
+ *
10
+ * Rows are host-owned data: prune them on a retention window comfortably
11
+ * longer than the provider's redelivery horizon (a cleanup cron over
12
+ * `receivedAt` — see docs/webhooks.md).
13
+ */
14
+ declare function webhookEventsTable(): {
15
+ tableName: "webhookEvents";
16
+ schema: z.ZodObject<{
17
+ createdAt: z.ZodOptional<z.ZodNumber>;
18
+ updatedAt: z.ZodOptional<z.ZodNumber>;
19
+ archivedAt: z.ZodOptional<z.ZodNumber>;
20
+ _id: import("convex-helpers/server/zod4").Zid<"webhookEvents">;
21
+ _creationTime: z.ZodNumber;
22
+ eventKey: z.ZodString;
23
+ source: z.ZodString;
24
+ receivedAt: z.ZodNumber;
25
+ }, z.core.$strict>;
26
+ storage: z.ZodObject<{
27
+ createdAt: z.ZodOptional<z.ZodNumber>;
28
+ updatedAt: z.ZodOptional<z.ZodNumber>;
29
+ archivedAt: z.ZodOptional<z.ZodNumber>;
30
+ eventKey: z.ZodString;
31
+ source: z.ZodString;
32
+ receivedAt: z.ZodNumber;
33
+ }, z.core.$strict>;
34
+ insertSchema: z.ZodObject<Omit<{
35
+ eventKey: z.ZodString;
36
+ source: z.ZodString;
37
+ receivedAt: z.ZodNumber;
38
+ }, "archivedAt" | "createdAt" | "updatedAt">, z.core.$strip>;
39
+ updateSchema: z.ZodObject<{
40
+ eventKey: z.ZodOptional<z.ZodString>;
41
+ source: z.ZodOptional<z.ZodString>;
42
+ receivedAt: z.ZodOptional<z.ZodNumber>;
43
+ }, z.core.$strip>;
44
+ commandInput: z.ZodObject<{}, z.core.$strict>;
45
+ publicDto: z.ZodObject<{}, z.core.$strict>;
46
+ toPublicDto: (row: {
47
+ createdAt?: number | undefined;
48
+ updatedAt?: number | undefined;
49
+ archivedAt?: number | undefined;
50
+ eventKey: string;
51
+ source: string;
52
+ receivedAt: number;
53
+ }) => Record<string, never>;
54
+ table: import("convex/server").TableDefinition<import("convex/values").VObject<{
55
+ createdAt?: number | undefined;
56
+ updatedAt?: number | undefined;
57
+ archivedAt?: number | undefined;
58
+ eventKey: string;
59
+ source: string;
60
+ receivedAt: number;
61
+ }, {
62
+ createdAt: import("convex/values").VFloat64<number | undefined, "optional">;
63
+ updatedAt: import("convex/values").VFloat64<number | undefined, "optional">;
64
+ archivedAt: import("convex/values").VFloat64<number | undefined, "optional">;
65
+ eventKey: import("convex/values").VString<string, "required">;
66
+ source: import("convex/values").VString<string, "required">;
67
+ receivedAt: import("convex/values").VFloat64<number, "required">;
68
+ }, "required", "archivedAt" | "createdAt" | "eventKey" | "receivedAt" | "source" | "updatedAt">, {}, {}, {}>;
69
+ insert: {
70
+ (): z.ZodObject<Omit<{
71
+ eventKey: z.ZodString;
72
+ source: z.ZodString;
73
+ receivedAt: z.ZodNumber;
74
+ }, "archivedAt" | "createdAt" | "updatedAt">, z.core.$strip>;
75
+ <OmitMask extends z.util.Mask<"eventKey" | "receivedAt" | "source">>(omit: OmitMask): z.ZodObject<Omit<Omit<{
76
+ eventKey: z.ZodString;
77
+ source: z.ZodString;
78
+ receivedAt: z.ZodNumber;
79
+ }, "archivedAt" | "createdAt" | "updatedAt">, Extract<"eventKey", keyof OmitMask> | Extract<"receivedAt", keyof OmitMask> | Extract<"source", keyof OmitMask>> extends (infer T) ? { [k in keyof T]: T[k]; } : never, z.core.$strip>;
80
+ };
81
+ update: {
82
+ (): z.ZodObject<{
83
+ eventKey: z.ZodOptional<z.ZodString>;
84
+ source: z.ZodOptional<z.ZodString>;
85
+ receivedAt: z.ZodOptional<z.ZodNumber>;
86
+ }, z.core.$strip>;
87
+ <OmitMask extends z.util.Mask<"eventKey" | "receivedAt" | "source">>(omit: OmitMask): z.ZodObject<Omit<{
88
+ eventKey: z.ZodOptional<z.ZodString>;
89
+ source: z.ZodOptional<z.ZodString>;
90
+ receivedAt: z.ZodOptional<z.ZodNumber>;
91
+ }, Extract<"eventKey", keyof OmitMask> | Extract<"receivedAt", keyof OmitMask> | Extract<"source", keyof OmitMask>> extends (infer T) ? { [k in keyof T]: T[k]; } : never, z.core.$strip>;
92
+ };
93
+ tools: {
94
+ insert: z.ZodObject<{}, z.core.$strict>;
95
+ update: z.ZodObject<{
96
+ data: z.ZodObject<{}, z.core.$strict>;
97
+ id: z.ZodType<import("convex/values").GenericId<"webhookEvents">, string, z.core.$ZodTypeInternals<import("convex/values").GenericId<"webhookEvents">, string>>;
98
+ }, z.core.$strict>;
99
+ id: z.ZodObject<{
100
+ id: z.ZodType<import("convex/values").GenericId<"webhookEvents">, string, z.core.$ZodTypeInternals<import("convex/values").GenericId<"webhookEvents">, string>>;
101
+ }, z.core.$strict>;
102
+ };
103
+ };
104
+ type DedupContext = {
105
+ db: {
106
+ query: (table: never) => {
107
+ withIndex: (index: never, range: (builder: {
108
+ eq: (field: never, value: never) => unknown;
109
+ }) => unknown) => {
110
+ first: () => Promise<unknown | null>;
111
+ };
112
+ };
113
+ insert: (table: never, value: never) => Promise<unknown>;
114
+ };
115
+ };
116
+ /**
117
+ * Transactional insert-if-absent dedup guard. Call this FIRST inside the
118
+ * internal mutation the boundary delegates to — dedup must live in the
119
+ * mutation (transactional), never in the action. Returns duplicate: true
120
+ * when the natural key was already recorded; the caller then no-ops.
121
+ */
122
+ declare function recordWebhookEvent(ctx: DedupContext, input: {
123
+ key: string;
124
+ source?: string;
125
+ table?: string;
126
+ }): Promise<{
127
+ duplicate: boolean;
128
+ }>;
129
+ type WebhookBoundaryConfig = {
130
+ /**
131
+ * Verifies the provider signature over the RAW body bytes — the helper
132
+ * hands it `request.text()` BEFORE any parsing. Implementations must use
133
+ * a constant-time comparison (e.g. crypto.subtle HMAC verify) and read
134
+ * the secret from a Convex environment variable. Return false or throw
135
+ * to reject.
136
+ */
137
+ verify: (raw: string, request: Request) => boolean | Promise<boolean>;
138
+ /**
139
+ * Derives the natural dedup key — the `${event}:${id}:${updatedAt}`
140
+ * pattern. Receives the raw body and the request; parse inside if needed.
141
+ */
142
+ eventKey: (raw: string, request: Request) => string | Promise<string>;
143
+ source?: string;
144
+ errors?: ErrorFactory;
145
+ };
146
+ /**
147
+ * The sanctioned webhook pattern as code: verify (raw body, fail closed) →
148
+ * derive natural key → delegate to a host internal mutation whose FIRST act
149
+ * is recordWebhookEvent (transactional dedup). Use inside the host's
150
+ * httpAction:
151
+ *
152
+ * const boundary = createWebhookBoundary({ verify, eventKey })
153
+ * http.route({ path: '/webhooks/vendor', method: 'POST',
154
+ * handler: httpAction((ctx, request) =>
155
+ * boundary.handle(ctx, request, internal.domain.vendor.functions.applyEvent)) })
156
+ */
157
+ declare function createWebhookBoundary(config: WebhookBoundaryConfig): {
158
+ handle: (ctx: {
159
+ runMutation: (target: never, args: never) => Promise<unknown>;
160
+ }, request: Request, target: unknown) => Promise<Response>;
161
+ };
162
+ //#endregion
163
+ export { WebhookBoundaryConfig, createWebhookBoundary, recordWebhookEvent, webhookEventsTable };
164
+ //# sourceMappingURL=webhooks.d.mts.map