cvx-kit 0.0.7 → 0.0.8
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.
- package/dist/components/foundation/client.d.mts +27 -3
- package/dist/components/foundation/client.mjs +1 -1
- package/dist/components/foundation/client.mjs.map +1 -1
- package/dist/components/foundation/modules/command/command.d.mts +2 -1
- package/dist/components/foundation/modules/command/command.mjs +1 -1
- package/dist/components/foundation/modules/command/command.mjs.map +1 -1
- package/dist/components/foundation/modules/query/query.d.mts +18 -2
- package/dist/components/foundation/modules/query/query.mjs +1 -1
- package/dist/components/foundation/modules/query/query.mjs.map +1 -1
- package/dist/index.d.mts +3 -2
- package/docs/commands.md +48 -0
- package/docs/llms.md +8 -2
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CommandExecution, CommandInput, CommandRegistry, CommandResult, Parseable } from "./modules/command/command.mjs";
|
|
2
2
|
import { CommandObservation, Observability, ObservabilityOptions } from "./modules/observability/observability.mjs";
|
|
3
|
-
import { Query, QueryExecution } from "./modules/query/query.mjs";
|
|
3
|
+
import { Query, QueryExecution, QueryMiddleware } from "./modules/query/query.mjs";
|
|
4
4
|
import { Result, ResultBoundary, TransactionMetricsContext, executeResultBoundary, projectResult } from "./result.mjs";
|
|
5
5
|
import { emitSemanticEvent } from "./telemetry.mjs";
|
|
6
6
|
declare namespace client_d_exports {
|
|
7
|
-
export { ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandDefaults, CommandExecution, CommandInput, CommandObservation, CommandRegistry, CommandResult, Foundation, FoundationOptions, ObservabilityOptions, Parseable, PermissionChecker, QueryExecution, Result, ResultBoundary, TransactionMetricsContext };
|
|
7
|
+
export { ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandDefaults, CommandExecution, CommandInput, CommandMiddleware, CommandObservation, CommandRegistry, CommandResult, Foundation, FoundationOptions, ObservabilityOptions, Parseable, PermissionChecker, QueryExecution, QueryMiddleware, Result, ResultBoundary, TransactionMetricsContext };
|
|
8
8
|
}
|
|
9
9
|
/** Shape handed to the injected audit writer; classification comes from the operation. */
|
|
10
10
|
type AuditEntryInput = {
|
|
@@ -45,15 +45,36 @@ type AuditedOperation = Readonly<{
|
|
|
45
45
|
* the audit vocabulary is enforced, not advisory.
|
|
46
46
|
*/
|
|
47
47
|
aggregates?: readonly string[];
|
|
48
|
+
/** Per-operation middleware, inside the registry-wide chain. */
|
|
49
|
+
middleware?: readonly CommandMiddleware[];
|
|
48
50
|
audit: (resolution: Readonly<{
|
|
49
51
|
command: never;
|
|
50
52
|
result: never;
|
|
51
53
|
}>, context: never) => MaybePromise<Omit<AuditEntryInput, 'classification'> | null>;
|
|
52
54
|
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Composable, next()-based middleware wrapping [guards → handler]. Runs
|
|
57
|
+
* INSIDE the pipeline's invariants: after the permission check, before the
|
|
58
|
+
* result-schema parse, aggregate allowlist, and audit — so middleware can
|
|
59
|
+
* time, trace, enrich context (`next({ context })`), short-circuit, or
|
|
60
|
+
* transform results, but can never skip authorization, return an invalid
|
|
61
|
+
* result, or desynchronize audit from effects.
|
|
62
|
+
*/
|
|
63
|
+
type CommandMiddleware<Context = never> = (input: {
|
|
64
|
+
operation: string;
|
|
65
|
+
command: unknown;
|
|
66
|
+
context: Context;
|
|
67
|
+
next: (options?: {
|
|
68
|
+
/** Merged into the context handed to inner middleware, guards, handler. */
|
|
69
|
+
context?: Record<string, unknown>;
|
|
70
|
+
}) => Promise<unknown>;
|
|
71
|
+
}) => Promise<unknown>;
|
|
53
72
|
/** Registry-wide defaults applied to every operation of one Command. */
|
|
54
73
|
type CommandDefaults = Readonly<{
|
|
55
74
|
/** Runs before every operation's own guard. Throw to deny. */
|
|
56
75
|
guard?: (context: never) => MaybePromise<void>;
|
|
76
|
+
/** Outermost middleware, in array order, around every operation. */
|
|
77
|
+
middleware?: readonly CommandMiddleware[];
|
|
57
78
|
}>;
|
|
58
79
|
/** Injected permission policy: throw to deny. Host semantics, kit ordering. */
|
|
59
80
|
type PermissionChecker = (context: never, input: Readonly<{
|
|
@@ -92,6 +113,8 @@ declare class BoundCommand<Context, const Operations extends AuditedRegistry> {
|
|
|
92
113
|
/** Declared aggregate types per operation — introspectable for tests. */
|
|
93
114
|
readonly aggregates: Readonly<{ [Key in OperationKey<Operations>]: Operations[Key]['aggregates']; }>;
|
|
94
115
|
static operation<const Definition extends AuditedOperation>(definition: Definition): Definition;
|
|
116
|
+
/** Identity helper that types a middleware against its context. */
|
|
117
|
+
static middleware<Context = never>(middleware: CommandMiddleware<Context>): CommandMiddleware<Context>;
|
|
95
118
|
constructor(operations: Operations, deps: {
|
|
96
119
|
observability: Observability;
|
|
97
120
|
writeAudit: AuditWriter;
|
|
@@ -111,6 +134,7 @@ type ApplicationCommand<Context, Operations extends AuditedRegistry> = BoundComm
|
|
|
111
134
|
type CommandConstructor = {
|
|
112
135
|
new <Context, const Operations extends AuditedRegistry>(operations: Operations, defaults?: CommandDefaults): BoundCommand<Context, Operations>;
|
|
113
136
|
operation: (typeof BoundCommand)['operation'];
|
|
137
|
+
middleware: (typeof BoundCommand)['middleware'];
|
|
114
138
|
};
|
|
115
139
|
/**
|
|
116
140
|
* Host facade for a component that owns no application execution capability.
|
|
@@ -129,5 +153,5 @@ declare class Foundation<Component extends FoundationComponentApi = FoundationCo
|
|
|
129
153
|
constructor(component: Component, options: FoundationOptions);
|
|
130
154
|
}
|
|
131
155
|
//#endregion
|
|
132
|
-
export { ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandDefaults, type CommandExecution, type CommandInput, type CommandObservation, type CommandRegistry, type CommandResult, Foundation, FoundationOptions, type ObservabilityOptions, type Parseable, PermissionChecker, type QueryExecution, type Result, type ResultBoundary, type TransactionMetricsContext, client_d_exports };
|
|
156
|
+
export { ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandDefaults, type CommandExecution, type CommandInput, CommandMiddleware, type CommandObservation, type CommandRegistry, type CommandResult, Foundation, FoundationOptions, type ObservabilityOptions, type Parseable, PermissionChecker, type QueryExecution, type QueryMiddleware, type Result, type ResultBoundary, type TransactionMetricsContext, client_d_exports };
|
|
133
157
|
//# sourceMappingURL=client.d.mts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Command as e}from"./modules/command/command.mjs";import{Observability as t}from"./modules/observability/observability.mjs";import{Query as n}from"./modules/query/query.mjs";import{executeResultBoundary as r,projectResult as i}from"./result.mjs";import{emitSemanticEvent as a}from"./telemetry.mjs";var o=class{#e;#t;#n;#r;#i;aggregates;static operation(e){return e}constructor(t,n){this.#t=n.observability,this.#n=n.writeAudit,this.#r=n.checkPermission,this.#i=n.defaults??{},this.aggregates=Object.freeze(Object.fromEntries(Object.entries(t).map(([e,t])=>[e,t.aggregates]))),this.#e=new e({operations:t,execute:e=>this.#a(e)})}exec(e){return this.#e.exec(e)}async#a(e){return this.#t.observe({operation:e.operation,classification:e.definition.classification},async()=>{let t=e.definition.permission;if(t!==void 0){if(!this.#r)throw new
|
|
1
|
+
import{Command as e}from"./modules/command/command.mjs";import{Observability as t}from"./modules/observability/observability.mjs";import{Query as n}from"./modules/query/query.mjs";import{executeResultBoundary as r,projectResult as i}from"./result.mjs";import{emitSemanticEvent as a}from"./telemetry.mjs";var o=class{#e;#t;#n;#r;#i;aggregates;static operation(e){return e}static middleware(e){return e}constructor(t,n){this.#t=n.observability,this.#n=n.writeAudit,this.#r=n.checkPermission,this.#i=n.defaults??{},this.aggregates=Object.freeze(Object.fromEntries(Object.entries(t).map(([e,t])=>[e,t.aggregates]))),this.#e=new e({operations:t,execute:e=>this.#a(e)})}exec(e){return this.#e.exec(e)}async#a(e){return this.#t.observe({operation:e.operation,classification:e.definition.classification},async()=>{let t=e.definition.permission;if(t!==void 0){if(!this.#r)throw new l(e.operation);await this.#r(e.context,{permission:t,operation:e.operation})}let n=[...this.#i.middleware??[],...e.definition.middleware??[]],r=async t=>(await this.#i.guard?.(t),await e.definition.guard?.(t,e.command),e.run(t)),i=-1,a=async(t,o)=>{if(t<=i)throw new s(e.operation);i=t;let c=n[t];return c?c({operation:e.operation,command:e.command,context:o,next:e=>a(t+1,e?.context?{...o,...e.context}:o)}):r(o)},o=e.parseResult(await a(0,e.context)),u=await e.definition.audit({command:e.command,result:o},e.context);if(u){let t=e.definition.aggregates;if(t&&!t.includes(u.aggregate.type))throw new c(e.operation,u.aggregate.type);await this.#n(e.context,{...u,classification:e.definition.classification})}return o})}},s=class extends Error{code=`COMMAND_MIDDLEWARE_NEXT_REUSED`;name=`CommandMiddlewareError`;constructor(e){super(`A middleware for operation "${e}" called next() more than once`)}},c=class extends Error{code=`COMMAND_AGGREGATE_NOT_DECLARED`;name=`CommandAggregateError`;constructor(e,t){super(`Operation "${e}" audited aggregate type "${t}" outside its declared aggregates`)}},l=class extends Error{code=`COMMAND_PERMISSION_NOT_CONFIGURED`;name=`CommandPermissionError`;constructor(e){super(`Operation "${e}" declares a permission but the Foundation has no checkPermission`)}},u=class{status;Command;Query=n;observability;executeResultBoundary=r;projectResult=i;emitSemanticEvent=a;constructor(e,n){this.status=e.functions.status;let r=new t(n.observability);this.observability=r;let i=n.observability.writeAudit??(()=>void 0),a=n.checkPermission;this.Command=class extends o{constructor(e,t){super(e,{observability:r,writeAudit:i,checkPermission:a,defaults:t})}}}};export{u as Foundation};
|
|
2
2
|
//# sourceMappingURL=client.mjs.map
|
|
@@ -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 CommandMiddleware[]\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<Context = never> = (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?: Record<string, unknown>\n\t}) => 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 CommandMiddleware[]\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/** Identity helper that types a middleware against its context. */\n\tstatic middleware<Context = never>(\n\t\tmiddleware: CommandMiddleware<Context>,\n\t): CommandMiddleware<Context> {\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 }\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\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\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\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":"gTA6HA,IAAM,EAAN,KAAsE,CACrE,GACA,GACA,GACA,GACA,GAGA,WAIA,OAAO,UACN,EACa,CACb,OAAO,CACR,CAGA,OAAO,WACN,EAC6B,CAC7B,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,IAAM,EAAa,CAClB,GAAI,KAAKC,GAAU,YAAc,CAAC,EAClC,GAAI,EAAU,WAAW,YAAc,CAAC,CACzC,EACM,EAAW,KAAO,KACvB,MAAM,KAAKA,GAAU,QAAQ,CAAgB,EAC7C,MAAM,EAAU,WAAW,QAC1B,EACA,EAAU,OACX,EACO,EAAU,IAAI,CAAgB,GAElC,EAAU,GACR,EAAW,MAChB,EACA,IACsB,CACtB,GAAI,GAAS,EACZ,MAAM,IAAI,EAAuB,EAAU,SAAS,EAErD,EAAU,EACV,IAAM,EAAQ,EAAW,GAEzB,OADK,EACE,EAAM,CACZ,UAAW,EAAU,UACrB,QAAS,EAAU,QACV,UACT,KAAO,GACN,EACC,EAAQ,EACR,GAAS,QACN,CAAE,GAAI,EAAoB,GAAG,EAAQ,OAAQ,EAC7C,CACJ,CACF,CAAC,EAZkB,EAAS,CAAO,CAapC,EAGM,EAAS,EAAU,YACxB,MAAM,EAAS,EAAG,EAAU,OAAO,CACpC,EACM,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,cAAqC,KAAM,CAC1C,KAAgB,iCAChB,KAAgB,yBAEhB,YAAY,EAAmB,CAC9B,MACC,+BAA+B,EAAU,+BAC1C,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,EAqBa,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"}
|
|
@@ -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
|
-
|
|
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
|
|
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 ()
|
|
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 { Query, QueryExecution, QueryMiddleware };
|
|
3
3
|
}
|
|
4
4
|
type QueryExecution<Context, Metadata, Result> = Readonly<{
|
|
5
5
|
context: Context;
|
|
@@ -7,18 +7,34 @@ 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> = (input: {
|
|
16
|
+
metadata: object;
|
|
17
|
+
context: Context;
|
|
18
|
+
next: (options?: {
|
|
19
|
+
context?: Record<string, unknown>;
|
|
20
|
+
}) => Promise<unknown>;
|
|
21
|
+
}) => Promise<unknown>;
|
|
10
22
|
/** Generic query kernel. Authorization remains an injected host policy. */
|
|
11
23
|
declare class Query<Context, Defaults extends object> {
|
|
12
24
|
#private;
|
|
25
|
+
/** Identity helper that types a middleware against its context. */
|
|
26
|
+
static middleware<Context = never>(middleware: QueryMiddleware<Context>): QueryMiddleware<Context>;
|
|
13
27
|
constructor(configuration: {
|
|
14
28
|
readonly defaults: Defaults;
|
|
29
|
+
readonly middleware?: readonly QueryMiddleware<Context>[];
|
|
15
30
|
readonly execute: <Metadata extends object, Result>(execution: QueryExecution<Context, Defaults & Metadata, Result>) => Promise<Result>;
|
|
16
31
|
});
|
|
17
32
|
exec<Arguments extends readonly unknown[], Result, Metadata extends object = object>(executor: {
|
|
18
33
|
readonly metadata?: Metadata;
|
|
34
|
+
readonly middleware?: readonly QueryMiddleware<Context>[];
|
|
19
35
|
readonly handler: (context: Context, ...args: Arguments) => MaybePromise<Result>;
|
|
20
36
|
}): (context: Context, ...args: Arguments) => Promise<Result>;
|
|
21
37
|
}
|
|
22
38
|
//#endregion
|
|
23
|
-
export { Query, QueryExecution, query_d_exports };
|
|
39
|
+
export { Query, QueryExecution, QueryMiddleware, query_d_exports };
|
|
24
40
|
//# 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(
|
|
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)
|
|
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<Context = never> = (input: {\n\tmetadata: object\n\tcontext: Context\n\tnext: (options?: {\n\t\tcontext?: Record<string, unknown>\n\t}) => 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 QueryMiddleware<Context>[]\n\treadonly #execute: <Metadata extends object, Result>(\n\t\texecution: QueryExecution<Context, Defaults & Metadata, Result>,\n\t) => Promise<Result>\n\n\t/** Identity helper that types a middleware against its context. */\n\tstatic middleware<Context = never>(\n\t\tmiddleware: QueryMiddleware<Context>,\n\t): QueryMiddleware<Context> {\n\t\treturn middleware\n\t}\n\n\tconstructor(configuration: {\n\t\treadonly defaults: Defaults\n\t\treadonly middleware?: readonly QueryMiddleware<Context>[]\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 QueryMiddleware<Context>[]\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,\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,\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":"AAqBA,IAAM,EAAN,cAAmC,KAAM,CACxC,KAAgB,+BAChB,KAAgB,uBAEhB,aAAc,CACb,MAAM,iDAAiD,CACxD,CACD,EAGa,EAAb,KAAqD,CACpD,GACA,GACA,GAKA,OAAO,WACN,EAC2B,CAC3B,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,GAAG,EAAQ,OACZ,EACC,CACJ,CACF,CAAC,EAdkB,EAAS,QAAQ,EAAS,GAAG,CAAI,CAerD,EACA,OAAQ,MAAM,EAAS,EAAG,CAAO,CAClC,CAC8C,CAAC,CAChD,CACD,CACD"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ErrorFactory, KitError, defaultErrors } from "./errors.mjs";
|
|
2
2
|
import { RLSConfig, Rules, TENANT_FIELD, assertTenantOwned, composeRules, createTenantRules, requireTenantReference } from "./tenancy.mjs";
|
|
3
3
|
import { Actor, AuthBundle, AuthFunctionsConfig, DefaultRole, Include, IncludedQuery, SecurityConfig, createAuthFunctions, createInclude, defaultRoleMap } from "./auth.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { QueryMiddleware } from "./components/foundation/modules/query/query.mjs";
|
|
5
|
+
import { ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandDefaults, CommandMiddleware, Foundation, FoundationOptions, PermissionChecker } from "./components/foundation/client.mjs";
|
|
5
6
|
import { TIMESTAMP_FIELDS, TableBoundaryOptions, createModule, jsonSafeZid, tenantTable, zodTable, zodVariantTable } from "./zod-table.mjs";
|
|
6
7
|
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 };
|
|
8
|
+
export { type Actor, type ApplicationCommand, type AuditEntryInput, type AuditWriter, type AuditedOperation, type AuditedRegistry, type AuthBundle, type AuthFunctionsConfig, type Change, type CommandDefaults, type CommandMiddleware, type DefaultRole, type ErrorFactory, Foundation, type FoundationOptions, type Include, type IncludedQuery, KitError, type PermissionChecker, type QueryMiddleware, 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 };
|
package/docs/commands.md
CHANGED
|
@@ -148,6 +148,54 @@ Guards deny by throwing; a denial before the handler never needs rollback
|
|
|
148
148
|
because nothing has executed. Keep guards read-only — a guard that writes is
|
|
149
149
|
a handler in disguise.
|
|
150
150
|
|
|
151
|
+
### Middleware — composable, next()-based
|
|
152
|
+
|
|
153
|
+
For wrap-around concerns (timing, tracing, context enrichment, replay
|
|
154
|
+
wrappers) that two disconnected callbacks can't express, both kernels take
|
|
155
|
+
Express/TanStack-style middleware. The chain runs **inside** the pipeline's
|
|
156
|
+
invariants: after the permission check, around [guards → handler], before the
|
|
157
|
+
result-schema re-parse, aggregate allowlist, and audit — so middleware can
|
|
158
|
+
never skip authorization, return an invalid result, or desynchronize audit
|
|
159
|
+
from effects.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const timing = Command.middleware(async ({ operation, next }) => {
|
|
163
|
+
const started = Date.now()
|
|
164
|
+
const result = await next() // inner middleware → guards → handler
|
|
165
|
+
observability.observe // (or your own sink)
|
|
166
|
+
console.info(`${operation} took ${Date.now() - started}ms`)
|
|
167
|
+
return result
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const withVendor = Command.middleware(async ({ context, next }) =>
|
|
171
|
+
next({ context: { vendor: await loadVendor(context) } })) // enriches downstream ctx
|
|
172
|
+
|
|
173
|
+
const commands = new Command<Ctx, typeof operations>(operations, {
|
|
174
|
+
guard: (ctx) => assertNotReadonlyWindow(ctx),
|
|
175
|
+
middleware: [timing], // registry-wide, outermost
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
'<entities>.publish': Command.operation({
|
|
179
|
+
...,
|
|
180
|
+
middleware: [withVendor], // per-operation, inside registry chain
|
|
181
|
+
guard: (ctx, command) => { /* ctx.vendor available here */ },
|
|
182
|
+
})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Order: registry middleware (array order) → operation middleware → default
|
|
186
|
+
guard → operation guard → handler. `next()` returns the downstream result;
|
|
187
|
+
`next({ context })` merges enrichment into the context handed to inner
|
|
188
|
+
middleware, guards, and the handler. Skipping `next()` short-circuits
|
|
189
|
+
(guards and handler never run — the returned value still must satisfy the
|
|
190
|
+
result schema); calling it twice throws `COMMAND_MIDDLEWARE_NEXT_REUSED`.
|
|
191
|
+
Whatever leaves the chain is re-parsed through the operation's strict result
|
|
192
|
+
schema — a middleware cannot fabricate an invalid result.
|
|
193
|
+
|
|
194
|
+
The Query kernel takes the same shape: `new Query({ defaults, middleware,
|
|
195
|
+
execute })` plus per-executor `middleware: [...]`, with `Query.middleware`
|
|
196
|
+
as the typing helper. Kernel middleware runs before executor middleware,
|
|
197
|
+
inside the host's injected `execute` policy.
|
|
198
|
+
|
|
151
199
|
If any step throws, the Convex transaction rolls back — handler writes and
|
|
152
200
|
audit entry together. Audit and effects can never disagree.
|
|
153
201
|
|
package/docs/llms.md
CHANGED
|
@@ -39,8 +39,14 @@ function constructors, a trigger registry, an audited command protocol
|
|
|
39
39
|
transaction. Optional per-operation `permission` (checked via the
|
|
40
40
|
Foundation's injected `checkPermission`) and `guard(ctx, command)`
|
|
41
41
|
preconditions run BEFORE the handler, plus a registry-wide default guard
|
|
42
|
-
(`new Command(operations, { guard })`).
|
|
43
|
-
|
|
42
|
+
(`new Command(operations, { guard })`). Composable middleware
|
|
43
|
+
(`Command.middleware(async ({ context, next }) => ...)`) wraps
|
|
44
|
+
[guards → handler] via registry `middleware: [...]` and per-operation
|
|
45
|
+
`middleware: [...]` — `next({ context })` enriches downstream ctx, and
|
|
46
|
+
the chain's output is re-parsed by the result schema. Query takes the
|
|
47
|
+
same shape (`Query.middleware`, kernel + per-executor arrays). Order:
|
|
48
|
+
permission → registry middleware → operation middleware → default guard
|
|
49
|
+
→ operation guard → handler → result parse → aggregates → audit.
|
|
44
50
|
6. **Never write timestamps by hand.** `createdAt`/`updatedAt` are maintained
|
|
45
51
|
by the `timestamps` trigger; `archivedAt` is the app-controlled soft-delete
|
|
46
52
|
marker.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cvx-kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Reusable Convex application kit: foundation kernels, approvals component, zod table boundaries, WorkOS-backed auth function constructors, and audited command protocol.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|