cvx-kit 0.1.6 → 0.1.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.
Files changed (116) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +63 -38
  3. package/dist/agent-tools.d.mts +2 -1
  4. package/dist/agent-tools.mjs +1 -1
  5. package/dist/agent-tools.mjs.map +1 -1
  6. package/dist/components/approvals/audit.mjs +1 -1
  7. package/dist/components/foundation/client.d.mts +16 -7
  8. package/dist/components/foundation/client.mjs +1 -1
  9. package/dist/components/foundation/client.mjs.map +1 -1
  10. package/dist/components/foundation/modules/command/command.d.mts +50 -10
  11. package/dist/components/foundation/modules/command/command.mjs +1 -1
  12. package/dist/components/foundation/modules/command/command.mjs.map +1 -1
  13. package/dist/components/foundation/modules/command/lifecycle.d.mts +81 -0
  14. package/dist/components/foundation/modules/command/lifecycle.mjs +2 -0
  15. package/dist/components/foundation/modules/command/lifecycle.mjs.map +1 -0
  16. package/dist/components/foundation/modules/observability/observability.d.mts +8 -0
  17. package/dist/components/foundation/modules/observability/observability.mjs +1 -1
  18. package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
  19. package/dist/components/foundation/operation.d.mts +6 -6
  20. package/dist/components/foundation/operation.mjs.map +1 -1
  21. package/dist/contracts.d.mts +4 -0
  22. package/dist/contracts.mjs +1 -0
  23. package/dist/crud.d.mts +2 -2
  24. package/dist/crud.mjs.map +1 -1
  25. package/dist/effect.d.mts +12 -0
  26. package/dist/effect.mjs +1 -0
  27. package/dist/errors.d.mts +2 -1
  28. package/dist/errors.mjs +1 -1
  29. package/dist/errors.mjs.map +1 -1
  30. package/dist/idempotency.d.mts +2 -0
  31. package/dist/idempotency.mjs +1 -0
  32. package/dist/modules/contracts/contract.d.mts +70 -0
  33. package/dist/modules/contracts/contract.mjs +2 -0
  34. package/dist/modules/contracts/contract.mjs.map +1 -0
  35. package/dist/modules/contracts/convex.d.mts +22 -0
  36. package/dist/modules/contracts/convex.mjs +2 -0
  37. package/dist/modules/contracts/convex.mjs.map +1 -0
  38. package/dist/modules/contracts/errors.d.mts +44 -0
  39. package/dist/modules/contracts/errors.mjs +2 -0
  40. package/dist/modules/contracts/errors.mjs.map +1 -0
  41. package/dist/modules/contracts/exposure.d.mts +105 -0
  42. package/dist/modules/contracts/exposure.mjs +2 -0
  43. package/dist/modules/contracts/exposure.mjs.map +1 -0
  44. package/dist/modules/contracts/idempotency.d.mts +87 -0
  45. package/dist/modules/contracts/idempotency.mjs +2 -0
  46. package/dist/modules/contracts/idempotency.mjs.map +1 -0
  47. package/dist/modules/contracts/standard.d.mts +16 -0
  48. package/dist/modules/contracts/standard.mjs +2 -0
  49. package/dist/modules/contracts/standard.mjs.map +1 -0
  50. package/dist/modules/contracts/workflow.d.mts +167 -0
  51. package/dist/modules/contracts/workflow.mjs +2 -0
  52. package/dist/modules/contracts/workflow.mjs.map +1 -0
  53. package/dist/modules/effect/agent-tools.d.mts +2 -0
  54. package/dist/modules/effect/agent-tools.mjs +1 -0
  55. package/dist/modules/effect/api-runtime.d.mts +38 -0
  56. package/dist/modules/effect/api-runtime.mjs +2 -0
  57. package/dist/modules/effect/api-runtime.mjs.map +1 -0
  58. package/dist/modules/effect/api-zod.d.mts +35 -0
  59. package/dist/modules/effect/api-zod.mjs +2 -0
  60. package/dist/modules/effect/api-zod.mjs.map +1 -0
  61. package/dist/modules/effect/api.d.mts +21 -0
  62. package/dist/modules/effect/api.mjs +2 -0
  63. package/dist/modules/effect/api.mjs.map +1 -0
  64. package/dist/modules/effect/client.d.mts +11 -0
  65. package/dist/modules/effect/client.mjs +1 -0
  66. package/dist/modules/effect/command.d.mts +63 -0
  67. package/dist/modules/effect/command.mjs +2 -0
  68. package/dist/modules/effect/command.mjs.map +1 -0
  69. package/dist/modules/effect/crud.d.mts +785 -0
  70. package/dist/modules/effect/crud.mjs +2 -0
  71. package/dist/modules/effect/crud.mjs.map +1 -0
  72. package/dist/modules/effect/errors.d.mts +8 -0
  73. package/dist/modules/effect/errors.mjs +2 -0
  74. package/dist/modules/effect/errors.mjs.map +1 -0
  75. package/dist/modules/effect/foundation.d.mts +21 -0
  76. package/dist/modules/effect/foundation.mjs +2 -0
  77. package/dist/modules/effect/foundation.mjs.map +1 -0
  78. package/dist/modules/effect/idempotency.d.mts +31 -0
  79. package/dist/modules/effect/idempotency.mjs +2 -0
  80. package/dist/modules/effect/idempotency.mjs.map +1 -0
  81. package/dist/modules/effect/normalize.d.mts +10 -0
  82. package/dist/modules/effect/normalize.mjs +2 -0
  83. package/dist/modules/effect/normalize.mjs.map +1 -0
  84. package/dist/modules/effect/operation.d.mts +141 -0
  85. package/dist/modules/effect/operation.mjs +2 -0
  86. package/dist/modules/effect/operation.mjs.map +1 -0
  87. package/dist/modules/effect/query.d.mts +69 -0
  88. package/dist/modules/effect/query.mjs +2 -0
  89. package/dist/modules/effect/query.mjs.map +1 -0
  90. package/dist/modules/effect/schema.d.mts +33 -0
  91. package/dist/modules/effect/schema.mjs +2 -0
  92. package/dist/modules/effect/schema.mjs.map +1 -0
  93. package/dist/modules/effect/workflow.d.mts +77 -0
  94. package/dist/modules/effect/workflow.mjs +2 -0
  95. package/dist/modules/effect/workflow.mjs.map +1 -0
  96. package/dist/node_modules/@standard-schema/spec/dist/index.d.mts +79 -0
  97. package/dist/workflow.d.mts +2 -0
  98. package/dist/workflow.mjs +1 -0
  99. package/docs/architecture.md +3 -3
  100. package/docs/auth.md +71 -0
  101. package/docs/command-context.md +150 -0
  102. package/docs/commands-legacy.md +511 -0
  103. package/docs/commands.md +143 -363
  104. package/docs/conventions.md +15 -5
  105. package/docs/crud.md +4 -0
  106. package/docs/domain-operations.md +615 -0
  107. package/docs/effect.md +395 -0
  108. package/docs/llms.md +69 -45
  109. package/docs/migration.md +1 -1
  110. package/docs/upgrading.md +9 -0
  111. package/package.json +33 -1
  112. package/scripts/convex-smoke-env.mjs +33 -0
  113. package/scripts/smoke-packed-components.mjs +9 -2
  114. package/scripts/smoke-packed-effect.mjs +439 -0
  115. package/scripts/test-packed-effect.mjs +460 -0
  116. package/scripts/test-packed-ids.mjs +1 -0
