cvx-kit 0.1.2 → 0.1.4
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/CHANGELOG.md +38 -0
- package/README.md +69 -24
- package/dist/agent-tools.d.mts +28 -12
- package/dist/agent-tools.mjs +1 -1
- package/dist/agent-tools.mjs.map +1 -1
- package/dist/auth.d.mts +4 -2
- package/dist/auth.mjs +1 -1
- package/dist/auth.mjs.map +1 -1
- package/dist/components/approvals/_generated/api.d.mts +1 -1
- package/dist/components/approvals/audit.mjs +1 -1
- package/dist/components/approvals/audit.mjs.map +1 -1
- package/dist/components/approvals/client.d.mts +1 -1
- package/dist/components/approvals/client.mjs +1 -1
- package/dist/components/approvals/client.mjs.map +1 -1
- package/dist/components/approvals/constants.mjs.map +1 -1
- package/dist/components/approvals/decisions.d.mts +6 -7
- package/dist/components/approvals/decisions.mjs +1 -1
- package/dist/components/approvals/decisions.mjs.map +1 -1
- package/dist/components/approvals/functions.mjs +1 -1
- package/dist/components/approvals/functions.mjs.map +1 -1
- package/dist/components/approvals/health.mjs.map +1 -1
- package/dist/components/approvals/requests.mjs.map +1 -1
- package/dist/components/approvals/validators.mjs.map +1 -1
- package/dist/components/approvals/workflow.d.mts +1 -1
- package/dist/components/approvals/workflow.mjs +1 -1
- package/dist/components/approvals/workflow.mjs.map +1 -1
- package/dist/components/approvals/workflow_steps.d.mts +3 -3
- package/dist/components/approvals/workflow_steps.mjs +1 -1
- package/dist/components/approvals/workflow_steps.mjs.map +1 -1
- package/dist/components/foundation/client.d.mts +27 -28
- 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 +17 -8
- 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/observability/observability.d.mts +1 -1
- package/dist/components/foundation/modules/observability/observability.mjs +1 -1
- package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
- package/dist/components/foundation/modules/query/query.d.mts +8 -8
- package/dist/components/foundation/modules/query/query.mjs +1 -1
- package/dist/components/foundation/modules/query/query.mjs.map +1 -1
- package/dist/components/foundation/operation.d.mts +74 -0
- package/dist/components/foundation/operation.mjs +2 -0
- package/dist/components/foundation/operation.mjs.map +1 -0
- package/dist/components/foundation/result.d.mts +1 -1
- package/dist/components/foundation/result.mjs.map +1 -1
- package/dist/components/foundation/telemetry.mjs +1 -1
- package/dist/components/foundation/telemetry.mjs.map +1 -1
- package/dist/crud.d.mts +177 -16
- package/dist/crud.mjs +1 -1
- package/dist/crud.mjs.map +1 -1
- package/dist/middleware.d.mts +7 -7
- package/dist/middleware.mjs +1 -1
- package/dist/middleware.mjs.map +1 -1
- package/dist/oxlint.d.mts +9 -0
- package/dist/oxlint.mjs +2 -0
- package/dist/oxlint.mjs.map +1 -0
- package/dist/tenancy.mjs +1 -1
- package/dist/tenancy.mjs.map +1 -1
- package/dist/triggers.mjs +1 -1
- package/dist/triggers.mjs.map +1 -1
- package/dist/webhooks.d.mts +17 -106
- package/dist/webhooks.mjs.map +1 -1
- package/dist/zod-table.d.mts +47 -91
- package/dist/zod-table.mjs +1 -1
- package/dist/zod-table.mjs.map +1 -1
- package/docs/approvals.md +57 -56
- package/docs/architecture.md +1 -1
- package/docs/auth.md +57 -33
- package/docs/commands.md +159 -44
- package/docs/conventions.md +40 -40
- package/docs/crud.md +5 -5
- package/docs/llms.md +117 -71
- package/docs/maintainability.md +4 -4
- package/docs/migration.md +33 -33
- package/docs/tenancy.md +5 -8
- package/docs/triggers.md +4 -2
- package/docs/upgrading.md +74 -7
- package/docs/zod-table.md +70 -45
- package/package.json +41 -28
- package/scripts/convex-config-bridge.mjs +7 -18
- package/scripts/extract-changelog.mjs +1 -1
- package/scripts/smoke-packed-components.mjs +35 -75
- package/scripts/test-packed-ids.mjs +149 -0
- package/scripts/test-packed-oxlint.mjs +121 -0
- package/src/test.ts +9 -17
- package/docs/plans/2026-08-14-001-feat-v0.1.0-feature-set-plan.md +0 -461
- package/docs/plans/2026-08-21-0930-feat-resolve-organization-hook-plan.md +0 -178
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { CommandExecution, CommandInput, CommandRegistry, CommandResult, Parseable } from "./modules/command/command.mjs";
|
|
1
|
+
import { CommandArgument, CommandExecution, CommandHandlerResult, CommandInput, CommandRegistry, CommandResult, Parseable, SchemaInput } from "./modules/command/command.mjs";
|
|
2
2
|
import { CommandObservation, Observability, ObservabilityOptions } from "./modules/observability/observability.mjs";
|
|
3
3
|
import { AnyQueryMiddleware, 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
|
+
import { CommandPreparation, OperationExtension, OperationHostContext, TypedOperation, operationFactory } from "./operation.mjs";
|
|
7
|
+
import { Value } from "convex/values";
|
|
6
8
|
declare namespace client_d_exports {
|
|
7
|
-
export { AnyCommandMiddleware, AnyQueryMiddleware, ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandConstructor, CommandDefaults, CommandExecution, CommandInput, CommandMiddleware, CommandObservation, CommandRegistry, CommandResult, Foundation, FoundationOptions, ObservabilityOptions, Parseable, PermissionChecker, QueryExecution, QueryMiddleware, Result, ResultBoundary, TransactionMetricsContext };
|
|
9
|
+
export { AnyCommandMiddleware, AnyQueryMiddleware, ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandArgument, CommandConstructor, CommandDefaults, CommandExecution, CommandHandlerResult, CommandInput, CommandMiddleware, CommandObservation, CommandPreparation, CommandRegistry, CommandResult, Foundation, FoundationOptions, ObservabilityOptions, Parseable, PermissionChecker, QueryExecution, QueryMiddleware, Result, ResultBoundary, TransactionMetricsContext, TypedOperation };
|
|
8
10
|
}
|
|
9
11
|
/** Shape handed to the injected audit writer; classification comes from the operation. */
|
|
10
12
|
type AuditEntryInput = {
|
|
@@ -14,19 +16,15 @@ type AuditEntryInput = {
|
|
|
14
16
|
type: string;
|
|
15
17
|
id: string;
|
|
16
18
|
};
|
|
17
|
-
metadata?: Record<string,
|
|
19
|
+
metadata?: Record<string, Value>;
|
|
18
20
|
classification: string;
|
|
19
21
|
};
|
|
20
|
-
type AuditWriter = (context: never, entry: AuditEntryInput) =>
|
|
22
|
+
type AuditWriter<Result = unknown> = (context: never, entry: AuditEntryInput) => MaybePromise<Result>;
|
|
21
23
|
type MaybePromise<Value> = Value | Promise<Value>;
|
|
22
24
|
type OperationKey<Operations extends CommandRegistry> = Extract<keyof Operations, string>;
|
|
23
25
|
type AuditedOperation = Readonly<{
|
|
24
|
-
command:
|
|
25
|
-
|
|
26
|
-
}>;
|
|
27
|
-
result: Readonly<{
|
|
28
|
-
parse: (value: unknown) => unknown;
|
|
29
|
-
}>;
|
|
26
|
+
command: Parseable<unknown>;
|
|
27
|
+
result: Parseable<unknown>;
|
|
30
28
|
classification: string;
|
|
31
29
|
/**
|
|
32
30
|
* Permission slug required to execute this operation. Checked through the
|
|
@@ -34,6 +32,8 @@ type AuditedOperation = Readonly<{
|
|
|
34
32
|
* declaring a permission without injecting a checker fails closed.
|
|
35
33
|
*/
|
|
36
34
|
permission?: string;
|
|
35
|
+
prepare?: (context: never, command: never) => MaybePromise<CommandPreparation<never>>;
|
|
36
|
+
replayResult?: Parseable<unknown>;
|
|
37
37
|
/**
|
|
38
38
|
* Per-operation precondition, after the permission check and the registry
|
|
39
39
|
* default guard, before the handler. Throw to deny — nothing has run yet.
|
|
@@ -60,24 +60,25 @@ type AuditedOperation = Readonly<{
|
|
|
60
60
|
* transform results, but can never skip authorization, return an invalid
|
|
61
61
|
* result, or desynchronize audit from effects.
|
|
62
62
|
*/
|
|
63
|
-
type CommandMiddleware<Context = never, Extension extends
|
|
63
|
+
type CommandMiddleware<Context = never, Extension extends object = object, Input = unknown, Result = unknown> = (input: {
|
|
64
64
|
operation: string;
|
|
65
|
-
command:
|
|
65
|
+
command: Input;
|
|
66
66
|
context: Context;
|
|
67
67
|
next: (options?: {
|
|
68
68
|
/** Merged into the context handed to inner middleware, guards, handler. */
|
|
69
69
|
context?: Extension;
|
|
70
|
-
}) => Promise<
|
|
71
|
-
}) => Promise<
|
|
70
|
+
}) => Promise<Result>;
|
|
71
|
+
}) => Promise<Result>;
|
|
72
72
|
/** Any-context middleware — what registries and operations accept. */
|
|
73
|
-
type AnyCommandMiddleware = (input: {
|
|
73
|
+
type AnyCommandMiddleware<Input = never, Result = unknown, NextResult = never> = (input: {
|
|
74
74
|
operation: string;
|
|
75
|
-
|
|
75
|
+
definition?: AuditedOperation;
|
|
76
|
+
command: Input;
|
|
76
77
|
context: never;
|
|
77
78
|
next: (options?: {
|
|
78
|
-
context?:
|
|
79
|
-
}) => Promise<
|
|
80
|
-
}) => Promise<
|
|
79
|
+
context?: object;
|
|
80
|
+
}) => Promise<NextResult>;
|
|
81
|
+
}) => Promise<Result>;
|
|
81
82
|
/** Registry-wide defaults applied to every operation of one Command. */
|
|
82
83
|
type CommandDefaults = Readonly<{
|
|
83
84
|
/** Runs before every operation's own guard. Throw to deny. */
|
|
@@ -119,6 +120,7 @@ type FoundationComponentApi = Readonly<{
|
|
|
119
120
|
*/
|
|
120
121
|
declare class BoundCommand<Context, const Operations extends AuditedRegistry> {
|
|
121
122
|
#private;
|
|
123
|
+
static withContext: typeof operationFactory;
|
|
122
124
|
/** Declared aggregate types per operation — introspectable for tests. */
|
|
123
125
|
readonly aggregates: Readonly<{ [Key in OperationKey<Operations>]: Operations[Key]['aggregates']; }>;
|
|
124
126
|
static operation<const Definition extends AuditedOperation>(definition: Definition): Definition;
|
|
@@ -126,7 +128,7 @@ declare class BoundCommand<Context, const Operations extends AuditedRegistry> {
|
|
|
126
128
|
* Identity helper that types a middleware against its context and the
|
|
127
129
|
* context extension it passes downstream via next({ context }).
|
|
128
130
|
*/
|
|
129
|
-
static middleware<Context = never, const Extension extends
|
|
131
|
+
static middleware<Context = never, const Extension extends object = object, Input = unknown, Result = unknown>(middleware: CommandMiddleware<Context, Extension, Input, Result>): CommandMiddleware<Context, Extension, Input, Result>;
|
|
130
132
|
constructor(operations: Operations, deps: {
|
|
131
133
|
observability: Observability;
|
|
132
134
|
writeAudit: AuditWriter;
|
|
@@ -135,12 +137,8 @@ declare class BoundCommand<Context, const Operations extends AuditedRegistry> {
|
|
|
135
137
|
});
|
|
136
138
|
exec<const Key extends OperationKey<Operations>>(executor: {
|
|
137
139
|
operation: Key;
|
|
138
|
-
handler: (context: Context
|
|
139
|
-
}): (context: Context, command: Operations[Key]["command"]
|
|
140
|
-
parse: (value: unknown) => infer Output;
|
|
141
|
-
}> ? Output : never : never : never) => Promise<Operations[Key]["result"] extends (infer T_1) ? T_1 extends Operations[Key]["result"] ? T_1 extends Readonly<{
|
|
142
|
-
parse: (value: unknown) => infer Output;
|
|
143
|
-
}> ? Output : never : never : never>;
|
|
140
|
+
handler: (context: Context & OperationExtension<Operations[Key]>, command: CommandInput<Operations, Key>) => MaybePromise<CommandHandlerResult<Operations, Key>>;
|
|
141
|
+
}): (context: Context, command: SchemaInput<Operations[Key]["command"]>) => Promise<ReturnType<Operations[Key]["result"]["parse"]>>;
|
|
144
142
|
}
|
|
145
143
|
type ApplicationCommand<Context, Operations extends AuditedRegistry> = BoundCommand<Context, Operations>;
|
|
146
144
|
/**
|
|
@@ -149,8 +147,9 @@ type ApplicationCommand<Context, Operations extends AuditedRegistry> = BoundComm
|
|
|
149
147
|
* destructured facade class as input without importing any runtime kernel.
|
|
150
148
|
*/
|
|
151
149
|
type CommandConstructor = {
|
|
152
|
-
new <Context, const Operations extends AuditedRegistry>(operations: Operations, defaults?: CommandDefaults): BoundCommand<Context, Operations>;
|
|
150
|
+
new <Context, const Operations extends AuditedRegistry>(operations: Operations & { readonly [Key in keyof Operations]: OperationHostContext<Context>; }, defaults?: CommandDefaults): BoundCommand<Context, Operations>;
|
|
153
151
|
operation: (typeof BoundCommand)['operation'];
|
|
152
|
+
withContext: (typeof BoundCommand)['withContext'];
|
|
154
153
|
middleware: (typeof BoundCommand)['middleware'];
|
|
155
154
|
};
|
|
156
155
|
/**
|
|
@@ -170,5 +169,5 @@ declare class Foundation<Component extends FoundationComponentApi = FoundationCo
|
|
|
170
169
|
constructor(component: Component, options: FoundationOptions);
|
|
171
170
|
}
|
|
172
171
|
//#endregion
|
|
173
|
-
export { AnyCommandMiddleware, type AnyQueryMiddleware, ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandConstructor, 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 };
|
|
172
|
+
export { AnyCommandMiddleware, type AnyQueryMiddleware, ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, type CommandArgument, CommandConstructor, CommandDefaults, type CommandExecution, type CommandHandlerResult, type CommandInput, CommandMiddleware, type CommandObservation, type CommandPreparation, type CommandRegistry, type CommandResult, Foundation, FoundationOptions, type ObservabilityOptions, type Parseable, PermissionChecker, type QueryExecution, type QueryMiddleware, type Result, type ResultBoundary, type TransactionMetricsContext, type TypedOperation, client_d_exports };
|
|
174
173
|
//# 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
|
|
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";import{operationFactory as o}from"./operation.mjs";var s=class{static withContext=o;#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({operation:e.operation,handler:(t,n)=>e.handler(t,n)})}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 u(e.operation);await this.#r(e.context,{permission:t,operation:e.operation})}let n=await e.definition.prepare?.(e.context,e.command);if(n?.kind===`replay`)return e.definition.replayResult?e.definition.replayResult.parse(n.result):e.parseResult(n.result);let r=[...this.#i.middleware??[],...e.definition.middleware??[]],i=async t=>(await this.#i.guard?.(t),await e.definition.guard?.(t,e.command),e.runUnparsed(t)),a=-1,o=async(t,n)=>{if(t<=a)throw new c(e.operation);a=t;let s=r[t];return s?s({operation:e.operation,definition:e.definition,command:e.command,context:n,next:e=>o(t+1,e?.context?{...n,...e.context}:n)}):i(n)},s=e.parseResult(await o(0,e.context)),d=await e.definition.audit({command:e.command,result:s},e.context);if(d){let t=e.definition.aggregates;if(t&&!t.includes(d.aggregate.type))throw new l(e.operation,d.aggregate.type);await this.#n(e.context,{...d,classification:e.definition.classification})}return await n?.complete?.(s),s})}},c=class extends Error{code=`COMMAND_MIDDLEWARE_NEXT_REUSED`;name=`CommandMiddlewareError`;constructor(e){super(`A middleware for operation "${e}" called next() more than once`)}},l=class extends Error{code=`COMMAND_AGGREGATE_NOT_DECLARED`;name=`CommandAggregateError`;constructor(e,t){super(`Operation "${e}" audited aggregate type "${t}" outside its declared aggregates`)}},u=class extends Error{code=`COMMAND_PERMISSION_NOT_CONFIGURED`;name=`CommandPermissionError`;constructor(e){super(`Operation "${e}" declares a permission but the Foundation has no checkPermission`)}},d=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;class o extends s{constructor(e,t){super(e,{observability:r,writeAudit:i,checkPermission:a,defaults:t})}}this.Command=o}};export{d 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\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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"}
|
|
1
|
+
{"version":3,"file":"client.mjs","names":["#kernel","#observability","#writeAudit","#checkPermission","#defaults","CommandKernelClass","#execute"],"sources":["../../../src/components/foundation/client.ts"],"sourcesContent":["import type { Value } from 'convex/values'\nimport {\n\tCommand as CommandKernelClass,\n\ttype CommandExecution,\n\ttype CommandHandlerResult,\n\ttype CommandInput,\n\ttype CommandRegistry,\n\ttype CommandResult,\n\ttype Parseable,\n} from './modules/command/command'\nimport { Observability, type ObservabilityOptions } from './modules/observability/observability'\nimport { Query } from './modules/query/query'\nimport { executeResultBoundary, projectResult } from './result'\nimport { emitSemanticEvent } from './telemetry'\nimport {\n\toperationFactory,\n\ttype CommandPreparation,\n\ttype OperationExtension,\n\ttype OperationHostContext,\n} from './operation'\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, Value>\n\tclassification: string\n}\n\nexport type AuditWriter<Result = unknown> = (\n\tcontext: never,\n\tentry: AuditEntryInput,\n) => MaybePromise<Result>\n\ntype MaybePromise<Value> = Value | Promise<Value>\ntype OperationKey<Operations extends CommandRegistry> = Extract<keyof Operations, string>\n\nexport type AuditedOperation = Readonly<{\n\tcommand: Parseable<unknown>\n\tresult: Parseable<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\tprepare?: (context: never, command: never) => MaybePromise<CommandPreparation<never>>\n\treplayResult?: Parseable<unknown>\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 object = object,\n\tInput = unknown,\n\tResult = unknown,\n> = (input: {\n\toperation: string\n\tcommand: Input\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<Result>\n}) => Promise<Result>\n\n/** Any-context middleware — what registries and operations accept. */\n// Contravariant seam: the supertype every typed CommandMiddleware<C, E>\n// assigns to. Context, command, and next are contravariant storage slots;\n// dispatch restores the selected operation's types before invocation.\nexport type AnyCommandMiddleware<Input = never, Result = unknown, NextResult = never> = (input: {\n\toperation: string\n\tdefinition?: AuditedOperation\n\tcommand: Input\n\tcontext: never\n\tnext: (options?: { context?: object }) => Promise<NextResult>\n}) => Promise<Result>\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\tstatic withContext = operationFactory\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>(definition: Definition): 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 object = object,\n\t\tInput = unknown,\n\t\tResult = unknown,\n\t>(\n\t\tmiddleware: CommandMiddleware<Context, Extension, Input, Result>,\n\t): CommandMiddleware<Context, Extension, Input, Result> {\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\t// SAFETY: this map preserves every operations key and selects its aggregates property.\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 & OperationExtension<Operations[Key]>,\n\t\t\tcommand: CommandInput<Operations, Key>,\n\t\t) => MaybePromise<CommandHandlerResult<Operations, Key>>\n\t}) {\n\t\treturn this.#kernel.exec({\n\t\t\toperation: executor.operation,\n\t\t\thandler: (context, command) =>\n\t\t\t\texecutor.handler(\n\t\t\t\t\t// SAFETY: operationFactory requires enrichment middleware before this handler.\n\t\t\t\t\tcontext as Context & OperationExtension<Operations[Key]>,\n\t\t\t\t\tcommand,\n\t\t\t\t),\n\t\t})\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\t// SAFETY: the injected policy receives the host context used by this Command instance.\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\t// SAFETY: prepare belongs to this definition; its context and parsed command are paired by the kernel.\n\t\t\t\tconst preparation = await execution.definition.prepare?.(\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\tif (preparation?.kind === 'replay') {\n\t\t\t\t\t// SAFETY: replayResult validates the stored output for this selected operation.\n\t\t\t\t\treturn execution.definition.replayResult\n\t\t\t\t\t\t? (execution.definition.replayResult.parse(preparation.result) as CommandResult<\n\t\t\t\t\t\t\t\tOperations,\n\t\t\t\t\t\t\t\tKey\n\t\t\t\t\t\t\t>)\n\t\t\t\t\t\t: execution.parseResult(preparation.result)\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: Context) => {\n\t\t\t\t\t// SAFETY: registry guards receive the same host context, enriched by its middleware.\n\t\t\t\t\tawait this.#defaults.guard?.(context as never)\n\t\t\t\t\t// SAFETY: guard is called with the selected operation's parsed command and middleware context.\n\t\t\t\t\tawait execution.definition.guard?.(context as never, execution.command as never)\n\t\t\t\t\treturn execution.runUnparsed(context)\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: Context,\n\t\t\t\t): Promise<CommandHandlerResult<Operations, Key>> => {\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\t// SAFETY: this chain belongs to the selected operation; parseResult validates its final output before audit.\n\t\t\t\t\tconst layer = middleware[index] as\n\t\t\t\t\t\t| AnyCommandMiddleware<\n\t\t\t\t\t\t\t\tCommandInput<Operations, Key>,\n\t\t\t\t\t\t\t\tCommandHandlerResult<Operations, Key>,\n\t\t\t\t\t\t\t\tCommandHandlerResult<Operations, Key>\n\t\t\t\t\t\t >\n\t\t\t\t\t\t| undefined\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\tdefinition: execution.definition,\n\t\t\t\t\t\tcommand: execution.command,\n\t\t\t\t\t\t// SAFETY: middleware context is erased only in the heterogeneous registry.\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? {\n\t\t\t\t\t\t\t\t\t\t\t...context,\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}\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 parses whatever leaves the chain exactly once.\n\t\t\t\tconst result = execution.parseResult(await dispatch(0, execution.context))\n\t\t\t\t// SAFETY: the selected audit callback receives its validated command/result and original host context.\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(execution.operation, audit.aggregate.type)\n\t\t\t\t\t}\n\t\t\t\t\t// SAFETY: the host injected this writer for the context supplied to its Command.\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\t// SAFETY: complete comes from this invocation's prepare hook and receives its validated result.\n\t\t\t\tawait preparation?.complete?.(result as never)\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(`A middleware for operation \"${operation}\" called next() more than once`)\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<Context, Operations extends AuditedRegistry> = BoundCommand<\n\tContext,\n\tOperations\n>\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\t\treadonly [Key in keyof Operations]: OperationHostContext<Context>\n\t\t},\n\t\tdefaults?: CommandDefaults,\n\t): BoundCommand<Context, Operations>\n\toperation: (typeof BoundCommand)['operation']\n\twithContext: (typeof BoundCommand)['withContext']\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<Component extends FoundationComponentApi = FoundationComponentApi> {\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\tclass HostCommand<Context, const Operations extends AuditedRegistry> extends BoundCommand<\n\t\t\tContext,\n\t\t\tOperations\n\t\t> {\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\tthis.Command = HostCommand\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 { CommandPreparation, TypedOperation } from './operation'\nexport type {\n\tCommandArgument,\n\tCommandExecution,\n\tCommandHandlerResult,\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 { AnyQueryMiddleware, QueryExecution, QueryMiddleware } from './modules/query/query'\nexport type { Result, ResultBoundary, TransactionMetricsContext } 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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"}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
declare namespace command_d_exports {
|
|
2
|
-
export { Command, CommandExecution, CommandInput, CommandRegistry, CommandResult, Parseable };
|
|
2
|
+
export { Command, CommandArgument, CommandExecution, CommandHandlerResult, CommandInput, CommandRegistry, CommandResult, Parseable, SchemaInput };
|
|
3
3
|
}
|
|
4
4
|
type Parseable<Output> = Readonly<{
|
|
5
|
-
parse: (value:
|
|
5
|
+
parse: <Input>(value: Input) => Output;
|
|
6
6
|
}>;
|
|
7
|
+
/** Zod exposes input separately for transforms; plain parsers retain their output contract. */
|
|
8
|
+
type SchemaInput<Schema extends Parseable<unknown>> = Schema extends {
|
|
9
|
+
_input: infer Input;
|
|
10
|
+
} ? Input : Parsed<Schema>;
|
|
11
|
+
type CommandHandlerResult<Registry extends CommandRegistry, Key extends Operation<Registry>> = SchemaInput<Registry[Key]['result']>;
|
|
7
12
|
type CommandRegistry = Readonly<Record<string, CommandDefinition>>;
|
|
13
|
+
/** Values accepted from callers before the command schema parses them. */
|
|
14
|
+
type CommandArgument<Registry extends CommandRegistry, Key extends Operation<Registry>> = SchemaInput<Registry[Key]['command']>;
|
|
8
15
|
type CommandInput<Registry extends CommandRegistry, Key extends Operation<Registry>> = Parsed<Registry[Key]['command']>;
|
|
9
16
|
type CommandResult<Registry extends CommandRegistry, Key extends Operation<Registry>> = Parsed<Registry[Key]['result']>;
|
|
10
17
|
type CommandExecution<Context, Registry extends CommandRegistry, Key extends Operation<Registry>> = Readonly<{
|
|
@@ -12,26 +19,28 @@ type CommandExecution<Context, Registry extends CommandRegistry, Key extends Ope
|
|
|
12
19
|
operation: Key;
|
|
13
20
|
definition: Registry[Key];
|
|
14
21
|
command: CommandInput<Registry, Key>;
|
|
15
|
-
parseResult: (value:
|
|
22
|
+
parseResult: <Input>(value: Input) => CommandResult<Registry, Key>;
|
|
16
23
|
/** Runs the handler (result-parsed). Accepts a middleware-enriched context. */
|
|
17
24
|
run: (context?: Context) => Promise<CommandResult<Registry, Key>>;
|
|
25
|
+
/** Runs without parsing for owners that validate after their middleware chain. */
|
|
26
|
+
runUnparsed: (context?: Context) => Promise<CommandHandlerResult<Registry, Key>>;
|
|
18
27
|
}>;
|
|
19
28
|
type CommandDefinition = Readonly<{
|
|
20
29
|
command: Parseable<unknown>;
|
|
21
30
|
result: Parseable<unknown>;
|
|
22
31
|
}>;
|
|
23
32
|
type Operation<Registry extends CommandRegistry> = Extract<keyof Registry, string>;
|
|
24
|
-
type Parsed<Schema
|
|
33
|
+
type Parsed<Schema extends Parseable<unknown>> = ReturnType<Schema['parse']>;
|
|
25
34
|
type MaybePromise<Value> = Value | Promise<Value>;
|
|
26
35
|
type Execute<Context, Registry extends CommandRegistry> = <Key extends Operation<Registry>>(execution: CommandExecution<Context, Registry, Key>) => Promise<CommandResult<Registry, Key>>;
|
|
27
36
|
type FixedExecutor<Context, Registry extends CommandRegistry, Key extends Operation<Registry>> = Readonly<{
|
|
28
37
|
operation: Key;
|
|
29
|
-
handler: (context: Context, command: CommandInput<Registry, Key>) => MaybePromise<
|
|
38
|
+
handler: (context: Context, command: CommandInput<Registry, Key>) => MaybePromise<CommandHandlerResult<Registry, Key>>;
|
|
30
39
|
}>;
|
|
31
40
|
type DynamicExecutor<Context, Registry extends CommandRegistry, Dispatcher, Key extends Operation<Registry>> = Readonly<{
|
|
32
41
|
dispatcher: Parseable<Dispatcher>;
|
|
33
42
|
select: (dispatcher: Dispatcher) => Key;
|
|
34
|
-
handler: (context: Context, command: CommandInput<Registry, Key>) => MaybePromise<
|
|
43
|
+
handler: (context: Context, command: CommandInput<Registry, Key>) => MaybePromise<CommandHandlerResult<Registry, Key>>;
|
|
35
44
|
}>;
|
|
36
45
|
/** Generic command kernel. Host policy is injected by the owning domain. */
|
|
37
46
|
declare class Command<Context, const Registry extends CommandRegistry> {
|
|
@@ -40,9 +49,9 @@ declare class Command<Context, const Registry extends CommandRegistry> {
|
|
|
40
49
|
readonly operations: Registry;
|
|
41
50
|
readonly execute: Execute<Context, Registry>;
|
|
42
51
|
});
|
|
43
|
-
exec<const Key extends Operation<Registry>>(executor: FixedExecutor<Context, Registry, Key>): (context: Context, command:
|
|
52
|
+
exec<const Key extends Operation<Registry>>(executor: FixedExecutor<Context, Registry, Key>): (context: Context, command: CommandArgument<Registry, Key>) => Promise<CommandResult<Registry, Key>>;
|
|
44
53
|
exec<Dispatcher, const Key extends Operation<Registry>>(executor: DynamicExecutor<Context, Registry, Dispatcher, Key>): (context: Context, command: Dispatcher) => Promise<CommandResult<Registry, Key>>;
|
|
45
54
|
}
|
|
46
55
|
//#endregion
|
|
47
|
-
export { Command, CommandExecution, CommandInput, CommandRegistry, CommandResult, Parseable, command_d_exports };
|
|
56
|
+
export { Command, CommandArgument, CommandExecution, CommandHandlerResult, CommandInput, CommandRegistry, CommandResult, Parseable, SchemaInput, command_d_exports };
|
|
48
57
|
//# sourceMappingURL=command.d.mts.map
|
|
@@ -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 e=>c(await l(e??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,runUnparsed:async e=>l(e??n,s),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<{
|
|
1
|
+
{"version":3,"file":"command.mjs","names":["#operations","#execute"],"sources":["../../../../../src/components/foundation/modules/command/command.ts"],"sourcesContent":["export type Parseable<Output> = Readonly<{\n\tparse: <Input>(value: Input) => Output\n}>\n\n/** Zod exposes input separately for transforms; plain parsers retain their output contract. */\nexport type SchemaInput<Schema extends Parseable<unknown>> = Schema extends { _input: infer Input }\n\t? Input\n\t: Parsed<Schema>\nexport type CommandHandlerResult<\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = SchemaInput<Registry[Key]['result']>\n\nexport type CommandRegistry = Readonly<Record<string, CommandDefinition>>\n\n/** Values accepted from callers before the command schema parses them. */\nexport type CommandArgument<\n\tRegistry extends CommandRegistry,\n\tKey extends Operation<Registry>,\n> = SchemaInput<Registry[Key]['command']>\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: <Input>(value: Input) => CommandResult<Registry, Key>\n\t/** Runs the handler (result-parsed). Accepts a middleware-enriched context. */\n\trun: (context?: Context) => Promise<CommandResult<Registry, Key>>\n\t/** Runs without parsing for owners that validate after their middleware chain. */\n\trunUnparsed: (context?: Context) => Promise<CommandHandlerResult<Registry, Key>>\n}>\n\ntype CommandDefinition = Readonly<{\n\tcommand: Parseable<unknown>\n\tresult: Parseable<unknown>\n}>\n\ntype Operation<Registry extends CommandRegistry> = Extract<keyof Registry, string>\ntype Parsed<Schema extends Parseable<unknown>> = ReturnType<Schema['parse']>\ntype MaybePromise<Value> = Value | Promise<Value>\ntype Execute<Context, Registry extends CommandRegistry> = <Key extends Operation<Registry>>(\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<CommandHandlerResult<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<CommandHandlerResult<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: CommandArgument<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): (context: Context, command: Dispatcher) => Promise<CommandResult<Registry, Key>>\n\texec<Dispatcher>(\n\t\texecutor:\n\t\t\t| FixedExecutor<Context, Registry, Operation<Registry>>\n\t\t\t| DynamicExecutor<Context, Registry, Dispatcher, Operation<Registry>>,\n\t) {\n\t\treturn async (\n\t\t\tcontext: Context,\n\t\t\tvalue: Dispatcher | CommandArgument<Registry, Operation<Registry>>,\n\t\t) => {\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 (!Object.prototype.hasOwnProperty.call(this.#operations, selected.operation)) {\n\t\t\t\tthrow new CommandConfigurationError()\n\t\t\t}\n\t\t\tconst operation = selected.operation\n\t\t\tconst definition = this.#operations[operation]\n\t\t\t// SAFETY: the selected registry entry owns this command schema and its output.\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\t// SAFETY: the selected entry also owns the result schema, including transforms.\n\t\t\tconst parseResult = <Input>(result: Input) =>\n\t\t\t\tdefinition.result.parse(result) as CommandResult<Registry, Operation<Registry>>\n\t\t\tconst handler = executor.handler\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\trunUnparsed: async (contextOverride?: Context) =>\n\t\t\t\t\thandler(contextOverride ?? context, command),\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":"AAqFA,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,CAWA,KACC,EAGC,CACD,OAAO,MACN,EACA,IACI,CACJ,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,GAAI,CAAC,OAAO,UAAU,eAAe,KAAK,KAAKD,GAAa,EAAS,SAAS,EAC7E,MAAM,IAAI,EAEX,IAAM,EAAY,EAAS,UACrB,EAAa,KAAKA,GAAY,GAE9B,EAAU,EAAW,QAAQ,MAAM,EAAS,KAAK,EAKjD,EAAsB,GAC3B,EAAW,OAAO,MAAM,CAAM,EACzB,EAAU,EAAS,QACzB,OAAO,KAAKC,GAAS,CACpB,UACA,YACA,aACA,UACA,cACA,YAAa,KAAO,IACnB,EAAQ,GAAmB,EAAS,CAAO,EAC5C,IAAK,KAAO,IACX,EAAY,MAAM,EAAQ,GAAmB,EAAS,CAAO,CAAC,CAChE,CAAC,CACF,CACD,CACD"}
|
|
@@ -10,7 +10,7 @@ type CommandObservation = Readonly<{
|
|
|
10
10
|
}>;
|
|
11
11
|
type ObservabilityOptions = Readonly<{
|
|
12
12
|
enabled?: boolean | (() => boolean);
|
|
13
|
-
classifyError: (
|
|
13
|
+
classifyError: (cause: unknown) => Readonly<{
|
|
14
14
|
outcome: 'denied' | 'failed';
|
|
15
15
|
errorCode: string;
|
|
16
16
|
}>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=/^[A-Za-z][A-Za-z0-9_.-]{0,159}$/,t=/^[A-Z][A-Z0-9_]{0,95}$/;var n=class{#e;constructor(e){this.#e=e}async observe(e,t){let n=this.#n();try{let r=await t();return this.#t({...e,outcome:`completed`,durationMs:this.#r(n)}),r}catch(t){try{this.#t({...e,...this.#e.classifyError(t),durationMs:this.#r(n)})}catch{}throw t}}#t(n){try{
|
|
1
|
+
const e=/^[A-Za-z][A-Za-z0-9_.-]{0,159}$/,t=/^[A-Z][A-Z0-9_]{0,95}$/;var n=class{#e;constructor(e){this.#e=e}async observe(e,t){let n=this.#n();try{let r=await t();return this.#t({...e,outcome:`completed`,durationMs:this.#r(n)}),r}catch(t){try{this.#t({...e,...this.#e.classifyError(t),durationMs:this.#r(n)})}catch{}throw t}}#t(n){try{let r=this.#e.enabled;if((r===void 0||r===!0||r===!1?r:r())!==!0||!e.test(n.operation)||!e.test(n.classification)||n.errorCode!==void 0&&!t.test(n.errorCode))return;if(this.#e.emit){this.#e.emit(Object.freeze(n));return}console.info(JSON.stringify({event:`command.execution`,...n}))}catch{}}#n(){try{return this.#e.clock?.()??Date.now()}catch{return Date.now()}}#r(e){return Math.max(0,Math.min(6e5,this.#n()-e))}};export{n as Observability};
|
|
2
2
|
//# sourceMappingURL=observability.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observability.mjs","names":["#options","#now","#emit","#duration"],"sources":["../../../../../src/components/foundation/modules/observability/observability.ts"],"sourcesContent":["export type CommandObservation = Readonly<{\n\toperation: string\n\tclassification: string\n\toutcome: 'completed' | 'denied' | 'failed'\n\terrorCode?: string\n\tdurationMs: number\n}>\n\nexport type ObservabilityOptions = Readonly<{\n\tenabled?: boolean | (() => boolean)\n\tclassifyError: (
|
|
1
|
+
{"version":3,"file":"observability.mjs","names":["#options","#now","#emit","#duration"],"sources":["../../../../../src/components/foundation/modules/observability/observability.ts"],"sourcesContent":["export type CommandObservation = Readonly<{\n\toperation: string\n\tclassification: string\n\toutcome: 'completed' | 'denied' | 'failed'\n\terrorCode?: string\n\tdurationMs: number\n}>\n\nexport type ObservabilityOptions = Readonly<{\n\tenabled?: boolean | (() => boolean)\n\tclassifyError: (cause: unknown) => Readonly<{\n\t\toutcome: 'denied' | 'failed'\n\t\terrorCode: string\n\t}>\n\temit?: (observation: CommandObservation) => void\n\tclock?: () => number\n}>\n\nconst identifier = /^[A-Za-z][A-Za-z0-9_.-]{0,159}$/\nconst errorCode = /^[A-Z][A-Z0-9_]{0,95}$/\n\n/** Payload-free command telemetry that cannot change command behavior. */\nexport class Observability {\n\treadonly #options: ObservabilityOptions\n\n\tconstructor(options: ObservabilityOptions) {\n\t\tthis.#options = options\n\t}\n\n\tasync observe<Result>(\n\t\tactivation: Readonly<{ operation: string; classification: string }>,\n\t\texecute: () => Result | Promise<Result>,\n\t): Promise<Result> {\n\t\tconst started = this.#now()\n\t\ttry {\n\t\t\tconst result = await execute()\n\t\t\tthis.#emit({\n\t\t\t\t...activation,\n\t\t\t\toutcome: 'completed',\n\t\t\t\tdurationMs: this.#duration(started),\n\t\t\t})\n\t\t\treturn result\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tthis.#emit({\n\t\t\t\t\t...activation,\n\t\t\t\t\t...this.#options.classifyError(error),\n\t\t\t\t\tdurationMs: this.#duration(started),\n\t\t\t\t})\n\t\t\t} catch {\n\t\t\t\t// Preserve the original command failure when telemetry fails.\n\t\t\t}\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t#emit(observation: CommandObservation): void {\n\t\ttry {\n\t\t\tconst option = this.#options.enabled\n\t\t\tconst enabled =\n\t\t\t\toption === undefined || option === true || option === false ? option : option()\n\t\t\tif (\n\t\t\t\tenabled !== true ||\n\t\t\t\t!identifier.test(observation.operation) ||\n\t\t\t\t!identifier.test(observation.classification) ||\n\t\t\t\t(observation.errorCode !== undefined && !errorCode.test(observation.errorCode))\n\t\t\t) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.#options.emit) {\n\t\t\t\tthis.#options.emit(Object.freeze(observation))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconsole.info(JSON.stringify({ event: 'command.execution', ...observation }))\n\t\t} catch {\n\t\t\t// Observability is intentionally behaviorally inert.\n\t\t}\n\t}\n\n\t#now(): number {\n\t\ttry {\n\t\t\treturn this.#options.clock?.() ?? Date.now()\n\t\t} catch {\n\t\t\treturn Date.now()\n\t\t}\n\t}\n\n\t#duration(started: number): number {\n\t\treturn Math.max(0, Math.min(600_000, this.#now() - started))\n\t}\n}\n"],"mappings":"AAkBA,MAAM,EAAa,kCACb,EAAY,yBAGlB,IAAa,EAAb,KAA2B,CAC1B,GAEA,YAAY,EAA+B,CAC1C,KAAKA,GAAW,CACjB,CAEA,MAAM,QACL,EACA,EACkB,CAClB,IAAM,EAAU,KAAKC,GAAK,EAC1B,GAAI,CACH,IAAM,EAAS,MAAM,EAAQ,EAM7B,OALA,KAAKC,GAAM,CACV,GAAG,EACH,QAAS,YACT,WAAY,KAAKC,GAAU,CAAO,CACnC,CAAC,EACM,CACR,OAAS,EAAO,CACf,GAAI,CACH,KAAKD,GAAM,CACV,GAAG,EACH,GAAG,KAAKF,GAAS,cAAc,CAAK,EACpC,WAAY,KAAKG,GAAU,CAAO,CACnC,CAAC,CACF,MAAQ,CAER,CACA,MAAM,CACP,CACD,CAEA,GAAM,EAAuC,CAC5C,GAAI,CACH,IAAM,EAAS,KAAKH,GAAS,QAG7B,IADC,IAAW,IAAA,IAAa,IAAW,IAAQ,IAAW,GAAQ,EAAS,EAAO,KAElE,IACZ,CAAC,EAAW,KAAK,EAAY,SAAS,GACtC,CAAC,EAAW,KAAK,EAAY,cAAc,GAC1C,EAAY,YAAc,IAAA,IAAa,CAAC,EAAU,KAAK,EAAY,SAAS,EAE7E,OAED,GAAI,KAAKA,GAAS,KAAM,CACvB,KAAKA,GAAS,KAAK,OAAO,OAAO,CAAW,CAAC,EAC7C,MACD,CACA,QAAQ,KAAK,KAAK,UAAU,CAAE,MAAO,oBAAqB,GAAG,CAAY,CAAC,CAAC,CAC5E,MAAQ,CAER,CACD,CAEA,IAAe,CACd,GAAI,CACH,OAAO,KAAKA,GAAS,QAAQ,GAAK,KAAK,IAAI,CAC5C,MAAQ,CACP,OAAO,KAAK,IAAI,CACjB,CACD,CAEA,GAAU,EAAyB,CAClC,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,IAAS,KAAKC,GAAK,EAAI,CAAO,CAAC,CAC5D,CACD"}
|
|
@@ -12,21 +12,21 @@ type MaybePromise<Value> = Value | Promise<Value>;
|
|
|
12
12
|
* inside the host's injected execute policy: `next({ context })` merges
|
|
13
13
|
* enrichment into the context handed to inner middleware and the handler.
|
|
14
14
|
*/
|
|
15
|
-
type QueryMiddleware<Context = never, Extension extends
|
|
15
|
+
type QueryMiddleware<Context = never, Extension extends object = object> = <Result>(input: {
|
|
16
16
|
metadata: object;
|
|
17
17
|
context: Context;
|
|
18
18
|
next: (options?: {
|
|
19
19
|
context?: Extension;
|
|
20
|
-
}) => Promise<
|
|
21
|
-
}) => Promise<
|
|
20
|
+
}) => Promise<Result>;
|
|
21
|
+
}) => Promise<Result>;
|
|
22
22
|
/** Any-context middleware — what kernels and executors accept. */
|
|
23
|
-
type AnyQueryMiddleware = (input: {
|
|
23
|
+
type AnyQueryMiddleware = <Result>(input: {
|
|
24
24
|
metadata: object;
|
|
25
25
|
context: never;
|
|
26
26
|
next: (options?: {
|
|
27
|
-
context?:
|
|
28
|
-
}) => Promise<
|
|
29
|
-
}) => Promise<
|
|
27
|
+
context?: object;
|
|
28
|
+
}) => Promise<Result>;
|
|
29
|
+
}) => Promise<Result>;
|
|
30
30
|
/** Generic query kernel. Authorization remains an injected host policy. */
|
|
31
31
|
declare class Query<Context, Defaults extends object> {
|
|
32
32
|
#private;
|
|
@@ -34,7 +34,7 @@ declare class Query<Context, Defaults extends object> {
|
|
|
34
34
|
* Identity helper that types a middleware against its context and the
|
|
35
35
|
* extension it passes downstream via next({ context }).
|
|
36
36
|
*/
|
|
37
|
-
static middleware<Context = never, const Extension extends
|
|
37
|
+
static middleware<Context = never, const Extension extends object = object>(middleware: QueryMiddleware<Context, Extension>): QueryMiddleware<Context, Extension>;
|
|
38
38
|
constructor(configuration: {
|
|
39
39
|
readonly defaults: Defaults;
|
|
40
40
|
readonly middleware?: readonly AnyQueryMiddleware[];
|
|
@@ -1,2 +1,2 @@
|
|
|
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
|
|
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 s(0,n)}})}}};export{t as Query};
|
|
2
2
|
//# sourceMappingURL=query.mjs.map
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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, Extension extends object = object> = <Result>(input: {\n\tmetadata: object\n\tcontext: Context\n\tnext: (options?: { context?: Extension }) => Promise<Result>\n}) => Promise<Result>\n\n/** Any-context middleware — what kernels and executors accept. */\n// Contravariant seam: the supertype every typed QueryMiddleware<C, E>\n// assigns to. The result stays generic across the chain; context is erased\n// only in storage. Definition sites stay typed via Query.middleware.\nexport type AnyQueryMiddleware = <Result>(input: {\n\tmetadata: object\n\tcontext: never\n\tnext: (options?: { context?: object }) => Promise<Result>\n}) => Promise<Result>\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<Context = never, const Extension extends object = object>(\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<Arguments extends readonly unknown[], Result, Metadata extends object = object>(executor: {\n\t\treadonly metadata?: Metadata\n\t\treadonly middleware?: readonly AnyQueryMiddleware[]\n\t\treadonly handler: (context: Context, ...args: Arguments) => 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,\n\t\t\t}\n\t\t\tconst middleware = [...this.#middleware, ...(executor.middleware ?? [])]\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 (index: number, current: Context): Promise<Result> => {\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\t// SAFETY: middleware context is erased only in storage; the kernel supplies its Context.\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(index + 1, options?.context ? { ...current, ...options.context } : current),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\treturn dispatch(0, context)\n\t\t\t}\n\t\t\treturn this.#execute({ context, metadata, run })\n\t\t}\n\t}\n}\n"],"mappings":"AA6BA,IAAM,EAAN,cAAmC,KAAM,CACxC,KAAgB,+BAChB,KAAgB,uBAEhB,aAAc,CACb,MAAM,iDAAiD,CACxD,CACD,EAGa,EAAb,KAAqD,CACpD,GACA,GACA,GAQA,OAAO,WACN,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,KAAqF,EAIvB,CAC7D,OAAQ,EAAS,GAAG,IAAS,CAC5B,IAAM,EAAW,CAChB,GAAG,KAAKF,GACR,GAAG,EAAS,QACb,EACM,EAAa,CAAC,GAAG,KAAKC,GAAa,GAAI,EAAS,YAAc,CAAC,CAAE,EAqBvE,OAAO,KAAKC,GAAS,CAAE,UAAS,WAAU,aApBD,CACxC,GAAI,EAAW,SAAW,EACzB,OAAO,EAAS,QAAQ,EAAS,GAAG,CAAI,EAEzC,IAAI,EAAU,GACR,EAAW,MAAO,EAAe,IAAsC,CAC5E,GAAI,GAAS,EAAS,MAAM,IAAI,EAChC,EAAU,EACV,IAAM,EAAQ,EAAW,GAEzB,OADK,EACE,EAAM,CACZ,WAEA,QAAS,EACT,KAAO,GACN,EAAS,EAAQ,EAAG,GAAS,QAAU,CAAE,GAAG,EAAS,GAAG,EAAQ,OAAQ,EAAI,CAAO,CACrF,CAAC,EAPkB,EAAS,QAAQ,EAAS,GAAG,CAAI,CAQrD,EACA,OAAO,EAAS,EAAG,CAAO,CAC3B,CAC8C,CAAC,CAChD,CACD,CACD"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CommandHandlerResult, CommandInput, Parseable, SchemaInput } from "./modules/command/command.mjs";
|
|
2
|
+
import { AnyCommandMiddleware, AuditEntryInput, AuditedOperation, AuditedRegistry } from "./client.mjs";
|
|
3
|
+
//#region src/components/foundation/operation.d.ts
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
type CommandPreparation<Result> = {
|
|
6
|
+
kind: 'replay';
|
|
7
|
+
result: unknown;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'execute';
|
|
10
|
+
complete?: (result: Result) => MaybePromise<void>;
|
|
11
|
+
};
|
|
12
|
+
type Parsed<S extends Parseable<unknown>> = ReturnType<S['parse']>;
|
|
13
|
+
declare const extension: unique symbol;
|
|
14
|
+
declare const hostContext: unique symbol;
|
|
15
|
+
type OperationHostContext<Context> = {
|
|
16
|
+
readonly [hostContext]?: (context: Context) => void;
|
|
17
|
+
};
|
|
18
|
+
type OperationExtension<Definition> = Definition extends {
|
|
19
|
+
readonly [extension]?: infer Extension;
|
|
20
|
+
} ? Extension : unknown;
|
|
21
|
+
type MiddlewareInput<Context, Input, Output, Extension> = {
|
|
22
|
+
operation: string;
|
|
23
|
+
context: Context;
|
|
24
|
+
command: Input;
|
|
25
|
+
next: keyof Extension extends never ? (options?: {
|
|
26
|
+
context?: Extension;
|
|
27
|
+
}) => Promise<Output> : (options: {
|
|
28
|
+
context: Extension;
|
|
29
|
+
}) => Promise<Output>;
|
|
30
|
+
};
|
|
31
|
+
type LocalMiddleware<C, I, O, E> = (input: MiddlewareInput<C, I, O, E>) => Promise<O>;
|
|
32
|
+
type AuditFor<Aggregate extends string> = Omit<AuditEntryInput, 'classification' | 'aggregate'> & {
|
|
33
|
+
aggregate: {
|
|
34
|
+
type: Aggregate;
|
|
35
|
+
id: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
type TypedOperation<Context, Input extends Parseable<unknown>, Output extends Parseable<unknown>, Aggregates extends readonly string[], Extension> = OperationHostContext<Context> & {
|
|
39
|
+
readonly command: Input;
|
|
40
|
+
readonly result: Output;
|
|
41
|
+
readonly classification: string;
|
|
42
|
+
readonly permission?: string;
|
|
43
|
+
/** Called after permission, inside observation; returns invocation-local hooks. */
|
|
44
|
+
readonly prepare?: (context: Context, command: Parsed<Input>) => MaybePromise<CommandPreparation<Parsed<Output>>>;
|
|
45
|
+
/** Validates stored outputs when result transforms accept a different input shape. */
|
|
46
|
+
readonly replayResult?: Parseable<Parsed<Output>>;
|
|
47
|
+
readonly aggregates?: Aggregates;
|
|
48
|
+
readonly guard?: (context: Context & Extension, command: Parsed<Input>) => MaybePromise<void>;
|
|
49
|
+
readonly audit: (resolution: {
|
|
50
|
+
command: Parsed<Input>;
|
|
51
|
+
result: Parsed<Output>;
|
|
52
|
+
}, context: Context) => MaybePromise<AuditFor<Aggregates[number]> | null>;
|
|
53
|
+
readonly [extension]?: Extension;
|
|
54
|
+
} & (keyof Extension extends never ? {
|
|
55
|
+
readonly middleware?: readonly LocalMiddleware<Context, Parsed<Input>, SchemaInput<Output>, Extension>[];
|
|
56
|
+
} : {
|
|
57
|
+
readonly middleware: readonly [LocalMiddleware<Context, Parsed<Input>, SchemaInput<Output>, Extension>, ...LocalMiddleware<Context & Extension, Parsed<Input>, SchemaInput<Output>, Record<never, never>>[]];
|
|
58
|
+
});
|
|
59
|
+
type RegistryInput<Context, Operations extends AuditedRegistry> = { [Key in Extract<keyof Operations, string>]: {
|
|
60
|
+
operation: Key;
|
|
61
|
+
definition: Operations[Key];
|
|
62
|
+
context: Context;
|
|
63
|
+
command: CommandInput<Operations, Key>;
|
|
64
|
+
next: () => Promise<CommandHandlerResult<Operations, Key>>;
|
|
65
|
+
}; }[Extract<keyof Operations, string>];
|
|
66
|
+
type StoredOperation<Context, Input extends Parseable<unknown>, Output extends Parseable<unknown>, Aggregates extends readonly string[], Extension> = Omit<TypedOperation<Context, Input, Output, Aggregates, Extension>, 'middleware'> & Pick<AuditedOperation, 'middleware'>;
|
|
67
|
+
/** Binds host context once; each operation infers its own parsed schemas. */
|
|
68
|
+
declare function operationFactory<Context, Extension extends Record<string, unknown> = Record<never, never>>(): {
|
|
69
|
+
operation<const Input extends Parseable<unknown>, const Output extends Parseable<unknown>, const Aggregates extends readonly string[] = readonly string[]>(definition: TypedOperation<Context, Input, Output, Aggregates, Extension>): StoredOperation<Context, Input, Output, Aggregates, Extension>;
|
|
70
|
+
registryMiddleware<const Operations extends AuditedRegistry>(operations: Operations & { readonly [Key in keyof Operations]: OperationHostContext<Context>; }, middleware: (input: RegistryInput<Context, Operations>) => Promise<CommandHandlerResult<Operations, Extract<keyof Operations, string>>>): AnyCommandMiddleware;
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
export { CommandPreparation, OperationExtension, OperationHostContext, TypedOperation, operationFactory };
|
|
74
|
+
//# sourceMappingURL=operation.d.mts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(){return{operation(e){return e},registryMiddleware(e,n){function r(t){return Object.prototype.hasOwnProperty.call(e,t.operation)&&e[t.operation]===t.definition}return e=>{if(!r(e))throw new t(e.operation);return n(e)}}}}var t=class extends Error{code=`COMMAND_MIDDLEWARE_REGISTRY_MISMATCH`;constructor(e){super(`Typed middleware does not own the definition of operation "${e}"`),this.name=`CommandMiddlewareRegistryError`}};export{e as operationFactory};
|
|
2
|
+
//# sourceMappingURL=operation.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation.mjs","names":[],"sources":["../../../src/components/foundation/operation.ts"],"sourcesContent":["import type {\n\tAuditEntryInput,\n\tAuditedRegistry,\n\tAuditedOperation,\n\tAnyCommandMiddleware,\n} from './client'\nimport type {\n\tCommandInput,\n\tCommandHandlerResult,\n\tParseable,\n\tSchemaInput,\n} from './modules/command/command'\n\ntype MaybePromise<T> = T | Promise<T>\nexport type CommandPreparation<Result> =\n\t| { kind: 'replay'; result: unknown }\n\t| { kind: 'execute'; complete?: (result: Result) => MaybePromise<void> }\ntype Parsed<S extends Parseable<unknown>> = ReturnType<S['parse']>\ndeclare const extension: unique symbol\ndeclare const hostContext: unique symbol\nexport type OperationHostContext<Context> = {\n\treadonly [hostContext]?: (context: Context) => void\n}\nexport type OperationExtension<Definition> = Definition extends {\n\treadonly [extension]?: infer Extension\n}\n\t? Extension\n\t: unknown\n\ntype MiddlewareInput<Context, Input, Output, Extension> = {\n\toperation: string\n\tcontext: Context\n\tcommand: Input\n\tnext: keyof Extension extends never\n\t\t? (options?: { context?: Extension }) => Promise<Output>\n\t\t: (options: { context: Extension }) => Promise<Output>\n}\ntype LocalMiddleware<C, I, O, E> = (input: MiddlewareInput<C, I, O, E>) => Promise<O>\ntype AuditFor<Aggregate extends string> = Omit<AuditEntryInput, 'classification' | 'aggregate'> & {\n\taggregate: { type: Aggregate; id: string }\n}\nexport type TypedOperation<\n\tContext,\n\tInput extends Parseable<unknown>,\n\tOutput extends Parseable<unknown>,\n\tAggregates extends readonly string[],\n\tExtension,\n> = OperationHostContext<Context> & {\n\treadonly command: Input\n\treadonly result: Output\n\treadonly classification: string\n\treadonly permission?: string\n\t/** Called after permission, inside observation; returns invocation-local hooks. */\n\treadonly prepare?: (\n\t\tcontext: Context,\n\t\tcommand: Parsed<Input>,\n\t) => MaybePromise<CommandPreparation<Parsed<Output>>>\n\t/** Validates stored outputs when result transforms accept a different input shape. */\n\treadonly replayResult?: Parseable<Parsed<Output>>\n\treadonly aggregates?: Aggregates\n\treadonly guard?: (context: Context & Extension, command: Parsed<Input>) => MaybePromise<void>\n\treadonly audit: (\n\t\tresolution: { command: Parsed<Input>; result: Parsed<Output> },\n\t\tcontext: Context,\n\t) => MaybePromise<AuditFor<Aggregates[number]> | null>\n\treadonly [extension]?: Extension\n} & (keyof Extension extends never\n\t\t? {\n\t\t\t\treadonly middleware?: readonly LocalMiddleware<\n\t\t\t\t\tContext,\n\t\t\t\t\tParsed<Input>,\n\t\t\t\t\tSchemaInput<Output>,\n\t\t\t\t\tExtension\n\t\t\t\t>[]\n\t\t\t}\n\t\t: {\n\t\t\t\treadonly middleware: readonly [\n\t\t\t\t\tLocalMiddleware<Context, Parsed<Input>, SchemaInput<Output>, Extension>,\n\t\t\t\t\t...LocalMiddleware<\n\t\t\t\t\t\tContext & Extension,\n\t\t\t\t\t\tParsed<Input>,\n\t\t\t\t\t\tSchemaInput<Output>,\n\t\t\t\t\t\tRecord<never, never>\n\t\t\t\t\t>[],\n\t\t\t\t]\n\t\t\t})\n\ntype RegistryInput<Context, Operations extends AuditedRegistry> = {\n\t[Key in Extract<keyof Operations, string>]: {\n\t\toperation: Key\n\t\tdefinition: Operations[Key]\n\t\tcontext: Context\n\t\tcommand: CommandInput<Operations, Key>\n\t\tnext: () => Promise<CommandHandlerResult<Operations, Key>>\n\t}\n}[Extract<keyof Operations, string>]\n\ntype StoredOperation<\n\tContext,\n\tInput extends Parseable<unknown>,\n\tOutput extends Parseable<unknown>,\n\tAggregates extends readonly string[],\n\tExtension,\n> = Omit<TypedOperation<Context, Input, Output, Aggregates, Extension>, 'middleware'> &\n\tPick<AuditedOperation, 'middleware'>\n\n/** Binds host context once; each operation infers its own parsed schemas. */\nexport function operationFactory<\n\tContext,\n\tExtension extends Record<string, unknown> = Record<never, never>,\n>() {\n\treturn {\n\t\toperation<\n\t\t\tconst Input extends Parseable<unknown>,\n\t\t\tconst Output extends Parseable<unknown>,\n\t\t\tconst Aggregates extends readonly string[] = readonly string[],\n\t\t>(\n\t\t\tdefinition: TypedOperation<Context, Input, Output, Aggregates, Extension>,\n\t\t): StoredOperation<Context, Input, Output, Aggregates, Extension> {\n\t\t\t// SAFETY: the kernel selects this definition and parses its input before\n\t\t\t// invoking the heterogeneous middleware registry. Erase only that\n\t\t\t// storage seam; callbacks were checked against these schemas above.\n\t\t\treturn definition as StoredOperation<Context, Input, Output, Aggregates, Extension>\n\t\t},\n\t\tregistryMiddleware<const Operations extends AuditedRegistry>(\n\t\t\toperations: Operations & {\n\t\t\t\treadonly [Key in keyof Operations]: OperationHostContext<Context>\n\t\t\t},\n\t\t\tmiddleware: (\n\t\t\t\tinput: RegistryInput<Context, Operations>,\n\t\t\t) => Promise<CommandHandlerResult<Operations, Extract<keyof Operations, string>>>,\n\t\t): AnyCommandMiddleware {\n\t\t\tfunction ownsDefinition(\n\t\t\t\tinput: Parameters<AnyCommandMiddleware>[0],\n\t\t\t): input is Parameters<AnyCommandMiddleware>[0] & RegistryInput<Context, Operations> {\n\t\t\t\treturn (\n\t\t\t\t\tObject.prototype.hasOwnProperty.call(operations, input.operation) &&\n\t\t\t\t\toperations[input.operation] === input.definition\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn (input) => {\n\t\t\t\tif (!ownsDefinition(input)) {\n\t\t\t\t\tthrow new CommandMiddlewareRegistryError(input.operation)\n\t\t\t\t}\n\t\t\t\treturn middleware(input)\n\t\t\t}\n\t\t},\n\t}\n}\n\nclass CommandMiddlewareRegistryError extends Error {\n\treadonly code = 'COMMAND_MIDDLEWARE_REGISTRY_MISMATCH'\n\tconstructor(operation: string) {\n\t\tsuper(`Typed middleware does not own the definition of operation \"${operation}\"`)\n\t\tthis.name = 'CommandMiddlewareRegistryError'\n\t}\n}\n"],"mappings":"AA2GA,SAAgB,GAGZ,CACH,MAAO,CACN,UAKC,EACiE,CAIjE,OAAO,CACR,EACA,mBACC,EAGA,EAGuB,CACvB,SAAS,EACR,EACoF,CACpF,OACC,OAAO,UAAU,eAAe,KAAK,EAAY,EAAM,SAAS,GAChE,EAAW,EAAM,aAAe,EAAM,UAExC,CACA,MAAQ,IAAU,CACjB,GAAI,CAAC,EAAe,CAAK,EACxB,MAAM,IAAI,EAA+B,EAAM,SAAS,EAEzD,OAAO,EAAW,CAAK,CACxB,CACD,CACD,CACD,CAEA,IAAM,EAAN,cAA6C,KAAM,CAClD,KAAgB,uCAChB,YAAY,EAAmB,CAC9B,MAAM,8DAA8D,EAAU,EAAE,EAChF,KAAK,KAAO,gCACb,CACD"}
|
|
@@ -17,7 +17,7 @@ type Result<Value, Failure> = Readonly<{
|
|
|
17
17
|
type ResultBoundary<Failure> = Readonly<{
|
|
18
18
|
ok: <Value>(value: Value) => Result<Value, Failure>;
|
|
19
19
|
handledError: <Value = never>(failure: Failure) => Result<Value, Failure>;
|
|
20
|
-
dataOf: (
|
|
20
|
+
dataOf: (cause: unknown) => Failure | undefined;
|
|
21
21
|
}>;
|
|
22
22
|
/** Return handled failures only before effects; otherwise rethrow for rollback. */
|
|
23
23
|
declare function executeResultBoundary<Value, Failure>(ctx: TransactionMetricsContext, operation: () => Promise<Value>, boundary: ResultBoundary<Failure>): Promise<Result<Value, Failure>>;
|