cvx-kit 0.1.6 → 0.1.7
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 +23 -0
- package/README.md +10 -1
- package/dist/agent-tools.d.mts +2 -1
- package/dist/agent-tools.mjs +1 -1
- package/dist/agent-tools.mjs.map +1 -1
- package/dist/components/approvals/audit.mjs +1 -1
- package/dist/components/foundation/client.d.mts +8 -7
- 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 +50 -10
- 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/command/lifecycle.d.mts +81 -0
- package/dist/components/foundation/modules/command/lifecycle.mjs +2 -0
- package/dist/components/foundation/modules/command/lifecycle.mjs.map +1 -0
- package/dist/components/foundation/modules/observability/observability.d.mts +8 -0
- 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/operation.d.mts +6 -6
- package/dist/components/foundation/operation.mjs.map +1 -1
- package/dist/contracts.d.mts +4 -0
- package/dist/contracts.mjs +1 -0
- package/dist/crud.d.mts +2 -2
- package/dist/crud.mjs.map +1 -1
- package/dist/effect.d.mts +12 -0
- package/dist/effect.mjs +1 -0
- package/dist/errors.d.mts +2 -1
- package/dist/errors.mjs +1 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/idempotency.d.mts +2 -0
- package/dist/idempotency.mjs +1 -0
- package/dist/modules/contracts/contract.d.mts +70 -0
- package/dist/modules/contracts/contract.mjs +2 -0
- package/dist/modules/contracts/contract.mjs.map +1 -0
- package/dist/modules/contracts/convex.d.mts +22 -0
- package/dist/modules/contracts/convex.mjs +2 -0
- package/dist/modules/contracts/convex.mjs.map +1 -0
- package/dist/modules/contracts/errors.d.mts +44 -0
- package/dist/modules/contracts/errors.mjs +2 -0
- package/dist/modules/contracts/errors.mjs.map +1 -0
- package/dist/modules/contracts/exposure.d.mts +105 -0
- package/dist/modules/contracts/exposure.mjs +2 -0
- package/dist/modules/contracts/exposure.mjs.map +1 -0
- package/dist/modules/contracts/idempotency.d.mts +87 -0
- package/dist/modules/contracts/idempotency.mjs +2 -0
- package/dist/modules/contracts/idempotency.mjs.map +1 -0
- package/dist/modules/contracts/standard.d.mts +16 -0
- package/dist/modules/contracts/standard.mjs +2 -0
- package/dist/modules/contracts/standard.mjs.map +1 -0
- package/dist/modules/contracts/workflow.d.mts +167 -0
- package/dist/modules/contracts/workflow.mjs +2 -0
- package/dist/modules/contracts/workflow.mjs.map +1 -0
- package/dist/modules/effect/agent-tools.d.mts +2 -0
- package/dist/modules/effect/agent-tools.mjs +1 -0
- package/dist/modules/effect/api-runtime.d.mts +28 -0
- package/dist/modules/effect/api-runtime.mjs +2 -0
- package/dist/modules/effect/api-runtime.mjs.map +1 -0
- package/dist/modules/effect/api-zod.d.mts +35 -0
- package/dist/modules/effect/api-zod.mjs +2 -0
- package/dist/modules/effect/api-zod.mjs.map +1 -0
- package/dist/modules/effect/api.d.mts +21 -0
- package/dist/modules/effect/api.mjs +2 -0
- package/dist/modules/effect/api.mjs.map +1 -0
- package/dist/modules/effect/client.d.mts +11 -0
- package/dist/modules/effect/client.mjs +1 -0
- package/dist/modules/effect/command.d.mts +59 -0
- package/dist/modules/effect/command.mjs +2 -0
- package/dist/modules/effect/command.mjs.map +1 -0
- package/dist/modules/effect/crud.d.mts +785 -0
- package/dist/modules/effect/crud.mjs +2 -0
- package/dist/modules/effect/crud.mjs.map +1 -0
- package/dist/modules/effect/errors.d.mts +8 -0
- package/dist/modules/effect/errors.mjs +2 -0
- package/dist/modules/effect/errors.mjs.map +1 -0
- package/dist/modules/effect/foundation.d.mts +21 -0
- package/dist/modules/effect/foundation.mjs +2 -0
- package/dist/modules/effect/foundation.mjs.map +1 -0
- package/dist/modules/effect/idempotency.d.mts +31 -0
- package/dist/modules/effect/idempotency.mjs +2 -0
- package/dist/modules/effect/idempotency.mjs.map +1 -0
- package/dist/modules/effect/normalize.d.mts +10 -0
- package/dist/modules/effect/normalize.mjs +2 -0
- package/dist/modules/effect/normalize.mjs.map +1 -0
- package/dist/modules/effect/operation.d.mts +141 -0
- package/dist/modules/effect/operation.mjs +2 -0
- package/dist/modules/effect/operation.mjs.map +1 -0
- package/dist/modules/effect/query.d.mts +65 -0
- package/dist/modules/effect/query.mjs +2 -0
- package/dist/modules/effect/query.mjs.map +1 -0
- package/dist/modules/effect/schema.d.mts +33 -0
- package/dist/modules/effect/schema.mjs +2 -0
- package/dist/modules/effect/schema.mjs.map +1 -0
- package/dist/modules/effect/workflow.d.mts +77 -0
- package/dist/modules/effect/workflow.mjs +2 -0
- package/dist/modules/effect/workflow.mjs.map +1 -0
- package/dist/node_modules/@standard-schema/spec/dist/index.d.mts +79 -0
- package/dist/workflow.d.mts +2 -0
- package/dist/workflow.mjs +1 -0
- package/docs/auth.md +65 -0
- package/docs/commands.md +118 -0
- package/docs/domain-operations.md +615 -0
- package/docs/effect.md +384 -0
- package/package.json +33 -1
- package/scripts/convex-smoke-env.mjs +33 -0
- package/scripts/smoke-packed-components.mjs +9 -2
- package/scripts/smoke-packed-effect.mjs +439 -0
- package/scripts/test-packed-effect.mjs +457 -0
- 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};
|
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
|
+
authMutation: ReturnType<typeof createAuthFunctions<DataModel>>['authMutation'],
|
|
95
|
+
) {
|
|
96
|
+
const effectAuthMutation = effectZodApiBuilder(authMutation, {
|
|
97
|
+
services: (ctx) => Context.make(Actor, ctx.actor),
|
|
98
|
+
})
|
|
99
|
+
const currentActor = effectAuthMutation({
|
|
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 { effectAuthMutation, 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 |
|
package/docs/commands.md
CHANGED
|
@@ -37,6 +37,124 @@ export const { Command, Query, observability } = new Foundation(components.found
|
|
|
37
37
|
Host code consumes the Foundation **only through this facade** — never deep
|
|
38
38
|
imports of the component's internal modules.
|
|
39
39
|
|
|
40
|
+
For new domains, [the optional Effect facade](./effect.md) offers cohesive
|
|
41
|
+
command/query entries with handlers in the declaration and injectable context.
|
|
42
|
+
The complete guide covers ordinary, `Effect.gen`, and `Effect.fn` handlers,
|
|
43
|
+
nested domain integrations, and shared API execution.
|
|
44
|
+
|
|
45
|
+
## Incremental migration to Effect
|
|
46
|
+
|
|
47
|
+
The existing Foundation keeps separate operation and handler declarations,
|
|
48
|
+
context-first handler arguments, and Promise executors. No existing consumer
|
|
49
|
+
needs to change when another domain adopts `cvx-kit/effect`.
|
|
50
|
+
|
|
51
|
+
This legacy factory accepts the mounted Foundation component and defines all
|
|
52
|
+
of its domain dependencies through the host context:
|
|
53
|
+
|
|
54
|
+
<!-- packed-effect-example -->
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { Foundation, type AuditEntryInput } from 'cvx-kit/components/foundation'
|
|
58
|
+
import { z } from 'zod'
|
|
59
|
+
|
|
60
|
+
type Host = {
|
|
61
|
+
actorId: string
|
|
62
|
+
rename: (id: string, title: string) => Promise<void>
|
|
63
|
+
appendAudit: (entry: AuditEntryInput) => Promise<void>
|
|
64
|
+
}
|
|
65
|
+
const input = z.object({ id: z.string(), title: z.string() }).strict()
|
|
66
|
+
const result = z.object({ ok: z.literal(true) }).strict()
|
|
67
|
+
|
|
68
|
+
export function createLegacyRename(component: ConstructorParameters<typeof Foundation>[0]) {
|
|
69
|
+
const { Command } = new Foundation(component, {
|
|
70
|
+
observability: {
|
|
71
|
+
enabled: false,
|
|
72
|
+
classifyError: () => ({ outcome: 'failed', errorCode: 'RENAME_FAILED' }),
|
|
73
|
+
writeAudit: (host: Host, entry) => host.appendAudit(entry),
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
const typed = Command.withContext<Host>()
|
|
77
|
+
const operations = {
|
|
78
|
+
rename: typed.operation({
|
|
79
|
+
command: input,
|
|
80
|
+
result,
|
|
81
|
+
classification: 'business',
|
|
82
|
+
audit: ({ command }, host) => ({
|
|
83
|
+
operation: 'documents.rename',
|
|
84
|
+
actorId: host.actorId,
|
|
85
|
+
aggregate: { type: 'document', id: command.id },
|
|
86
|
+
}),
|
|
87
|
+
}),
|
|
88
|
+
}
|
|
89
|
+
const commands = new Command<Host, typeof operations>(operations)
|
|
90
|
+
return commands.exec({
|
|
91
|
+
operation: 'rename',
|
|
92
|
+
handler: async (host, command) => {
|
|
93
|
+
await host.rename(command.id, command.title)
|
|
94
|
+
return { ok: true }
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The replacement keeps the handler beside its schemas and audit policy. It
|
|
101
|
+
uses input-first, context-second callbacks and returns a lazy Effect from
|
|
102
|
+
`exec(operation, input, host)`, including when the handler is ordinary async:
|
|
103
|
+
|
|
104
|
+
<!-- packed-effect-example -->
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { type AuditEntryInput } from 'cvx-kit/components/foundation'
|
|
108
|
+
import { createEffectFoundation } from 'cvx-kit/effect'
|
|
109
|
+
import { z } from 'zod'
|
|
110
|
+
|
|
111
|
+
type Host = {
|
|
112
|
+
actorId: string
|
|
113
|
+
rename: (id: string, title: string) => Promise<void>
|
|
114
|
+
appendAudit: (entry: AuditEntryInput) => Promise<void>
|
|
115
|
+
}
|
|
116
|
+
const input = z.object({ id: z.string(), title: z.string() }).strict()
|
|
117
|
+
const result = z.object({ ok: z.literal(true) }).strict()
|
|
118
|
+
const foundation = createEffectFoundation({
|
|
119
|
+
observability: {
|
|
120
|
+
enabled: false,
|
|
121
|
+
classifyError: () => ({ outcome: 'failed', errorCode: 'RENAME_FAILED' }),
|
|
122
|
+
},
|
|
123
|
+
writeAudit: (host: Host, entry) => host.appendAudit(entry),
|
|
124
|
+
})
|
|
125
|
+
export const commands = foundation.Command({
|
|
126
|
+
context: (host: Host) => host,
|
|
127
|
+
operations: ({ command }) => ({
|
|
128
|
+
rename: command({
|
|
129
|
+
input,
|
|
130
|
+
result,
|
|
131
|
+
classification: 'business',
|
|
132
|
+
handler: async (input, context) => {
|
|
133
|
+
await context.rename(input.id, input.title)
|
|
134
|
+
return { ok: true as const }
|
|
135
|
+
},
|
|
136
|
+
audit: ({ command }, context) => ({
|
|
137
|
+
operation: 'documents.rename',
|
|
138
|
+
actorId: context.actorId,
|
|
139
|
+
aggregate: { type: 'document', id: command.id },
|
|
140
|
+
}),
|
|
141
|
+
}),
|
|
142
|
+
}),
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Pass the resulting Effect to a handler registered through a shared
|
|
147
|
+
`effectApiBuilder` or `effectZodApiBuilder`; see [the complete domain-to-API
|
|
148
|
+
example](./effect.md). Domain composition does not start a separate runner.
|
|
149
|
+
Ordinary throws and rejected Promises become defects; use `Effect.fail` or
|
|
150
|
+
`Effect.tryPromise` for an inferred typed error channel. The native Convex
|
|
151
|
+
reference retains argument/result types, but does not transport the server's
|
|
152
|
+
Effect error and service types.
|
|
153
|
+
|
|
154
|
+
Both facades share command lifecycle ordering, validation, audit, and replay
|
|
155
|
+
rules. Commands that write remain inside a Convex mutation, and failures must
|
|
156
|
+
escape that mutation to roll its writes back.
|
|
157
|
+
|
|
40
158
|
## Defining commands — per domain
|
|
41
159
|
|
|
42
160
|
Each domain declares a frozen registry of operations and derives typed
|