@@ -0,0 +1,77 @@
1
+ import { ContractInput, ContractOutput, ContractSchema } from "../contracts/contract.mjs";
2
+ import { ContractDecoderError, ContractDecoderRequirements } from "./schema.mjs";
3
+ import { CallbackError, CallbackRequirements, EffectValue } from "./operation.mjs";
4
+ import { IdempotencyInvocation } from "../contracts/idempotency.mjs";
5
+ import { EffectIdempotencyPreparation } from "./idempotency.mjs";
6
+ import { WorkflowBindingError, WorkflowExecution, WorkflowHostState, WorkflowNativeStatus, WorkflowOutcome, WorkflowTransition } from "../contracts/workflow.mjs";
7
+ import { Effect } from "effect";
8
+ //#region src/modules/effect/workflow.d.ts
9
+ type Supported<A> = A | PromiseLike<A> | Effect.Effect<A, unknown, unknown>;
10
+ /** Services exist only inside a new native invocation; ordinary throws/rejections remain defects. */
11
+ declare function effectWorkflowAttempt<Args, Authorize, Execute>(configuration: {
12
+ authorize: (execution: WorkflowExecution<Args>) => Authorize;
13
+ execute: (execution: WorkflowExecution<Args>, authority: EffectValue<Authorize>) => Execute;
14
+ }): (input: WorkflowExecution<Args>) => Effect.Effect<EffectValue<Execute>, WorkflowBindingError | CallbackError<Authorize | Execute>, CallbackRequirements<Authorize | Execute>>;
15
+ /** Native mutation promise rejects outside the child transaction before mapping declared terminal details. */
16
+ declare function effectTerminalWorkflowAttempt<Args, Authorize, const Result extends ContractSchema, const Failure extends ContractSchema, Terminal extends Supported<ContractInput<Failure> | undefined>>(configuration: ({
17
+ kind: 'mutation';
18
+ atomicJournalProof: 'host-verified';
19
+ } | {
20
+ kind: 'action';
21
+ protectedMutation: EffectReceiptProtectedWorkflowMutation<Args, unknown, unknown, unknown>;
22
+ }) & {
23
+ authorize: (execution: WorkflowExecution<Args>) => Authorize;
24
+ invokeMutation: (execution: WorkflowExecution<Args>, authority: EffectValue<Authorize>) => Promise<ContractInput<Result>>;
25
+ terminal: (error: unknown) => Terminal;
26
+ result: Result;
27
+ failure: Failure;
28
+ }): (input: WorkflowExecution<Args>) => Effect.Effect<WorkflowOutcome<ContractInput<Result>, ContractOutput<Failure>>, WorkflowBindingError | CallbackError<Authorize | Terminal> | ContractDecoderError<Result | Failure>, CallbackRequirements<Authorize | Terminal> | ContractDecoderRequirements<Result | Failure>>;
29
+ declare const protectedMutation: unique symbol;
30
+ interface EffectReceiptProtectedWorkflowMutation<Args, Result, Error, Requirements> {
31
+ readonly [protectedMutation]: {
32
+ args: Args;
33
+ result: Result;
34
+ };
35
+ readonly handler: (execution: WorkflowExecution<Args>) => Effect.Effect<Result, Error, Requirements>;
36
+ }
37
+ type Completion<Prepare> = Extract<EffectValue<Prepare>, {
38
+ kind: 'execute';
39
+ }> extends {
40
+ complete: (...arguments_: never[]) => infer Returned;
41
+ } ? Returned : never;
42
+ /** Host runs the handler in the secured inner native mutation transaction, including receipt completion. */
43
+ declare function effectReceiptWorkflowMutation<Args, Authorize, const Replay extends ContractSchema, Prepare extends Supported<EffectIdempotencyPreparation<ContractOutput<Replay>, unknown, unknown>>, Execute extends Supported<ContractOutput<Replay>>>(configuration: {
44
+ authorize: (execution: WorkflowExecution<Args>) => Authorize;
45
+ receipt: (authority: EffectValue<Authorize>) => {
46
+ replayResult: Replay;
47
+ prepare: (invocation: IdempotencyInvocation) => Prepare;
48
+ };
49
+ invocation: (execution: WorkflowExecution<Args>, authority: EffectValue<Authorize>) => IdempotencyInvocation;
50
+ execute: (execution: WorkflowExecution<Args>, authority: EffectValue<Authorize>) => Execute;
51
+ }): EffectReceiptProtectedWorkflowMutation<Args, ContractOutput<Replay>, WorkflowBindingError | CallbackError<Authorize | Prepare | Execute | Completion<Prepare>> | ContractDecoderError<Replay>, CallbackRequirements<Authorize | Prepare | Execute | Completion<Prepare>> | ContractDecoderRequirements<Replay>>;
52
+ type StateResult<Read> = EffectValue<Read> extends WorkflowHostState<infer Result, unknown> ? Result : never;
53
+ type StateFailure<Read> = EffectValue<Read> extends WorkflowHostState<unknown, infer Failure> ? Failure : never;
54
+ /** The host still owns native manager calls and transactional/CAS writes; each method retains only its used channels. */
55
+ declare function effectWorkflowStatusBindings<Authorize, Read extends Supported<WorkflowHostState>, Write, Native extends Supported<WorkflowNativeStatus>, Receipt extends Supported<WorkflowOutcome<StateResult<Read>, StateFailure<Read>> | undefined>, Cancel, Restart>(configuration: {
56
+ authorize: () => Authorize;
57
+ read: () => Read;
58
+ write: (expectedGeneration: number, state: WorkflowHostState<StateResult<Read>, StateFailure<Read>>) => Write;
59
+ nativeStatus: () => Native;
60
+ receiptOutcome: () => Receipt;
61
+ cancelNative: () => Cancel;
62
+ restartNative: () => Restart;
63
+ }): {
64
+ callback(expectedGeneration: number, nativeStatus: WorkflowNativeStatus, outcome?: WorkflowOutcome<StateResult<Read>, StateFailure<Read>>): Effect.Effect<WorkflowTransition<StateResult<Read>, StateFailure<Read>>, WorkflowBindingError | CallbackError<Authorize> | CallbackError<Read> | CallbackError<Write>, CallbackRequirements<Authorize> | CallbackRequirements<Read> | CallbackRequirements<Write>>;
65
+ reconcile(expectedGeneration: number): Effect.Effect<WorkflowTransition<StateResult<Read>, StateFailure<Read>>, WorkflowBindingError | CallbackError<Authorize> | CallbackError<Native> | CallbackError<Read> | CallbackError<Receipt> | CallbackError<Write>, CallbackRequirements<Authorize> | CallbackRequirements<Native> | CallbackRequirements<Read> | CallbackRequirements<Receipt> | CallbackRequirements<Write>>;
66
+ cancel(expectedGeneration: number): Effect.Effect<WorkflowTransition<StateResult<Read>, StateFailure<Read>>, WorkflowBindingError | CallbackError<Authorize> | CallbackError<Cancel> | CallbackError<Read> | CallbackError<Write>, CallbackRequirements<Authorize> | CallbackRequirements<Cancel> | CallbackRequirements<Read> | CallbackRequirements<Write>>;
67
+ restart(expectedGeneration: number): Effect.Effect<{
68
+ kind: 'ignored';
69
+ state: WorkflowHostState<StateResult<Read>, StateFailure<Read>>;
70
+ } | {
71
+ kind: 'applied';
72
+ state: WorkflowHostState<StateResult<Read>, StateFailure<Read>>;
73
+ }, WorkflowBindingError | CallbackError<Authorize> | CallbackError<Read> | CallbackError<Restart> | CallbackError<Write>, CallbackRequirements<Authorize> | CallbackRequirements<Read> | CallbackRequirements<Restart> | CallbackRequirements<Write>>;
74
+ };
75
+ //#endregion
76
+ export { EffectReceiptProtectedWorkflowMutation, effectReceiptWorkflowMutation, effectTerminalWorkflowAttempt, effectWorkflowAttempt, effectWorkflowStatusBindings };
77
+ //# sourceMappingURL=workflow.d.mts.map
@@ -0,0 +1,2 @@
1
+ import{decodeEffectContract as e}from"./schema.mjs";import{normalizeEffect as t}from"./normalize.mjs";import{WorkflowBindingError as n,workflowCancel as r,workflowExecution as i,workflowRestart as a,workflowTransition as o}from"../contracts/workflow.mjs";import{Effect as s}from"effect";function c(e){return n=>s.gen(function*(){let r=yield*f(n),i=yield*t(()=>e.authorize(r));return yield*t(()=>e.execute(r,i))})}function l(n){return r=>s.gen(function*(){let i=yield*f(r),a=yield*t(()=>n.authorize(i)),o=yield*s.tryPromise({try:()=>n.invokeMutation(i,a),catch:e=>e}).pipe(s.map(e=>({kind:`success`,value:e})),s.catch(e=>s.succeed({kind:`rejected`,error:e})));if(o.kind===`rejected`){let r=yield*t(()=>n.terminal(o.error));if(r===void 0)return yield*s.die(o.error);let a=yield*e(n.failure,r);return yield*f({...i,args:a}),{version:1,kind:`failed`,error:a}}return yield*e(n.result,o.value),yield*f({...i,args:o.value}),{version:1,kind:`succeeded`,value:o.value}})}function u(n){return{handler:r=>s.gen(function*(){let i=yield*f(r),a=yield*t(()=>n.authorize(i)),o=n.receipt(a),s=yield*t(()=>o.prepare(n.invocation(i,a)));if(s.kind===`replay`)return yield*e(o.replayResult,s.result);let c=yield*t(()=>n.execute(i,a)),l=s.complete;return yield*t(()=>l(c)),c})}}function d(e){function n(){return t(()=>e.read()).pipe(s.map(e=>e))}function i(r,i,a){return s.gen(function*(){let s=yield*n(),c={generation:r,nativeStatus:i},l=yield*p(()=>o(s,a===void 0?c:{...c,outcome:a}));return l.kind===`applied`&&(yield*t(()=>e.write(r,l.state))),l})}return{callback(n,r,a){return t(()=>e.authorize()).pipe(s.flatMap(()=>i(n,r,a)))},reconcile(n){return s.gen(function*(){return yield*t(()=>e.authorize()),yield*i(n,yield*t(()=>e.nativeStatus()),yield*t(()=>e.receiptOutcome()))})},cancel(i){return s.gen(function*(){yield*t(()=>e.authorize());let a=yield*n(),o=yield*p(()=>r(a,i));return o.kind===`applied`&&a.status===`running`&&(yield*t(()=>e.cancelNative()),yield*t(()=>e.write(i,o.state))),o})},restart(r){return s.gen(function*(){yield*t(()=>e.authorize());let i=yield*n();if(i.generation!==r)return{kind:`ignored`,state:i};let o=yield*p(()=>a(i));return yield*t(()=>e.restartNative()),yield*t(()=>e.write(r,o)),{kind:`applied`,state:o}})}}}function f(e){return p(()=>i(e))}function p(e){return s.try({try:e,catch:e=>{if(e instanceof n)return e;throw e}})}export{u as effectReceiptWorkflowMutation,l as effectTerminalWorkflowAttempt,c as effectWorkflowAttempt,d as effectWorkflowStatusBindings};
2
+ //# sourceMappingURL=workflow.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.mjs","names":[],"sources":["../../../src/modules/effect/workflow.ts"],"sourcesContent":["import { Effect } from 'effect'\nimport type { ContractInput, ContractOutput, ContractSchema } from '../contracts/contract'\nimport {\n\tWorkflowBindingError,\n\tworkflowExecution,\n\tworkflowTransition,\n\tworkflowCancel,\n\tworkflowRestart,\n\ttype WorkflowExecution,\n\ttype WorkflowOutcome,\n\ttype WorkflowHostState,\n\ttype WorkflowNativeStatus,\n} from '../contracts/workflow'\nimport type { IdempotencyInvocation } from '../contracts/idempotency'\nimport type { EffectIdempotencyPreparation } from './idempotency'\nimport type { CallbackError, CallbackRequirements, EffectValue } from './operation'\nimport { normalizeEffect } from './normalize'\nimport {\n\tdecodeEffectContract,\n\ttype ContractDecoderError,\n\ttype ContractDecoderRequirements,\n} from './schema'\n\ntype Supported<A> = A | PromiseLike<A> | Effect.Effect<A, unknown, unknown>\n/** Services exist only inside a new native invocation; ordinary throws/rejections remain defects. */\nexport function effectWorkflowAttempt<Args, Authorize, Execute>(configuration: {\n\tauthorize: (execution: WorkflowExecution<Args>) => Authorize\n\texecute: (execution: WorkflowExecution<Args>, authority: EffectValue<Authorize>) => Execute\n}) {\n\treturn (\n\t\tinput: WorkflowExecution<Args>,\n\t): Effect.Effect<\n\t\tEffectValue<Execute>,\n\t\tWorkflowBindingError | CallbackError<Authorize | Execute>,\n\t\tCallbackRequirements<Authorize | Execute>\n\t> =>\n\t\tEffect.gen(function* () {\n\t\t\tconst execution = yield* descriptor(input)\n\t\t\tconst authority = yield* normalizeEffect(() => configuration.authorize(execution))\n\t\t\treturn yield* normalizeEffect(() => configuration.execute(execution, authority))\n\t\t})\n}\n/** Native mutation promise rejects outside the child transaction before mapping declared terminal details. */\nexport function effectTerminalWorkflowAttempt<\n\tArgs,\n\tAuthorize,\n\tconst Result extends ContractSchema,\n\tconst Failure extends ContractSchema,\n\tTerminal extends Supported<ContractInput<Failure> | undefined>,\n>(\n\tconfiguration: (\n\t\t| { kind: 'mutation'; atomicJournalProof: 'host-verified' }\n\t\t| {\n\t\t\t\tkind: 'action'\n\t\t\t\tprotectedMutation: EffectReceiptProtectedWorkflowMutation<Args, unknown, unknown, unknown>\n\t\t }\n\t) & {\n\t\tauthorize: (execution: WorkflowExecution<Args>) => Authorize\n\t\tinvokeMutation: (\n\t\t\texecution: WorkflowExecution<Args>,\n\t\t\tauthority: EffectValue<Authorize>,\n\t\t) => Promise<ContractInput<Result>>\n\t\t// oxlint-disable-next-line anti-slop/no-unknown-parameters -- A rejected native mutation can throw any value; the host terminal callback classifies it before failure contract validation.\n\t\tterminal: (error: unknown) => Terminal\n\t\tresult: Result\n\t\tfailure: Failure\n\t},\n) {\n\treturn (\n\t\tinput: WorkflowExecution<Args>,\n\t): Effect.Effect<\n\t\tWorkflowOutcome<ContractInput<Result>, ContractOutput<Failure>>,\n\t\t| WorkflowBindingError\n\t\t| CallbackError<Authorize | Terminal>\n\t\t| ContractDecoderError<Result | Failure>,\n\t\tCallbackRequirements<Authorize | Terminal> | ContractDecoderRequirements<Result | Failure>\n\t> =>\n\t\tEffect.gen(function* () {\n\t\t\tconst execution = yield* descriptor(input)\n\t\t\tconst authority = yield* normalizeEffect(() => configuration.authorize(execution))\n\t\t\tconst native = yield* Effect.tryPromise({\n\t\t\t\ttry: () => configuration.invokeMutation(execution, authority),\n\t\t\t\tcatch: (error) => error,\n\t\t\t}).pipe(\n\t\t\t\tEffect.map((value) => ({ kind: 'success' as const, value })),\n\t\t\t\tEffect.catch((error) => Effect.succeed({ kind: 'rejected' as const, error })),\n\t\t\t)\n\t\t\tif (native.kind === 'rejected') {\n\t\t\t\tconst failure = yield* normalizeEffect(() => configuration.terminal(native.error))\n\t\t\t\tif (failure === undefined) return yield* Effect.die(native.error)\n\t\t\t\t// SAFETY: Terminal is constrained to this exact failure wire input or undefined.\n\t\t\t\tconst error = yield* decodeEffectContract(\n\t\t\t\t\tconfiguration.failure,\n\t\t\t\t\tfailure as ContractInput<Failure>,\n\t\t\t\t)\n\t\t\t\tyield* descriptor({ ...execution, args: error })\n\t\t\t\treturn { version: 1 as const, kind: 'failed' as const, error }\n\t\t\t}\n\t\t\tyield* decodeEffectContract(configuration.result, native.value)\n\t\t\tyield* descriptor({ ...execution, args: native.value })\n\t\t\treturn { version: 1 as const, kind: 'succeeded' as const, value: native.value }\n\t\t})\n}\ndeclare const protectedMutation: unique symbol\nexport interface EffectReceiptProtectedWorkflowMutation<Args, Result, Error, Requirements> {\n\treadonly [protectedMutation]: { args: Args; result: Result }\n\treadonly handler: (\n\t\texecution: WorkflowExecution<Args>,\n\t) => Effect.Effect<Result, Error, Requirements>\n}\ntype Completion<Prepare> =\n\tExtract<EffectValue<Prepare>, { kind: 'execute' }> extends {\n\t\tcomplete: (...arguments_: never[]) => infer Returned\n\t}\n\t\t? Returned\n\t\t: never\n/** Host runs the handler in the secured inner native mutation transaction, including receipt completion. */\nexport function effectReceiptWorkflowMutation<\n\tArgs,\n\tAuthorize,\n\tconst Replay extends ContractSchema,\n\tPrepare extends Supported<EffectIdempotencyPreparation<ContractOutput<Replay>, unknown, unknown>>,\n\tExecute extends Supported<ContractOutput<Replay>>,\n>(configuration: {\n\tauthorize: (execution: WorkflowExecution<Args>) => Authorize\n\treceipt: (authority: EffectValue<Authorize>) => {\n\t\treplayResult: Replay\n\t\tprepare: (invocation: IdempotencyInvocation) => Prepare\n\t}\n\tinvocation: (\n\t\texecution: WorkflowExecution<Args>,\n\t\tauthority: EffectValue<Authorize>,\n\t) => IdempotencyInvocation\n\texecute: (execution: WorkflowExecution<Args>, authority: EffectValue<Authorize>) => Execute\n}): EffectReceiptProtectedWorkflowMutation<\n\tArgs,\n\tContractOutput<Replay>,\n\t| WorkflowBindingError\n\t| CallbackError<Authorize | Prepare | Execute | Completion<Prepare>>\n\t| ContractDecoderError<Replay>,\n\t| CallbackRequirements<Authorize | Prepare | Execute | Completion<Prepare>>\n\t| ContractDecoderRequirements<Replay>\n> {\n\tconst handler = (input: WorkflowExecution<Args>) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst execution = yield* descriptor(input)\n\t\t\tconst authority = yield* normalizeEffect(() => configuration.authorize(execution))\n\t\t\tconst adapter = configuration.receipt(authority)\n\t\t\tconst preparation = yield* normalizeEffect(() =>\n\t\t\t\tadapter.prepare(configuration.invocation(execution, authority)),\n\t\t\t)\n\t\t\tif (preparation.kind === 'replay')\n\t\t\t\treturn yield* decodeEffectContract(adapter.replayResult, preparation.result)\n\t\t\tconst value = yield* normalizeEffect(() => configuration.execute(execution, authority))\n\t\t\t// SAFETY: the execute branch came from this exact Prepare value; recover its complete callback rather than the broad constraint.\n\t\t\tconst complete = preparation.complete as (\n\t\t\t\tresult: ContractOutput<Replay>,\n\t\t\t) => Completion<Prepare>\n\t\t\t// SAFETY: Execute's supported success is the exact final replay output contract.\n\t\t\tyield* normalizeEffect(() => complete(value as ContractOutput<Replay>))\n\t\t\t// SAFETY: Execute's supported success is the exact final replay output contract.\n\t\t\treturn value as ContractOutput<Replay>\n\t\t})\n\t// SAFETY: only private receipt evidence is asserted; handler's inferred E/R remain checked against the public return.\n\treturn { handler } as ReceiptEvidence<Args, ContractOutput<Replay>, typeof handler>\n}\ntype ReceiptEvidence<Args, Result, Handler> = {\n\thandler: Handler\n\treadonly [protectedMutation]: { args: Args; result: Result }\n}\ntype StateResult<Read> =\n\tEffectValue<Read> extends WorkflowHostState<infer Result, unknown> ? Result : never\ntype StateFailure<Read> =\n\tEffectValue<Read> extends WorkflowHostState<unknown, infer Failure> ? Failure : never\n/** The host still owns native manager calls and transactional/CAS writes; each method retains only its used channels. */\nexport function effectWorkflowStatusBindings<\n\tAuthorize,\n\tRead extends Supported<WorkflowHostState>,\n\tWrite,\n\tNative extends Supported<WorkflowNativeStatus>,\n\tReceipt extends Supported<WorkflowOutcome<StateResult<Read>, StateFailure<Read>> | undefined>,\n\tCancel,\n\tRestart,\n>(configuration: {\n\tauthorize: () => Authorize\n\tread: () => Read\n\twrite: (\n\t\texpectedGeneration: number,\n\t\tstate: WorkflowHostState<StateResult<Read>, StateFailure<Read>>,\n\t) => Write\n\tnativeStatus: () => Native\n\treceiptOutcome: () => Receipt\n\tcancelNative: () => Cancel\n\trestartNative: () => Restart\n}) {\n\ttype State = WorkflowHostState<StateResult<Read>, StateFailure<Read>>\n\tfunction read() {\n\t\t// SAFETY: Read's supported success is the host state whose outcome types are extracted above.\n\t\treturn normalizeEffect(() => configuration.read()).pipe(Effect.map((state) => state as State))\n\t}\n\tfunction apply(\n\t\texpectedGeneration: number,\n\t\tnativeStatus: WorkflowNativeStatus,\n\t\toutcome?: WorkflowOutcome<StateResult<Read>, StateFailure<Read>>,\n\t) {\n\t\treturn Effect.gen(function* () {\n\t\t\tconst current = yield* read()\n\t\t\tconst update = { generation: expectedGeneration, nativeStatus }\n\t\t\tconst transition = yield* protocol(() =>\n\t\t\t\tworkflowTransition(current, outcome === undefined ? update : { ...update, outcome }),\n\t\t\t)\n\t\t\tif (transition.kind === 'applied')\n\t\t\t\tyield* normalizeEffect(() => configuration.write(expectedGeneration, transition.state))\n\t\t\treturn transition\n\t\t})\n\t}\n\treturn {\n\t\tcallback(\n\t\t\texpectedGeneration: number,\n\t\t\tnativeStatus: WorkflowNativeStatus,\n\t\t\toutcome?: WorkflowOutcome<StateResult<Read>, StateFailure<Read>>,\n\t\t) {\n\t\t\treturn normalizeEffect(() => configuration.authorize()).pipe(\n\t\t\t\tEffect.flatMap(() => apply(expectedGeneration, nativeStatus, outcome)),\n\t\t\t)\n\t\t},\n\t\treconcile(expectedGeneration: number) {\n\t\t\treturn Effect.gen(function* () {\n\t\t\t\tyield* normalizeEffect(() => configuration.authorize())\n\t\t\t\tconst native = yield* normalizeEffect(() => configuration.nativeStatus())\n\t\t\t\tconst outcome = yield* normalizeEffect(() => configuration.receiptOutcome())\n\t\t\t\t// SAFETY: Native and Receipt are constrained to these exact host transport values.\n\t\t\t\treturn yield* apply(\n\t\t\t\t\texpectedGeneration,\n\t\t\t\t\tnative as WorkflowNativeStatus,\n\t\t\t\t\toutcome as WorkflowOutcome<StateResult<Read>, StateFailure<Read>> | undefined,\n\t\t\t\t)\n\t\t\t})\n\t\t},\n\t\tcancel(expectedGeneration: number) {\n\t\t\treturn Effect.gen(function* () {\n\t\t\t\tyield* normalizeEffect(() => configuration.authorize())\n\t\t\t\tconst current = yield* read()\n\t\t\t\tconst transition = yield* protocol(() => workflowCancel(current, expectedGeneration))\n\t\t\t\tif (transition.kind === 'applied' && current.status === 'running') {\n\t\t\t\t\tyield* normalizeEffect(() => configuration.cancelNative())\n\t\t\t\t\tyield* normalizeEffect(() => configuration.write(expectedGeneration, transition.state))\n\t\t\t\t}\n\t\t\t\treturn transition\n\t\t\t})\n\t\t},\n\t\trestart(expectedGeneration: number) {\n\t\t\treturn Effect.gen(function* () {\n\t\t\t\tyield* normalizeEffect(() => configuration.authorize())\n\t\t\t\tconst current = yield* read()\n\t\t\t\tif (current.generation !== expectedGeneration)\n\t\t\t\t\treturn { kind: 'ignored' as const, state: current }\n\t\t\t\tconst state = yield* protocol(() => workflowRestart(current))\n\t\t\t\tyield* normalizeEffect(() => configuration.restartNative())\n\t\t\t\tyield* normalizeEffect(() => configuration.write(expectedGeneration, state))\n\t\t\t\treturn { kind: 'applied' as const, state }\n\t\t\t})\n\t\t},\n\t}\n}\nfunction descriptor<Args>(\n\tvalue: WorkflowExecution<Args>,\n): Effect.Effect<WorkflowExecution<Args>, WorkflowBindingError> {\n\treturn protocol(() => workflowExecution(value))\n}\nfunction protocol<A>(evaluate: () => A): Effect.Effect<A, WorkflowBindingError> {\n\treturn Effect.try({\n\t\ttry: evaluate,\n\t\tcatch: (error) => {\n\t\t\tif (error instanceof WorkflowBindingError) return error\n\t\t\tthrow error\n\t\t},\n\t})\n}\n"],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ //#region node_modules/@standard-schema/spec/dist/index.d.ts
2
+ /** The Standard Typed interface. This is a base type extended by other specs. */
3
+ interface StandardTypedV1<Input = unknown, Output = Input> {
4
+ /** The Standard properties. */
5
+ readonly "~standard": StandardTypedV1.Props<Input, Output>;
6
+ }
7
+ declare namespace StandardTypedV1 {
8
+ /** The Standard Typed properties interface. */
9
+ interface Props<Input = unknown, Output = Input> {
10
+ /** The version number of the standard. */
11
+ readonly version: 1;
12
+ /** The vendor name of the schema library. */
13
+ readonly vendor: string;
14
+ /** Inferred types associated with the schema. */
15
+ readonly types?: Types<Input, Output> | undefined;
16
+ }
17
+ /** The Standard Typed types interface. */
18
+ interface Types<Input = unknown, Output = Input> {
19
+ /** The input type of the schema. */
20
+ readonly input: Input;
21
+ /** The output type of the schema. */
22
+ readonly output: Output;
23
+ }
24
+ /** Infers the input type of a Standard Typed. */
25
+ type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
26
+ /** Infers the output type of a Standard Typed. */
27
+ type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
28
+ }
29
+ /** The Standard Schema interface. */
30
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
31
+ /** The Standard Schema properties. */
32
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
33
+ }
34
+ declare namespace StandardSchemaV1 {
35
+ /** The Standard Schema properties interface. */
36
+ interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
37
+ /** Validates unknown input values. */
38
+ readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
39
+ }
40
+ /** The result interface of the validate function. */
41
+ type Result<Output> = SuccessResult<Output> | FailureResult;
42
+ /** The result interface if validation succeeds. */
43
+ interface SuccessResult<Output> {
44
+ /** The typed output value. */
45
+ readonly value: Output;
46
+ /** A falsy value for `issues` indicates success. */
47
+ readonly issues?: undefined;
48
+ }
49
+ interface Options {
50
+ /** Explicit support for additional vendor-specific parameters, if needed. */
51
+ readonly libraryOptions?: Record<string, unknown> | undefined;
52
+ }
53
+ /** The result interface if validation fails. */
54
+ interface FailureResult {
55
+ /** The issues of failed validation. */
56
+ readonly issues: ReadonlyArray<Issue>;
57
+ }
58
+ /** The issue interface of the failure output. */
59
+ interface Issue {
60
+ /** The error message of the issue. */
61
+ readonly message: string;
62
+ /** The path of the issue, if any. */
63
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
64
+ }
65
+ /** The path segment interface of the issue. */
66
+ interface PathSegment {
67
+ /** The key representing a path segment. */
68
+ readonly key: PropertyKey;
69
+ }
70
+ /** The Standard types interface. */
71
+ interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
72
+ /** Infers the input type of a Standard. */
73
+ type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
74
+ /** Infers the output type of a Standard. */
75
+ type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
76
+ }
77
+ //#endregion
78
+ export { StandardSchemaV1, StandardTypedV1 };
79
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,2 @@
1
+ import { ReceiptProtectedWorkflowMutation, WorkflowBinding, WorkflowBindingError, WorkflowExecution, WorkflowHostState, WorkflowHostUpdate, WorkflowNativeStatus, WorkflowOutcome, WorkflowReceiptAdapter, WorkflowStepArguments, WorkflowStepOptions, WorkflowStepRunner, WorkflowTransition, receiptWorkflowMutation, runWorkflowStep, terminalWorkflowAttempt, workflowAttempt, workflowBinding, workflowCancel, workflowExecution, workflowReceiptInvocation, workflowRestart, workflowStatusBindings, workflowTransition } from "./modules/contracts/workflow.mjs";
2
+ export { type ReceiptProtectedWorkflowMutation, type WorkflowBinding, WorkflowBindingError, type WorkflowExecution, type WorkflowHostState, type WorkflowHostUpdate, type WorkflowNativeStatus, type WorkflowOutcome, type WorkflowReceiptAdapter, type WorkflowStepArguments, type WorkflowStepOptions, type WorkflowStepRunner, type WorkflowTransition, receiptWorkflowMutation, runWorkflowStep, terminalWorkflowAttempt, workflowAttempt, workflowBinding, workflowCancel, workflowExecution, workflowReceiptInvocation, workflowRestart, workflowStatusBindings, workflowTransition };
@@ -0,0 +1 @@
1
+ import{WorkflowBindingError as e,receiptWorkflowMutation as t,runWorkflowStep as n,terminalWorkflowAttempt as r,workflowAttempt as i,workflowBinding as a,workflowCancel as o,workflowExecution as s,workflowReceiptInvocation as c,workflowRestart as l,workflowStatusBindings as u,workflowTransition as d}from"./modules/contracts/workflow.mjs";export{e as WorkflowBindingError,t as receiptWorkflowMutation,n as runWorkflowStep,r as terminalWorkflowAttempt,i as workflowAttempt,a as workflowBinding,o as workflowCancel,s as workflowExecution,c as workflowReceiptInvocation,l as workflowRestart,u as workflowStatusBindings,d as workflowTransition};
@@ -13,7 +13,7 @@ convex/
13
13
  convex.config.ts # component mounting — each app.use(x) exactly once
14
14
  schema.ts # defineSchema(domainTables) — nothing else
15
15
  functions.ts # createAuthFunctions(...) — the ONLY place raw builders appear
16
- foundation.ts # const { Command, Query, observability } = new Foundation(...)
16
+ foundation.ts # const { Command, Query, observability } = createEffectFoundation(...)
17
17
  approvals.ts # const approvals = new Approvals(components.approvals)
18
18
  audit.ts # writeAuditEntry over convex-audit-log + admin readers
19
19
  triggers.ts # the single Triggers registry + helper registrations
@@ -27,7 +27,7 @@ convex/
27
27
  schema.ts # zodTable declaration (shapes only)
28
28
  table.ts # indexes attached to <zodTable>.table
29
29
  constants.ts # finite vocabularies (UPPER_SNAKE readonly tuples)
30
- commands.ts # operation registry + executors (new Command(...))
30
+ commands.ts # Command({ context, operations }) with handlers inside command({...})
31
31
  queries.ts # domain read logic
32
32
  approval.ts # approvals.define(...) workflow definitions
33
33
  *_functions.ts # internal callbacks (systemMutation/systemAction)
@@ -57,7 +57,7 @@ Every kit surface takes its policy as a parameter:
57
57
 
58
58
  - `createAuthFunctions` receives the generated builders, `getAuthUser`,
59
59
  `mapRole`, `verifyMembership`, `triggers`, `errors`.
60
- - `Foundation` receives `classifyError`, `writeAudit`, the `enabled` flag.
60
+ - `createEffectFoundation` receives `classifyError`, `writeAudit`, the `enabled` flag.
61
61
  - `Approvals` receives only its component reference; host data crosses as
62
62
  opaque validated strings.
63
63
 
package/docs/auth.md CHANGED
@@ -64,6 +64,71 @@ export const {
64
64
  Every function in the app is then built from these — raw `query`/`mutation`
65
65
  imports from `_generated/server` appear **only** in this file.
66
66
 
67
+ ## Shared Effect execution after authentication
68
+
69
+ Wrap the constructors returned by `createAuthFunctions` with
70
+ `effectZodApiBuilder` from `cvx-kit/effect`. The original constructor resolves
71
+ authentication, authorization, tenant policy, and the wrapped database before
72
+ the service provider runs. Keep that ordering when adding Effect; use the Zod
73
+ adapter for these custom constructors.
74
+
75
+ This factory accepts the configured `authMutation` from the setup above. The
76
+ actor service comes from trusted `ctx.actor`, with no actor or organization
77
+ argument from callers:
78
+
79
+ <!-- packed-effect-example -->
80
+
81
+ ```ts
82
+ import type { GenericDataModel } from 'convex/server'
83
+ import { createAuthFunctions } from 'cvx-kit/auth'
84
+ import { effectZodApiBuilder } from 'cvx-kit/effect'
85
+ import { Context, Effect } from 'effect'
86
+ import { z } from 'zod'
87
+
88
+ class Actor extends Context.Service<
89
+ Actor,
90
+ { userId: string; organizationId: string; role: string }
91
+ >()('app/Actor') {}
92
+
93
+ export function createEffectAccountApi<DataModel extends GenericDataModel>(
94
+ baseAuthMutation: ReturnType<typeof createAuthFunctions<DataModel>>['authMutation'],
95
+ ) {
96
+ const authMutation = effectZodApiBuilder(baseAuthMutation, {
97
+ services: (ctx) => Context.make(Actor, ctx.actor),
98
+ })
99
+ const currentActor = authMutation({
100
+ args: {},
101
+ returns: z.object({ userId: z.string(), organizationId: z.string() }),
102
+ handler: () =>
103
+ Effect.gen(function* () {
104
+ const actor = yield* Actor
105
+ return { userId: actor.userId, organizationId: actor.organizationId }
106
+ }),
107
+ })
108
+ return { authMutation, currentActor }
109
+ }
110
+ ```
111
+
112
+ Call `createEffectAccountApi(authMutation)` with the constructor exported by
113
+ your `convex/functions.ts`, then export `currentActor` from the account API
114
+ module. The factory preserves the configured auth policy; it does not choose
115
+ an identity provider or membership authority.
116
+
117
+ Declare the provider once per constructor, then reuse the builder across
118
+ endpoints. Database services should capture the provider's `ctx.db`, so
119
+ trigger and RLS enforcement also applies to Effect-driven writes. Services
120
+ are created per invocation; do not cache an authenticated context in a module
121
+ singleton. Query providers receive readers; HTTP integrations belong in
122
+ actions.
123
+
124
+ The adapter runs the composed Effect within an invocation scope and awaits
125
+ finalizers before returning. Domain errors can be mapped with handler pipes
126
+ or shared `mapError`; they remain thrown failures at the native boundary so
127
+ mutation writes roll back. Native argument/result validators remain in place.
128
+ Effect's inferred server-side error and service channels are not a typed
129
+ transport error contract. See [the Effect guide](./effect.md) for complete
130
+ domain registries, error mapping, scoped services, and tracing.
131
+
67
132
  ## The constructor families
68
133
 
69
134
  | Constructor | Visibility | Auth | Extra |
@@ -240,3 +305,9 @@ errors: { throw: (input) => { throw App.errors.from(input) } }
240
305
  3. Reads go through `ctx.include(...)` with an explicit limit.
241
306
  4. Use `ctx.actor` as the actor reference in audits and approvals — never
242
307
  re-derive identity in a handler.
308
+
309
+ Optional request context can be added through the existing Effect API adapter's
310
+ `context` callback. Bind selected registries with `commands.withContext(value)`
311
+ and consume `ctx.commands.exec(name, input)` through the application's existing
312
+ function helper names. Keep dependencies local to each feature; explicit execution
313
+ and input-only command handlers remain supported. See [optional context injection](./command-context.md).
@@ -0,0 +1,150 @@
1
+ # Optional command context injection
2
+
3
+ Bind context once at a function boundary when it makes endpoint code clearer.
4
+ `withContext(value)` accepts whatever the registry's resolver accepts: a number,
5
+ a service bundle, a request context, or another bound command group. It returns
6
+ a small handle whose `exec(name, input)` still produces a lazy Effect. Binding
7
+ neither resolves context nor executes a command. Explicit
8
+ `commands.exec(name, input, context)` remains supported.
9
+
10
+ ## Keep existing function names
11
+
12
+ Add context through `effectZodApiBuilder` around the existing custom constructor.
13
+ Export it under your application's ordinary `authMutation`, `adminQuery`,
14
+ `organizationMutation`, or other function name. These are application choices;
15
+ the library does not require a new helper family. The same option works on
16
+ `effectApiBuilder` for native builders. `services` is optional when your Effects
17
+ need no services.
18
+
19
+ The complete example below uses a custom authenticated mutation to make the
20
+ context flow standalone. A kit application can supply
21
+ `createAuthFunctions(...).authMutation` instead. Its authenticated, secured
22
+ context is constructed before the additions callback runs.
23
+
24
+ <!-- packed-effect-example -->
25
+
26
+ ```ts
27
+ import { Effect } from 'effect'
28
+ import { mutationGeneric } from 'convex/server'
29
+ import { zCustomMutation } from 'convex-helpers/server/zod4'
30
+ import { createEffectFoundation, effectZodApiBuilder } from 'cvx-kit/effect'
31
+ import { z } from 'zod'
32
+
33
+ const foundation = createEffectFoundation({
34
+ observability: {
35
+ enabled: false,
36
+ classifyError: () => ({ outcome: 'failed', errorCode: 'COMMAND_FAILED' }),
37
+ },
38
+ // Standalone demonstration only: production applications provide their audit writer.
39
+ writeAudit: () => undefined,
40
+ })
41
+
42
+ const notifications = foundation.Command({
43
+ context: (dependencies: { format: (message: string) => string }) => dependencies,
44
+ operations: ({ command }) => ({
45
+ format: command({
46
+ input: z.string(), result: z.string(), classification: 'business',
47
+ handler: (message, context) => context.format(message),
48
+ audit: () => null,
49
+ }),
50
+ }),
51
+ })
52
+
53
+ const tasks = foundation.Command({
54
+ context: (dependencies: {
55
+ nextNumber: () => number,
56
+ notifications: ReturnType<typeof notifications.withContext>,
57
+ }) => dependencies,
58
+ operations: ({ command }) => ({
59
+ create: command({
60
+ input: z.string(), result: z.string(), classification: 'business',
61
+ handler: (title, context) => context.notifications.exec(
62
+ 'format', `${context.nextNumber()}:${title}`,
63
+ ),
64
+ audit: () => null,
65
+ }),
66
+ }),
67
+ })
68
+
69
+ const baseAuthMutation = zCustomMutation(mutationGeneric, {
70
+ args: {},
71
+ input: async (ctx) => {
72
+ const identity = await ctx.auth.getUserIdentity()
73
+ if (!identity) throw Error('UNAUTHORIZED')
74
+ return { ctx: { actor: identity.subject }, args: {} }
75
+ },
76
+ })
77
+
78
+ // In a real app, keep this composition in the tasks feature's functions module.
79
+ export const authMutation = effectZodApiBuilder(baseAuthMutation, {
80
+ context: (ctx) => {
81
+ const messages = notifications.withContext({
82
+ format: (message) => `${ctx.actor}:${message}`,
83
+ })
84
+ return {
85
+ tasks: tasks.withContext({ nextNumber: () => 42, notifications: messages }),
86
+ }
87
+ },
88
+ })
89
+
90
+ export const createTask = authMutation({
91
+ args: { title: z.string() },
92
+ returns: z.string(),
93
+ handler: (ctx, input) => ctx.tasks.exec('create', input.title),
94
+ })
95
+
96
+ // An input-only command needs no domain dependencies.
97
+ const echo = foundation.Command({
98
+ context: () => undefined,
99
+ operations: ({ command }) => ({
100
+ echo: command({
101
+ input: z.string(), result: z.string(), classification: 'business',
102
+ handler: (input) => input, audit: () => null,
103
+ }),
104
+ }),
105
+ })
106
+ export const echoEffect = echo.exec('echo', 'hello', undefined)
107
+ export const boundEchoEffect = echo.withContext(undefined).exec('echo', 'hello')
108
+ // A program can compose further work; the API adapter owns execution.
109
+ export const composedEcho = Effect.map(boundEchoEffect, (value) => value.length)
110
+ ```
111
+
112
+ Context additions are optional and cannot replace existing fields, including
113
+ `db`, `auth`, or a custom constructor's authenticated actor. Type checking rejects
114
+ known collisions with declared fields. Runtime checking also protects inherited
115
+ properties (such as `toString`) and keys from broadly typed records or erased
116
+ types; those collisions cannot always be determined from the declared context
117
+ type. Additions can
118
+ return a plain record of own properties, or a Promise or Effect of that record.
119
+ Wrap class instances and services in named properties, such as `{ pricingService }`,
120
+ rather than returning an instance as the additions object. The adapter provides services first, then
121
+ runs initialization and the handler in the same invocation scope. Missing
122
+ initializer services are rejected when registering the endpoint, just like
123
+ missing handler services. Initialization failures use the configured error
124
+ projection and release acquired resources.
125
+
126
+ ## Applications with 25–50 entities
127
+
128
+ Organize by feature or workflow rather than making every entity a property of a
129
+ single global context. A tasks helper can bind tasks and notifications; a billing
130
+ helper can bind billing and pricing. Each registry declares its own narrow
131
+ resolver input. Share stable service implementations at module scope, and build
132
+ request-dependent handles within the context callback.
133
+
134
+ Keep shared identity or tenant information small. Inject a business function or
135
+ service only into the features that use it. Pass bound commands only where a
136
+ workflow calls them; avoid mutual command dependencies. Bindings hold references
137
+ and do not initialize the referenced registry. Large dependency graphs are
138
+ application architecture choices, not a required library container.
139
+
140
+ If a feature needs no injected commands, omit `context` additions. If an endpoint
141
+ benefits from explicit execution, pass its context directly. An input-only
142
+ handler can ignore domain context, but permission and audit policies may still
143
+ need the original trusted invocation context. Preserve those policies when
144
+ choosing the registry resolver and binding input.
145
+
146
+ Nested calls return Effects and compose in the current program. Do not call
147
+ `Effect.runPromise` inside command handlers; the function adapter runs the
148
+ complete program once. Command schema checks, permissions, audit, and completion
149
+ remain active for each nested command. Keep database mutation work in a mutation
150
+ boundary so Convex can roll it back if execution or result validation fails.