@affordance/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/dist/engine/compute.d.ts +2 -2
- package/dist/engine/compute.js.map +1 -1
- package/dist/engine/engine.d.ts +14 -22
- package/dist/engine/engine.js +35 -17
- package/dist/engine/engine.js.map +1 -1
- package/dist/errors.d.ts +3 -9
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -1
- package/dist/execution/delta.d.ts +1 -1
- package/dist/execution/delta.js +1 -1
- package/dist/execution/delta.js.map +1 -1
- package/dist/execution/execute.d.ts +19 -18
- package/dist/execution/execute.js +22 -25
- package/dist/execution/execute.js.map +1 -1
- package/dist/execution/index.d.ts +1 -3
- package/dist/execution/index.js +1 -3
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/journal.d.ts +4 -12
- package/dist/execution/journal.js +20 -93
- package/dist/execution/journal.js.map +1 -1
- package/dist/execution/port.d.ts +15 -44
- package/dist/execution/port.js +1 -100
- package/dist/execution/port.js.map +1 -1
- package/dist/execution/replay.d.ts +1 -1
- package/dist/execution/replay.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/ingestion/correlation.d.ts +0 -32
- package/dist/ingestion/correlation.js +1 -77
- package/dist/ingestion/correlation.js.map +1 -1
- package/dist/ingestion/index.d.ts +1 -2
- package/dist/ingestion/index.js +1 -2
- package/dist/ingestion/index.js.map +1 -1
- package/dist/ingestion/ingest.d.ts +10 -16
- package/dist/ingestion/ingest.js +12 -100
- package/dist/ingestion/ingest.js.map +1 -1
- package/dist/migration/migrate.d.ts +6 -7
- package/dist/migration/migrate.js +11 -40
- package/dist/migration/migrate.js.map +1 -1
- package/dist/model/casetype.d.ts +8 -8
- package/dist/model/casetype.js.map +1 -1
- package/dist/model/handler.d.ts +21 -15
- package/dist/model/handler.js.map +1 -1
- package/dist/model/index.d.ts +3 -3
- package/dist/model/index.js +1 -1
- package/dist/model/index.js.map +1 -1
- package/dist/model/step.d.ts +18 -13
- package/dist/model/step.js +2 -1
- package/dist/model/step.js.map +1 -1
- package/dist/model/target.d.ts +12 -15
- package/dist/model/target.js +2 -5
- package/dist/model/target.js.map +1 -1
- package/dist/storage.d.ts +93 -0
- package/dist/storage.js +4 -0
- package/dist/storage.js.map +1 -0
- package/dist/store/ids.d.ts +1 -2
- package/dist/store/ids.js +1 -2
- package/dist/store/ids.js.map +1 -1
- package/dist/store/index.d.ts +3 -8
- package/dist/store/index.js +2 -6
- package/dist/store/index.js.map +1 -1
- package/dist/store/resolve.d.ts +7 -14
- package/dist/store/resolve.js +4 -11
- package/dist/store/resolve.js.map +1 -1
- package/dist/store/store.d.ts +4 -41
- package/dist/store/store.js +1 -95
- package/dist/store/store.js.map +1 -1
- package/package.json +8 -8
- package/dist/execution/transaction.d.ts +0 -24
- package/dist/execution/transaction.js +0 -49
- package/dist/execution/transaction.js.map +0 -1
- package/dist/store/bootstrap.d.ts +0 -57
- package/dist/store/bootstrap.js +0 -268
- package/dist/store/bootstrap.js.map +0 -1
- package/dist/store/queryable.d.ts +0 -60
- package/dist/store/queryable.js +0 -7
- package/dist/store/queryable.js.map +0 -1
- package/dist/store/sql.d.ts +0 -26
- package/dist/store/sql.js +0 -21
- package/dist/store/sql.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"casetype.js","sourceRoot":"","sources":["../../src/model/casetype.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"casetype.js","sourceRoot":"","sources":["../../src/model/casetype.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAgErE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAKtB,OAA4C,EACJ,EAAE;IAC1C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,OAAO,CAAA;IACtC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACnD,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAA;IAClE,CAAC;IACD,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,SAAS,CACjB,aAAa,IAAI,wDAAwD,CAC1E,CAAA;IACH,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,SAAS,CACjB,aAAa,IAAI,oDAAoD,CACtE,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,EAGnB,CAAA;IACH,KAAK,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,uBAAuB,CAAC,UAAU,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,SAAS,CACjB,aAAa,IAAI,4CAA4C,CAC9D,CAAA;QACH,CAAC;QACD,IAAI,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,SAAS,CACjB,aAAa,IAAI,2BAA2B,UAAU,CAAC,IAAI,GAAG,CAC/D,CAAA;QACH,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACzC,CAAC;IAED,OAAO;QACL,IAAI;QACJ,KAAK;QACL,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC;QACjB,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;KAC5C,CAAA;AACH,CAAC,CAAA","sourcesContent":["/**\n * Case type definitions.\n *\n * A **case type** declares exactly two things: a typed state schema and a set\n * of steps. Nothing else — no flow, stages, graph, or completion test; a\n * case's only \"position\" is its state.\n *\n * There is deliberately no completion predicate. Whether a\n * matter is finished is a fact about the matter, and outcomes are state: a\n * closed purchase has `closedAt`. The framework's own answer to \"is there\n * anything to do here\" is the affordance listing being empty for the actor\n * asking — no declaration required, and correct again the moment state moves.\n */\n\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { StepDefinition } from './step.js'\nimport { isStandardSchema, looksLikeStepDefinition } from './step.js'\n\n/** Options for {@link caseType}. */\nexport interface CaseTypeOptions<\n S extends StandardSchemaV1,\n TActor = unknown,\n TCommit = unknown,\n> {\n /**\n * The case type's name — what the persisted case records. Only the name is\n * stored: the code definition floats, meaning existing cases always run\n * against the latest deployed definition.\n */\n readonly name: string\n /** The Case State schema (any Standard Schema — zod v4 qualifies). */\n readonly state: S\n /** The case type's steps, in declaration order (which affordance listings preserve). */\n readonly steps: readonly StepDefinition<\n StandardSchemaV1.InferOutput<S>,\n TActor,\n TCommit\n >[]\n}\n\n/** A validated case type definition — the unit the engine registers. */\nexport interface CaseTypeDefinition<\n S extends StandardSchemaV1 = StandardSchemaV1,\n TActor = unknown,\n TCommit = unknown,\n> {\n readonly name: string\n readonly state: S\n readonly steps: readonly StepDefinition<\n StandardSchemaV1.InferOutput<S>,\n TActor,\n TCommit\n >[]\n /** Look up a step by name; `undefined` when the case type declares no such step. */\n readonly getStep: (\n name: string,\n ) =>\n | StepDefinition<StandardSchemaV1.InferOutput<S>, TActor, TCommit>\n | undefined\n}\n\n/**\n * A case type with its schema and actor generics erased — what heterogeneous\n * registries (the engine's `caseTypes`) hold. `any` is deliberate: it is the\n * only way a `CaseTypeDefinition<PurchaseSchema, Ops>` and a\n * `CaseTypeDefinition<LoanSchema, Servicer>` fit one list; every use is\n * re-anchored by the state schema validation the engine performs on load.\n */\n// deliberate `any`: the existential form of CaseTypeDefinition — \"some case\n// type\", its schema deliberately unstated (see doc above). The schema slot\n// must be bare `any` — CaseTypeDefinition is invariant in S, meaning no wider\n// or narrower schema type is assignable (S feeds both the state property and\n// condition/handler parameters), so any narrower existential would reject\n// every concrete schema.\nexport type AnyCaseType<TCommit = unknown> = CaseTypeDefinition<\n any,\n any,\n TCommit\n>\n\n/**\n * Define a case type. Validates loudly at construction time:\n *\n * - `name` must be a non-empty string and `state` a Standard Schema\n * - every element of `steps` must be a `step(...)` definition\n * - step names must be unique within the case type — a duplicate would make\n * affordance identity (step × scope key) ambiguous\n */\nexport const caseType = <\n S extends StandardSchemaV1,\n TActor = unknown,\n TCommit = unknown,\n>(\n options: CaseTypeOptions<S, TActor, TCommit>,\n): CaseTypeDefinition<S, TActor, TCommit> => {\n const { name, state, steps } = options\n if (typeof name !== 'string' || name.trim() === '') {\n throw new TypeError('caseType: name must be a non-empty string')\n }\n if (!isStandardSchema(state)) {\n throw new TypeError(\n `caseType '${name}': state must be a Standard Schema (e.g. a zod schema)`,\n )\n }\n if (!Array.isArray(steps)) {\n throw new TypeError(\n `caseType '${name}': steps must be an array of step(...) definitions`,\n )\n }\n\n const byName = new Map<\n string,\n StepDefinition<StandardSchemaV1.InferOutput<S>, TActor, TCommit>\n >()\n for (const definition of steps) {\n if (!looksLikeStepDefinition(definition)) {\n throw new TypeError(\n `caseType '${name}': every step must be built with step(...)`,\n )\n }\n if (byName.has(definition.name)) {\n throw new TypeError(\n `caseType '${name}': duplicate step name '${definition.name}'`,\n )\n }\n byName.set(definition.name, definition)\n }\n\n return {\n name,\n state,\n steps: [...steps],\n getStep: (stepName) => byName.get(stepName),\n }\n}\n"]}
|
package/dist/model/handler.d.ts
CHANGED
|
@@ -12,12 +12,11 @@
|
|
|
12
12
|
* external effect must be deduplicated on `ctx.executionId`. Nothing in the
|
|
13
13
|
* model layer ever invokes a handler — `packages/core/src/execution` does.
|
|
14
14
|
*/
|
|
15
|
-
import type {
|
|
15
|
+
import type { CorrelationRegistration } from '../ingestion/correlation.js';
|
|
16
16
|
/**
|
|
17
17
|
* A write to run inside the framework's **commit** transaction, registered
|
|
18
|
-
* from a handler via `ctx.onCommit`. It receives
|
|
19
|
-
*
|
|
20
|
-
* atomically with the case row and the journal entry. This is the
|
|
18
|
+
* from a handler via `ctx.onCommit`. It receives an adapter-defined transaction or repository context. Those writes
|
|
19
|
+
* commit atomically with Case State and the journal entry. This is the
|
|
21
20
|
* shared-transaction seam — the one point where app writes join the
|
|
22
21
|
* framework's transaction — and it avoids holding a transaction open across
|
|
23
22
|
* the handler's external calls.
|
|
@@ -25,7 +24,7 @@ import type { Queryable } from '../store/index.js';
|
|
|
25
24
|
* A callback that throws aborts the whole commit: nothing is written, the
|
|
26
25
|
* attempt is journaled as failed, and the retry policy applies.
|
|
27
26
|
*/
|
|
28
|
-
export type CommitWrite = (tx:
|
|
27
|
+
export type CommitWrite<TCommit = unknown> = (tx: TCommit) => Promise<void>;
|
|
29
28
|
/**
|
|
30
29
|
* What a handler registers when it hands work to an external system
|
|
31
30
|
*: "this envelope id is how the answer will come back". The case
|
|
@@ -51,7 +50,7 @@ export interface CorrelationRequest {
|
|
|
51
50
|
* external effect must be deduplicated on it. `attempt` /
|
|
52
51
|
* `maxAttempts` let a handler tell a first try from a retry.
|
|
53
52
|
*/
|
|
54
|
-
export interface HandlerContext<TActor = unknown, TInput = undefined> {
|
|
53
|
+
export interface HandlerContext<TActor = unknown, TInput = undefined, TCommit = unknown> {
|
|
55
54
|
/** Unique id of this Execution — the handler's idempotency key. */
|
|
56
55
|
readonly executionId: string;
|
|
57
56
|
/** The case this Execution runs on. */
|
|
@@ -68,21 +67,20 @@ export interface HandlerContext<TActor = unknown, TInput = undefined> {
|
|
|
68
67
|
/** Total attempts this Execution is allowed, from the step's retry policy. */
|
|
69
68
|
readonly maxAttempts: number;
|
|
70
69
|
/**
|
|
71
|
-
* Register an
|
|
70
|
+
* Register an application write to run inside the framework's commit
|
|
72
71
|
* transaction, so it commits atomically with the new Case State.
|
|
73
72
|
* Callbacks run in registration order; registrations from a failed attempt
|
|
74
73
|
* are discarded before the next attempt.
|
|
75
74
|
*/
|
|
76
|
-
onCommit(write: CommitWrite): void;
|
|
75
|
+
onCommit(write: CommitWrite<TCommit>): void;
|
|
77
76
|
/**
|
|
78
77
|
* Register an external identifier against this case (and, on a scoped
|
|
79
78
|
* step, this element) so the eventual webhook can be routed back.
|
|
80
79
|
* Correlation is one half of integrating an external system; Ingestion —
|
|
81
80
|
* executing the routed event as an ordinary step — is the other.
|
|
82
81
|
*
|
|
83
|
-
* Written
|
|
84
|
-
*
|
|
85
|
-
* because the sending and the mapping are the same commit.
|
|
82
|
+
* Written atomically with Case State and other commit effects. External
|
|
83
|
+
* provider calls are outside this atomic operation and must be idempotent.
|
|
86
84
|
*/
|
|
87
85
|
correlate(request: CorrelationRequest): void;
|
|
88
86
|
/**
|
|
@@ -97,7 +95,7 @@ export interface HandlerContext<TActor = unknown, TInput = undefined> {
|
|
|
97
95
|
reopen(): void;
|
|
98
96
|
}
|
|
99
97
|
/** The context a scoped step's handler receives: the base context plus the scope binding. */
|
|
100
|
-
export interface ScopedHandlerContext<TElement, TActor = unknown, TInput = undefined> extends HandlerContext<TActor, TInput> {
|
|
98
|
+
export interface ScopedHandlerContext<TElement, TActor = unknown, TInput = undefined, TCommit = unknown> extends HandlerContext<TActor, TInput, TCommit> {
|
|
101
99
|
/** The bound scope element the Execution is about (e.g. one buyer). */
|
|
102
100
|
readonly scope: TElement;
|
|
103
101
|
/** The element's scope key — half of the affordance's identity. */
|
|
@@ -107,9 +105,9 @@ export interface ScopedHandlerContext<TElement, TActor = unknown, TInput = undef
|
|
|
107
105
|
* An unscoped step's handler: async, receives the current Case State and the
|
|
108
106
|
* execution context, and returns the **next** Case State document.
|
|
109
107
|
*/
|
|
110
|
-
export type StepHandler<TState, TActor = unknown, TInput = undefined> = (state: TState, ctx: HandlerContext<TActor, TInput>) => Promise<TState>;
|
|
108
|
+
export type StepHandler<TState, TActor = unknown, TInput = undefined, TCommit = unknown> = (state: TState, ctx: HandlerContext<TActor, TInput, TCommit>) => Promise<TState>;
|
|
111
109
|
/** A scoped step's handler: as {@link StepHandler}, with the scope binding on ctx. */
|
|
112
|
-
export type ScopedStepHandler<TState, TElement, TActor = unknown, TInput = undefined> = (state: TState, ctx: ScopedHandlerContext<TElement, TActor, TInput>) => Promise<TState>;
|
|
110
|
+
export type ScopedStepHandler<TState, TElement, TActor = unknown, TInput = undefined, TCommit = unknown> = (state: TState, ctx: ScopedHandlerContext<TElement, TActor, TInput, TCommit>) => Promise<TState>;
|
|
113
111
|
/**
|
|
114
112
|
* A handler as held on a normalized {@link StepDefinition}: the authoring
|
|
115
113
|
* generics (input, scope element) erased. The execution lifecycle invokes
|
|
@@ -117,4 +115,12 @@ export type ScopedStepHandler<TState, TElement, TActor = unknown, TInput = undef
|
|
|
117
115
|
* (the model layer guarantees input was validated and, for scoped steps, a
|
|
118
116
|
* scope is bound).
|
|
119
117
|
*/
|
|
120
|
-
export type ErasedStepHandler<TState, TActor = unknown> = (state: TState, ctx: HandlerContext<TActor, unknown> | ScopedHandlerContext<unknown, TActor, unknown>) => Promise<TState>;
|
|
118
|
+
export type ErasedStepHandler<TState, TActor = unknown, TCommit = unknown> = (state: TState, ctx: HandlerContext<TActor, unknown, TCommit> | ScopedHandlerContext<unknown, TActor, unknown, TCommit>) => Promise<TState>;
|
|
119
|
+
/** Registrations share one ordered queue and are discarded on a failed attempt. */
|
|
120
|
+
export type CommitEffect<TCommit = unknown> = {
|
|
121
|
+
readonly kind: 'write';
|
|
122
|
+
readonly write: CommitWrite<TCommit>;
|
|
123
|
+
} | {
|
|
124
|
+
readonly kind: 'correlation';
|
|
125
|
+
readonly registration: CorrelationRegistration;
|
|
126
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/model/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG","sourcesContent":["/**\n * Handler types.\n *\n * A **handler** is a step's effect function — the only thing that mutates\n * Case State (CONTEXT.md). It receives the current Case State and an\n * execution context, and returns the **next** Case State document; the\n * execution lifecycle derives the journaled delta from\n * (previous, next).\n *\n * Handlers are short-lived, at-least-once and idempotent: the\n * same handler may run more than once for the same Execution, so every\n * external effect must be deduplicated on `ctx.executionId`. Nothing in the\n * model layer ever invokes a handler — `packages/core/src/execution` does.\n */\n\nimport type {
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/model/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG","sourcesContent":["/**\n * Handler types.\n *\n * A **handler** is a step's effect function — the only thing that mutates\n * Case State (CONTEXT.md). It receives the current Case State and an\n * execution context, and returns the **next** Case State document; the\n * execution lifecycle derives the journaled delta from\n * (previous, next).\n *\n * Handlers are short-lived, at-least-once and idempotent: the\n * same handler may run more than once for the same Execution, so every\n * external effect must be deduplicated on `ctx.executionId`. Nothing in the\n * model layer ever invokes a handler — `packages/core/src/execution` does.\n */\n\nimport type { CorrelationRegistration } from '../ingestion/correlation.js'\n\n/**\n * A write to run inside the framework's **commit** transaction, registered\n * from a handler via `ctx.onCommit`. It receives an adapter-defined transaction or repository context. Those writes\n * commit atomically with Case State and the journal entry. This is the\n * shared-transaction seam — the one point where app writes join the\n * framework's transaction — and it avoids holding a transaction open across\n * the handler's external calls.\n *\n * A callback that throws aborts the whole commit: nothing is written, the\n * attempt is journaled as failed, and the retry policy applies.\n */\nexport type CommitWrite<TCommit = unknown> = (tx: TCommit) => Promise<void>\n\n/**\n * What a handler registers when it hands work to an external system\n *: \"this envelope id is how the answer will come back\". The case\n * is implicit — it is the one the handler is running on — and for a scoped\n * step the scope element is too.\n */\nexport interface CorrelationRequest {\n /** The external system, as the app names it: `'esign'`, `'verify'`, `'escrow'`. */\n readonly system: string\n /** The identifier that system will quote back. */\n readonly externalId: string\n /** Defaults to the Execution's own scope key on a scoped step; pass `null` for case-level. */\n readonly scopeKey?: string | null\n /** The step an event on this identifier should execute — usually the materializing step. */\n readonly step?: string | null\n /** Anything the app wants to keep alongside the mapping. */\n readonly metadata?: unknown\n}\n\n/**\n * The context a handler receives alongside Case State.\n *\n * `executionId` is the idempotency key: handlers run at-least-once, so any\n * external effect must be deduplicated on it. `attempt` /\n * `maxAttempts` let a handler tell a first try from a retry.\n */\nexport interface HandlerContext<\n TActor = unknown,\n TInput = undefined,\n TCommit = unknown,\n> {\n /** Unique id of this Execution — the handler's idempotency key. */\n readonly executionId: string\n /** The case this Execution runs on. */\n readonly caseId: string\n /** The Actor the step is being executed as (app-defined shape). */\n readonly actor: TActor\n /**\n * The step's input, validated against the step's `input` schema before the\n * handler runs (`undefined` for steps that declare no input schema).\n */\n readonly input: TInput\n /** 1-based attempt number for this Execution; > 1 means a retry of the same `executionId`. */\n readonly attempt: number\n /** Total attempts this Execution is allowed, from the step's retry policy. */\n readonly maxAttempts: number\n\n /**\n * Register an application write to run inside the framework's commit\n * transaction, so it commits atomically with the new Case State.\n * Callbacks run in registration order; registrations from a failed attempt\n * are discarded before the next attempt.\n */\n onCommit(write: CommitWrite<TCommit>): void\n\n /**\n * Register an external identifier against this case (and, on a scoped\n * step, this element) so the eventual webhook can be routed back.\n * Correlation is one half of integrating an external system; Ingestion —\n * executing the routed event as an ordinary step — is the other.\n *\n * Written atomically with Case State and other commit effects. External\n * provider calls are outside this atomic operation and must be idempotent.\n */\n correlate(request: CorrelationRequest): void\n\n /**\n * Mark the case dormant: a journaled terminal marker written with this\n * Execution's commit. Dormancy means exclusion from\n * default active listings — **never** a freeze: a dormant case still\n * computes affordances, and a step guarded on ended state can still claim\n * and {@link HandlerContext.reopen} it.\n */\n end(): void\n\n /** Clear the dormancy marker — un-ending is an ordinary step. */\n reopen(): void\n}\n\n/** The context a scoped step's handler receives: the base context plus the scope binding. */\nexport interface ScopedHandlerContext<\n TElement,\n TActor = unknown,\n TInput = undefined,\n TCommit = unknown,\n> extends HandlerContext<TActor, TInput, TCommit> {\n /** The bound scope element the Execution is about (e.g. one buyer). */\n readonly scope: TElement\n /** The element's scope key — half of the affordance's identity. */\n readonly scopeKey: string\n}\n\n/**\n * An unscoped step's handler: async, receives the current Case State and the\n * execution context, and returns the **next** Case State document.\n */\nexport type StepHandler<\n TState,\n TActor = unknown,\n TInput = undefined,\n TCommit = unknown,\n> = (\n state: TState,\n ctx: HandlerContext<TActor, TInput, TCommit>,\n) => Promise<TState>\n\n/** A scoped step's handler: as {@link StepHandler}, with the scope binding on ctx. */\nexport type ScopedStepHandler<\n TState,\n TElement,\n TActor = unknown,\n TInput = undefined,\n TCommit = unknown,\n> = (\n state: TState,\n ctx: ScopedHandlerContext<TElement, TActor, TInput, TCommit>,\n) => Promise<TState>\n\n/**\n * A handler as held on a normalized {@link StepDefinition}: the authoring\n * generics (input, scope element) erased. The execution lifecycle invokes\n * through this type, constructing a context that satisfies the authored shape\n * (the model layer guarantees input was validated and, for scoped steps, a\n * scope is bound).\n */\nexport type ErasedStepHandler<TState, TActor = unknown, TCommit = unknown> = (\n state: TState,\n ctx:\n | HandlerContext<TActor, unknown, TCommit>\n | ScopedHandlerContext<unknown, TActor, unknown, TCommit>,\n) => Promise<TState>\n\n/** Registrations share one ordered queue and are discarded on a failed attempt. */\nexport type CommitEffect<TCommit = unknown> =\n | { readonly kind: 'write'; readonly write: CommitWrite<TCommit> }\n | {\n readonly kind: 'correlation'\n readonly registration: CorrelationRegistration\n }\n"]}
|
package/dist/model/index.d.ts
CHANGED
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
export type { AnyCaseType, CaseTypeDefinition, CaseTypeOptions, } from './casetype.js';
|
|
15
15
|
export { caseType } from './casetype.js';
|
|
16
16
|
export { ScopeKeyError, UnknownStepError } from './errors.js';
|
|
17
|
-
export type { CommitWrite, CorrelationRequest, ErasedStepHandler, HandlerContext, ScopedHandlerContext, ScopedStepHandler, StepHandler, } from './handler.js';
|
|
17
|
+
export type { CommitEffect, CommitWrite, CorrelationRequest, ErasedStepHandler, HandlerContext, ScopedHandlerContext, ScopedStepHandler, StepHandler, } from './handler.js';
|
|
18
18
|
export type { RetryOptions, RetryPolicy } from './retry.js';
|
|
19
19
|
export { DEFAULT_RETRY, normalizeRetry } from './retry.js';
|
|
20
20
|
export type { ScopeDeclaration, ScopedCondition, ScopedConditionContext, ScopedConditionMap, ScopedConditionMapEntry, } from './scope.js';
|
|
21
|
-
export type { ActorMarker, BoundStep, ScopedStepOptions, StepDefinition, StepMetadata, StepOptions, } from './step.js';
|
|
22
|
-
export { actor, StepInputValidationError, step, stepsOf, validateStepInput, } from './step.js';
|
|
21
|
+
export type { ActorMarker, BoundStep, CommitContextMarker, ScopedStepOptions, StepDefinition, StepMetadata, StepOptions, } from './step.js';
|
|
22
|
+
export { actor, commitContext, StepInputValidationError, step, stepsOf, validateStepInput, } from './step.js';
|
|
23
23
|
export type { ComputationContext, ScopeBinding, StepTarget, TargetAddress, TargetAddressFailure, TargetSelection, } from './target.js';
|
|
24
24
|
export { addressTarget, evaluateTarget, resolveTarget, SCOPE_FAILURE_CONDITION, scopeFailureEvaluation, selectTargets, } from './target.js';
|
package/dist/model/index.js
CHANGED
|
@@ -14,6 +14,6 @@
|
|
|
14
14
|
export { caseType } from './casetype.js';
|
|
15
15
|
export { ScopeKeyError, UnknownStepError } from './errors.js';
|
|
16
16
|
export { DEFAULT_RETRY, normalizeRetry } from './retry.js';
|
|
17
|
-
export { actor, StepInputValidationError, step, stepsOf, validateStepInput, } from './step.js';
|
|
17
|
+
export { actor, commitContext, StepInputValidationError, step, stepsOf, validateStepInput, } from './step.js';
|
|
18
18
|
export { addressTarget, evaluateTarget, resolveTarget, SCOPE_FAILURE_CONDITION, scopeFailureEvaluation, selectTargets, } from './target.js';
|
|
19
19
|
//# sourceMappingURL=index.js.map
|
package/dist/model/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAY7D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAiB1D,OAAO,EACL,KAAK,EACL,aAAa,EACb,wBAAwB,EACxB,IAAI,EACJ,OAAO,EACP,iBAAiB,GAClB,MAAM,WAAW,CAAA;AASlB,OAAO,EACL,aAAa,EACb,cAAc,EACd,aAAa,EACb,uBAAuB,EACvB,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAA","sourcesContent":["/**\n * The definition API: case types and steps.\n *\n * A **case type** declares a typed state schema and a set of guarded\n * **steps** — nothing else. A **step** is a\n * guard plus a handler; a step may declare a **scope** over a state\n * collection, yielding one affordance per selected element with identity\n * (step × scope key). Handlers are declared here and executed by the\n * execution lifecycle; this module never invokes one.\n *\n * See CONTEXT.md for the vocabulary and the guards module for the condition\n * algebra these definitions are built from.\n */\n\nexport type {\n AnyCaseType,\n CaseTypeDefinition,\n CaseTypeOptions,\n} from './casetype.js'\nexport { caseType } from './casetype.js'\nexport { ScopeKeyError, UnknownStepError } from './errors.js'\nexport type {\n CommitEffect,\n CommitWrite,\n CorrelationRequest,\n ErasedStepHandler,\n HandlerContext,\n ScopedHandlerContext,\n ScopedStepHandler,\n StepHandler,\n} from './handler.js'\nexport type { RetryOptions, RetryPolicy } from './retry.js'\nexport { DEFAULT_RETRY, normalizeRetry } from './retry.js'\nexport type {\n ScopeDeclaration,\n ScopedCondition,\n ScopedConditionContext,\n ScopedConditionMap,\n ScopedConditionMapEntry,\n} from './scope.js'\nexport type {\n ActorMarker,\n BoundStep,\n CommitContextMarker,\n ScopedStepOptions,\n StepDefinition,\n StepMetadata,\n StepOptions,\n} from './step.js'\nexport {\n actor,\n commitContext,\n StepInputValidationError,\n step,\n stepsOf,\n validateStepInput,\n} from './step.js'\nexport type {\n ComputationContext,\n ScopeBinding,\n StepTarget,\n TargetAddress,\n TargetAddressFailure,\n TargetSelection,\n} from './target.js'\nexport {\n addressTarget,\n evaluateTarget,\n resolveTarget,\n SCOPE_FAILURE_CONDITION,\n scopeFailureEvaluation,\n selectTargets,\n} from './target.js'\n"]}
|
package/dist/model/step.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { ErasedStepHandler, ScopedStepHandler, StepHandler } from './handle
|
|
|
14
14
|
import type { RetryOptions, RetryPolicy } from './retry.js';
|
|
15
15
|
import type { ScopeDeclaration, ScopedConditionMap } from './scope.js';
|
|
16
16
|
/** Options for an unscoped step. */
|
|
17
|
-
export interface StepOptions<TState, TActor = unknown, TInput = undefined> {
|
|
17
|
+
export interface StepOptions<TState, TActor = unknown, TInput = undefined, TCommit = unknown> {
|
|
18
18
|
/** The step's name — unique within its case type; half of a scoped affordance's identity. */
|
|
19
19
|
readonly name: string;
|
|
20
20
|
/**
|
|
@@ -46,10 +46,10 @@ export interface StepOptions<TState, TActor = unknown, TInput = undefined> {
|
|
|
46
46
|
*/
|
|
47
47
|
readonly retry?: RetryOptions;
|
|
48
48
|
/** The step's effect function — the only thing that mutates Case State. */
|
|
49
|
-
readonly handler: StepHandler<TState, TActor, TInput>;
|
|
49
|
+
readonly handler: StepHandler<TState, TActor, TInput, TCommit>;
|
|
50
50
|
}
|
|
51
51
|
/** Options for a scoped step: an unscoped step plus the scope declaration. */
|
|
52
|
-
export interface ScopedStepOptions<TState, TElement, TActor = unknown, TInput = undefined> {
|
|
52
|
+
export interface ScopedStepOptions<TState, TElement, TActor = unknown, TInput = undefined, TCommit = unknown> {
|
|
53
53
|
readonly name: string;
|
|
54
54
|
readonly title?: string;
|
|
55
55
|
readonly description?: string;
|
|
@@ -59,7 +59,7 @@ export interface ScopedStepOptions<TState, TElement, TActor = unknown, TInput =
|
|
|
59
59
|
readonly permits?: ScopedConditionMap<TState, TElement, TActor>;
|
|
60
60
|
readonly input?: StandardSchemaV1<unknown, TInput>;
|
|
61
61
|
readonly retry?: RetryOptions;
|
|
62
|
-
readonly handler: ScopedStepHandler<TState, TElement, TActor, TInput>;
|
|
62
|
+
readonly handler: ScopedStepHandler<TState, TElement, TActor, TInput, TCommit>;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* A step definition as held by a case type and consumed by the engine — the
|
|
@@ -67,7 +67,7 @@ export interface ScopedStepOptions<TState, TElement, TActor = unknown, TInput =
|
|
|
67
67
|
* The options types above carry the precise authoring shapes; this is the
|
|
68
68
|
* machine-facing normal form.
|
|
69
69
|
*/
|
|
70
|
-
export interface StepDefinition<TState, TActor = unknown> {
|
|
70
|
+
export interface StepDefinition<TState, TActor = unknown, TCommit = unknown> {
|
|
71
71
|
readonly name: string;
|
|
72
72
|
/** The declared human label, or `null` — clients fall back to `name`. */
|
|
73
73
|
readonly title: string | null;
|
|
@@ -89,7 +89,7 @@ export interface StepDefinition<TState, TActor = unknown> {
|
|
|
89
89
|
/** The normalized retry policy the execution lifecycle applies to this step. */
|
|
90
90
|
readonly retry: RetryPolicy;
|
|
91
91
|
/** The step's handler — invoked only by the execution lifecycle. */
|
|
92
|
-
readonly handler: ErasedStepHandler<TState, TActor>;
|
|
92
|
+
readonly handler: ErasedStepHandler<TState, TActor, TCommit>;
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
95
|
* A step's declared human metadata, as `Engine.stepMetadataFor` answers it —
|
|
@@ -143,8 +143,8 @@ export declare const looksLikeStepDefinition: (value: unknown) => value is StepD
|
|
|
143
143
|
* (explicit type-argument lists interact badly with overloaded
|
|
144
144
|
* context-sensitive options — annotate inside the options instead).
|
|
145
145
|
*/
|
|
146
|
-
export declare function step<TState, TElement, TActor = unknown, TInput = undefined>(options: ScopedStepOptions<TState, TElement, TActor, TInput>): StepDefinition<TState, TActor>;
|
|
147
|
-
export declare function step<TState, TActor = unknown, TInput = undefined>(options: StepOptions<TState, TActor, TInput>): StepDefinition<TState, TActor>;
|
|
146
|
+
export declare function step<TState, TElement, TActor = unknown, TInput = undefined, TCommit = unknown>(options: ScopedStepOptions<TState, TElement, TActor, TInput, TCommit>): StepDefinition<TState, TActor, TCommit>;
|
|
147
|
+
export declare function step<TState, TActor = unknown, TInput = undefined, TCommit = unknown>(options: StepOptions<TState, TActor, TInput, TCommit>): StepDefinition<TState, TActor, TCommit>;
|
|
148
148
|
/**
|
|
149
149
|
* The `step()` authoring surface with the case's state and actor types fixed.
|
|
150
150
|
*
|
|
@@ -156,9 +156,9 @@ export declare function step<TState, TActor = unknown, TInput = undefined>(optio
|
|
|
156
156
|
* contextually, and a scoped step's element type anchors on `scope.select`
|
|
157
157
|
* alone (so `ctx.scope` is the element, with no `undefined` to narrow away).
|
|
158
158
|
*/
|
|
159
|
-
export interface BoundStep<TState, TActor = unknown> {
|
|
160
|
-
<TElement, TInput = undefined>(options: ScopedStepOptions<TState, TElement, TActor, TInput>): StepDefinition<TState, TActor>;
|
|
161
|
-
<TInput = undefined>(options: StepOptions<TState, TActor, TInput>): StepDefinition<TState, TActor>;
|
|
159
|
+
export interface BoundStep<TState, TActor = unknown, TCommit = unknown> {
|
|
160
|
+
<TElement, TInput = undefined>(options: ScopedStepOptions<TState, TElement, TActor, TInput, TCommit>): StepDefinition<TState, TActor, TCommit>;
|
|
161
|
+
<TInput = undefined>(options: StepOptions<TState, TActor, TInput, TCommit>): StepDefinition<TState, TActor, TCommit>;
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
164
|
* A value-level carrier for `stepsOf`'s actor type — nothing but the type.
|
|
@@ -215,7 +215,7 @@ export declare const actor: <TActor>() => ActorMarker<TActor>;
|
|
|
215
215
|
* Throws at definition time when `state` is not a Standard Schema, like
|
|
216
216
|
* every other malformed-definition case in this module.
|
|
217
217
|
*/
|
|
218
|
-
export declare const stepsOf: <S extends StandardSchemaV1, TActor = unknown>(state: S, _actor?: ActorMarker<TActor>) => BoundStep<StandardSchemaV1.InferOutput<S>, TActor>;
|
|
218
|
+
export declare const stepsOf: <S extends StandardSchemaV1, TActor = unknown, TCommit = unknown>(state: S, _actor?: ActorMarker<TActor>, _commit?: CommitContextMarker<TCommit>) => BoundStep<StandardSchemaV1.InferOutput<S>, TActor, TCommit>;
|
|
219
219
|
/** A step's declared input failed validation against its input schema. */
|
|
220
220
|
export declare class StepInputValidationError extends AffordanceError {
|
|
221
221
|
readonly stepName: string;
|
|
@@ -229,4 +229,9 @@ export declare class StepInputValidationError extends AffordanceError {
|
|
|
229
229
|
* without an input schema accepts only `undefined` and yields `undefined`;
|
|
230
230
|
* anything else is a caller bug and throws.
|
|
231
231
|
*/
|
|
232
|
-
export declare const validateStepInput: <TState, TActor>(definition: StepDefinition<TState, TActor>, input: unknown) => Promise<unknown>;
|
|
232
|
+
export declare const validateStepInput: <TState, TActor, TCommit>(definition: StepDefinition<TState, TActor, TCommit>, input: unknown) => Promise<unknown>;
|
|
233
|
+
/** Name the transaction/repository context supplied to onCommit callbacks. */
|
|
234
|
+
export interface CommitContextMarker<TCommit> {
|
|
235
|
+
readonly __commit?: TCommit;
|
|
236
|
+
}
|
|
237
|
+
export declare const commitContext: <TCommit>() => CommitContextMarker<TCommit>;
|
package/dist/model/step.js
CHANGED
|
@@ -169,7 +169,7 @@ export const actor = () => ACTOR_MARKER;
|
|
|
169
169
|
* Throws at definition time when `state` is not a Standard Schema, like
|
|
170
170
|
* every other malformed-definition case in this module.
|
|
171
171
|
*/
|
|
172
|
-
export const stepsOf = (state, _actor) => {
|
|
172
|
+
export const stepsOf = (state, _actor, _commit) => {
|
|
173
173
|
if (!isStandardSchema(state)) {
|
|
174
174
|
throw new TypeError('stepsOf: state must be a Standard Schema (e.g. a zod schema)');
|
|
175
175
|
}
|
|
@@ -207,4 +207,5 @@ export const validateStepInput = async (definition, input) => {
|
|
|
207
207
|
throw new StepInputValidationError(definition.name, result.issues);
|
|
208
208
|
return result.value;
|
|
209
209
|
};
|
|
210
|
+
export const commitContext = () => ({});
|
|
210
211
|
//# sourceMappingURL=step.js.map
|
package/dist/model/step.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step.js","sourceRoot":"","sources":["../../src/model/step.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Step definitions.\n *\n * A **step** is an independently-defined unit of possible work on a case: a\n * guard plus a handler (CONTEXT.md). Steps never declare ordering —\n * sequencing is data dependencies between guards. `step()` validates the\n * definition loudly at construction time: a malformed step should fail the\n * deploy, not an evaluation.\n */\n\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { AffordanceError } from '../errors.js'\nimport type { ConditionMap, Guard, GuardSection } from '../guards/index.js'\nimport { guardEntries } from '../guards/index.js'\nimport type {\n ErasedStepHandler,\n ScopedStepHandler,\n StepHandler,\n} from './handler.js'\nimport type { RetryOptions, RetryPolicy } from './retry.js'\nimport { normalizeRetry } from './retry.js'\nimport type { ScopeDeclaration, ScopedConditionMap } from './scope.js'\nimport { eraseScopedConditionMap } from './scope.js'\n\n/** Options for an unscoped step. */\nexport interface StepOptions<TState, TActor = unknown, TInput = undefined> {\n /** The step's name — unique within its case type; half of a scoped affordance's identity. */\n readonly name: string\n /**\n * A short human label for the step (\"Issue the funding call\"). Definition\n * metadata, not identity: journal entries and refusals name the step by\n * `name`; adapters serialize the title so a client renders steps without\n * an out-of-band label table.\n */\n readonly title?: string\n /**\n * A sentence on what the step does and when to take it — what makes the\n * affordance contract a usable tool list for a caller (an agent included)\n * that has never seen this case type.\n */\n readonly description?: string\n /** Case conditions: when one fails the step is not possible on this case, for anyone. */\n readonly requires?: ConditionMap<TState, TActor>\n /** Actor conditions: when one fails the step is possible but not permitted for this actor. */\n readonly permits?: ConditionMap<TState, TActor>\n /**\n * Optional input schema (any Standard Schema — zod v4 qualifies). Validated\n * by `validateStepInput` before the handler runs.\n */\n readonly input?: StandardSchemaV1<unknown, TInput>\n /**\n * How many times a failed attempt is retried, and how long between\n * attempts. Defaults to three attempts with exponential backoff; set\n * `{ maxAttempts: 1 }` to disable retry for this step.\n */\n readonly retry?: RetryOptions\n /** The step's effect function — the only thing that mutates Case State. */\n readonly handler: StepHandler<TState, TActor, TInput>\n}\n\n/** Options for a scoped step: an unscoped step plus the scope declaration. */\nexport interface ScopedStepOptions<\n TState,\n TElement,\n TActor = unknown,\n TInput = undefined,\n> {\n readonly name: string\n readonly title?: string\n readonly description?: string\n /** The collection the step ranges over and how each element is identified. */\n readonly scope: ScopeDeclaration<TState, TElement>\n readonly requires?: ScopedConditionMap<TState, TElement, TActor>\n readonly permits?: ScopedConditionMap<TState, TElement, TActor>\n readonly input?: StandardSchemaV1<unknown, TInput>\n readonly retry?: RetryOptions\n readonly handler: ScopedStepHandler<TState, TElement, TActor, TInput>\n}\n\n/**\n * A step definition as held by a case type and consumed by the engine — the\n * authoring generics (scope element, input) erased to the case-state level.\n * The options types above carry the precise authoring shapes; this is the\n * machine-facing normal form.\n */\nexport interface StepDefinition<TState, TActor = unknown> {\n readonly name: string\n /** The declared human label, or `null` — clients fall back to `name`. */\n readonly title: string | null\n /** The declared what-and-when sentence, or `null`. */\n readonly description: string | null\n /**\n * The step's guard in the guards module's shape, ready for `evaluateGuard`.\n * For a scoped step the engine evaluates it once per selected element with\n * the element bound as `scope`.\n */\n readonly guard: Guard<TState, TActor>\n /** The scope declaration (element type erased), or `null` for an unscoped step. */\n readonly scope: {\n readonly select: (state: TState) => readonly unknown[]\n readonly key: (element: unknown) => string\n } | null\n /** The declared input schema, or `null` when the step takes no input. */\n readonly input: StandardSchemaV1 | null\n /** The normalized retry policy the execution lifecycle applies to this step. */\n readonly retry: RetryPolicy\n /** The step's handler — invoked only by the execution lifecycle. */\n readonly handler: ErasedStepHandler<TState, TActor>\n}\n\n/**\n * A step's declared human metadata, as `Engine.stepMetadataFor` answers it —\n * the serializable slice of a {@link StepDefinition} an adapter puts on the\n * wire.\n */\nexport interface StepMetadata {\n readonly title: string | null\n readonly description: string | null\n}\n\n/** Whether a value is a Standard-Schema instance — the one spelling of the check. */\nexport const isStandardSchema = (value: unknown): value is StandardSchemaV1 =>\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { '~standard'?: unknown })['~standard'] === 'object'\n\n/**\n * Whether a value is plausibly a {@link StepDefinition} — the structural\n * check `caseType()` applies to every step it is given.\n */\nexport const looksLikeStepDefinition = (\n value: unknown,\n): value is StepDefinition<unknown, unknown> =>\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { name?: unknown }).name === 'string' &&\n typeof (value as { handler?: unknown }).handler === 'function' &&\n typeof (value as { guard?: unknown }).guard === 'object'\n\n/**\n * Validate one guard section map at definition time: every entry must be a\n * condition function or an `anyOf(...)` group\n * (unscoped only — scoped maps admit conditions alone).\n *\n * The classification and the `requires.escrowReady` addressing both come from\n * `guardEntries`, so what a definition is allowed to contain is decided\n * against the same walk evaluation uses — a definition `step()` accepts is\n * one `evaluateGuard` can read.\n */\nconst validateConditionMap = (\n stepName: string,\n section: GuardSection,\n map: Readonly<Record<string, unknown>> | undefined,\n allowAnyOf: boolean,\n): void => {\n if (map === undefined) return\n if (typeof map !== 'object' || map === null || Array.isArray(map)) {\n throw new TypeError(\n `step '${stepName}': ${section} must be a plain object of named conditions`,\n )\n }\n const section_ = { [section]: map } as Guard<unknown, unknown>\n for (const entry of guardEntries(section_)) {\n if (entry.kind === 'condition') continue\n if (entry.kind === 'anyOf' && allowAnyOf) continue\n const allowed = allowAnyOf\n ? 'a condition function or an anyOf(...) group'\n : 'a condition function (anyOf is not part of the scoped guard surface)'\n throw new TypeError(\n `step '${stepName}': ${entry.address} must be ${allowed}`,\n )\n }\n}\n\nconst validateCommon = (options: {\n name: unknown\n title?: unknown\n description?: unknown\n input?: unknown\n handler: unknown\n}): string => {\n const { name } = options\n if (typeof name !== 'string' || name.trim() === '') {\n throw new TypeError('step: name must be a non-empty string')\n }\n if (typeof options.handler !== 'function') {\n throw new TypeError(`step '${name}': handler must be an async function`)\n }\n if (options.input !== undefined && !isStandardSchema(options.input)) {\n throw new TypeError(\n `step '${name}': input must be a Standard Schema (e.g. a zod schema)`,\n )\n }\n for (const field of ['title', 'description'] as const) {\n const value = options[field]\n if (\n value !== undefined &&\n (typeof value !== 'string' || value.trim() === '')\n ) {\n throw new TypeError(\n `step '${name}': ${field} must be a non-empty string when given`,\n )\n }\n }\n return name\n}\n\n/**\n * Define a step. Two shapes, discriminated by the presence of `scope`:\n *\n * ```ts\n * // Standalone form (internal — apps author through {@link stepsOf}, which\n * // binds the generics once and takes the same options annotation-free):\n * // type parameters are inferred from annotations in the options — annotate\n * // the state parameter of a condition (and the actor on a permits ctx).\n * step({\n * name: 'issue-funding-call',\n * requires: { escrowReady: (s: Purchase) => s.escrow?.status === 'open' },\n * permits: { isOrganizer: (_s: Purchase, ctx: ConditionContext<Ops>) => ctx.actor.roles.includes('organizer') },\n * handler: async (s, ctx) => s,\n * })\n *\n * // Scoped, standalone form: annotating scope.select anchors both the state\n * // and element types; conditions then read the bound element as ctx.scope,\n * // fully typed (through stepsOf, even the select annotation goes away):\n * step({\n * name: 'escalate-verification',\n * scope: { select: (s: Purchase) => s.buyers.filter(b => b.verification?.status === 'review'), key: b => b.id },\n * requires: { flagged: (s: Purchase, ctx) => (ctx.scope as Buyer).verification?.flaggedAt != null },\n * handler: async (s, ctx) => s,\n * })\n * ```\n *\n * Validates loudly at construction time — malformed guard entries, a\n * malformed scope declaration, a non-function handler, or a non-schema\n * `input` all throw `TypeError` (definition-time validation; a definition\n * bug should fail the deploy, not an evaluation).\n *\n * The scoped overload is declared first: type parameters are meant to be\n * inferred, and inference resolves each call shape against its own overload\n * (explicit type-argument lists interact badly with overloaded\n * context-sensitive options — annotate inside the options instead).\n */\nexport function step<TState, TElement, TActor = unknown, TInput = undefined>(\n options: ScopedStepOptions<TState, TElement, TActor, TInput>,\n): StepDefinition<TState, TActor>\nexport function step<TState, TActor = unknown, TInput = undefined>(\n options: StepOptions<TState, TActor, TInput>,\n): StepDefinition<TState, TActor>\nexport function step<TState, TActor>(\n options:\n | StepOptions<TState, TActor, unknown>\n | ScopedStepOptions<TState, unknown, TActor, unknown>,\n): StepDefinition<TState, TActor> {\n const name = validateCommon(options)\n const scoped = 'scope' in options && options.scope !== undefined\n let scope: StepDefinition<TState, TActor>['scope'] = null\n let guard: Guard<TState, TActor>\n\n if (scoped) {\n const declaration = (\n options as ScopedStepOptions<TState, unknown, TActor, unknown>\n ).scope\n if (\n typeof declaration !== 'object' ||\n declaration === null ||\n typeof declaration.select !== 'function' ||\n typeof declaration.key !== 'function'\n ) {\n throw new TypeError(\n `step '${name}': scope must be { select: state => elements, key: element => string }`,\n )\n }\n validateConditionMap(name, 'requires', options.requires, false)\n validateConditionMap(name, 'permits', options.permits, false)\n scope = { select: declaration.select, key: declaration.key }\n // Erasure, not conversion: scoped maps are runtime-identical to unscoped\n // ones; evaluation binds the scope element the scoped types promise.\n const scopedOptions = options as ScopedStepOptions<\n TState,\n unknown,\n TActor,\n unknown\n >\n guard = {\n ...(scopedOptions.requires !== undefined && {\n requires: eraseScopedConditionMap(scopedOptions.requires),\n }),\n ...(scopedOptions.permits !== undefined && {\n permits: eraseScopedConditionMap(scopedOptions.permits),\n }),\n }\n } else {\n const unscoped = options as StepOptions<TState, TActor, unknown>\n validateConditionMap(name, 'requires', unscoped.requires, true)\n validateConditionMap(name, 'permits', unscoped.permits, true)\n guard = {\n ...(unscoped.requires !== undefined && { requires: unscoped.requires }),\n ...(unscoped.permits !== undefined && { permits: unscoped.permits }),\n }\n }\n\n return {\n name,\n title: options.title ?? null,\n description: options.description ?? null,\n guard,\n scope,\n input: options.input ?? null,\n retry: normalizeRetry(name, options.retry),\n // The one erasure cast for handlers: the authored context (typed input,\n // typed scope element) is what the execution lifecycle constructs; see\n // ErasedStepHandler.\n handler: options.handler as unknown as ErasedStepHandler<TState, TActor>,\n }\n}\n\n/**\n * The `step()` authoring surface with the case's state and actor types fixed.\n *\n * Same two call shapes as `step()` — scoped first, discriminated by the\n * presence of `scope` — but `TState`/`TActor` are already substituted, so\n * only the per-step generics (scope element, input) remain to be inferred.\n * That is what makes annotation-free authoring work: conditions and handlers\n * no longer participate in inferring the state type, they just receive it\n * contextually, and a scoped step's element type anchors on `scope.select`\n * alone (so `ctx.scope` is the element, with no `undefined` to narrow away).\n */\nexport interface BoundStep<TState, TActor = unknown> {\n <TElement, TInput = undefined>(\n options: ScopedStepOptions<TState, TElement, TActor, TInput>,\n ): StepDefinition<TState, TActor>\n <TInput = undefined>(\n options: StepOptions<TState, TActor, TInput>,\n ): StepDefinition<TState, TActor>\n}\n\n/**\n * A value-level carrier for `stepsOf`'s actor type — nothing but the type.\n * Exists because `TActor` has no value to be inferred from (a case's actor\n * shape is app-defined and never materializes at definition time), and\n * spelling it as a type argument would force spelling the schema's type too\n * (TypeScript has no partial type-argument inference).\n */\nexport interface ActorMarker<TActor> {\n readonly __actor?: TActor\n}\n\nconst ACTOR_MARKER: ActorMarker<never> = Object.freeze({})\n\n/** Name the actor type of a `stepsOf` factory: `stepsOf(PurchaseState, actor<PurchaseActor>())`. */\nexport const actor = <TActor>(): ActorMarker<TActor> => ACTOR_MARKER\n\n/**\n * Bind `step()` to a case's state schema — the schema-anchored authoring\n * factory.\n *\n * ```ts\n * const purchaseStep = stepsOf(PurchaseState, actor<PurchaseActor>())\n *\n * purchaseStep({\n * name: 'issue-funding-call',\n * requires: { escrowReady: s => s.escrow.status === 'open' }, // s: inferred from the schema\n * permits: { isOrganizer: (_s, ctx) => hasRole(ctx.actor, 'organizer') },\n * handler: async s => s,\n * })\n *\n * purchaseStep({\n * name: 'escalate-verification',\n * scope: { select: s => s.buyers.filter(b => b.verification.status === 'review'), key: b => b.id },\n * requires: { flagged: (_s, ctx) => ctx.scope.verification.flaggedAt !== null }, // ctx.scope: Buyer\n * handler: async s => s,\n * })\n * ```\n *\n * The state type is derived from the schema *value* — the same\n * `InferOutput` derivation `caseType` performs — so the factory and the case\n * type are anchored to one declaration and cannot drift apart: the state a\n * condition sees is definitionally the state the engine validates against.\n * The per-condition annotations the bare `step()` needs\n * (`(s: Purchase) => …`) disappear, because `TState` is no longer inferred\n * from the options.\n *\n * The second argument exists only to name the actor type and carries no\n * runtime information; omit it for an untyped actor. Define one factory per\n * case type module, next to the schema, and author every step of that case\n * type through it.\n *\n * Returns `step` itself, re-typed — a step authored through the factory is\n * bit-for-bit an ordinary step definition. `step` is deliberately not part\n * of the package barrel: this factory is the public authoring surface, and\n * a helper that builds steps generically should accept a\n * {@link BoundStep} rather than reach for the unbound `step`.\n * Throws at definition time when `state` is not a Standard Schema, like\n * every other malformed-definition case in this module.\n */\nexport const stepsOf = <S extends StandardSchemaV1, TActor = unknown>(\n state: S,\n _actor?: ActorMarker<TActor>,\n): BoundStep<StandardSchemaV1.InferOutput<S>, TActor> => {\n if (!isStandardSchema(state)) {\n throw new TypeError(\n 'stepsOf: state must be a Standard Schema (e.g. a zod schema)',\n )\n }\n return step\n}\n\n/** A step's declared input failed validation against its input schema. */\nexport class StepInputValidationError extends AffordanceError {\n readonly stepName: string\n readonly issues: readonly StandardSchemaV1.Issue[]\n\n constructor(stepName: string, issues: readonly StandardSchemaV1.Issue[]) {\n super(\n 'invalid-input',\n `invalid input for step '${stepName}': ${issues.map((issue) => issue.message).join('; ')}`,\n )\n this.name = 'StepInputValidationError'\n this.stepName = stepName\n this.issues = issues\n }\n}\n\n/**\n * Validate a step's input against its declared input schema — the validation\n * plumbing the execution lifecycle runs before invoking the handler (\"validated before the\n * handler runs\"). Returns the schema *output* (defaults applied). A step\n * without an input schema accepts only `undefined` and yields `undefined`;\n * anything else is a caller bug and throws.\n */\nexport const validateStepInput = async <TState, TActor>(\n definition: StepDefinition<TState, TActor>,\n input: unknown,\n): Promise<unknown> => {\n if (definition.input === null) {\n if (input !== undefined) {\n throw new StepInputValidationError(definition.name, [\n { message: 'step declares no input schema, but input was provided' },\n ])\n }\n return undefined\n }\n const result = await definition.input['~standard'].validate(input)\n if (result.issues)\n throw new StepInputValidationError(definition.name, result.issues)\n return result.value\n}\n"]}
|
|
1
|
+
{"version":3,"file":"step.js","sourceRoot":"","sources":["../../src/model/step.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Step definitions.\n *\n * A **step** is an independently-defined unit of possible work on a case: a\n * guard plus a handler (CONTEXT.md). Steps never declare ordering —\n * sequencing is data dependencies between guards. `step()` validates the\n * definition loudly at construction time: a malformed step should fail the\n * deploy, not an evaluation.\n */\n\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport { AffordanceError } from '../errors.js'\nimport type { ConditionMap, Guard, GuardSection } from '../guards/index.js'\nimport { guardEntries } from '../guards/index.js'\nimport type {\n ErasedStepHandler,\n ScopedStepHandler,\n StepHandler,\n} from './handler.js'\nimport type { RetryOptions, RetryPolicy } from './retry.js'\nimport { normalizeRetry } from './retry.js'\nimport type { ScopeDeclaration, ScopedConditionMap } from './scope.js'\nimport { eraseScopedConditionMap } from './scope.js'\n\n/** Options for an unscoped step. */\nexport interface StepOptions<\n TState,\n TActor = unknown,\n TInput = undefined,\n TCommit = unknown,\n> {\n /** The step's name — unique within its case type; half of a scoped affordance's identity. */\n readonly name: string\n /**\n * A short human label for the step (\"Issue the funding call\"). Definition\n * metadata, not identity: journal entries and refusals name the step by\n * `name`; adapters serialize the title so a client renders steps without\n * an out-of-band label table.\n */\n readonly title?: string\n /**\n * A sentence on what the step does and when to take it — what makes the\n * affordance contract a usable tool list for a caller (an agent included)\n * that has never seen this case type.\n */\n readonly description?: string\n /** Case conditions: when one fails the step is not possible on this case, for anyone. */\n readonly requires?: ConditionMap<TState, TActor>\n /** Actor conditions: when one fails the step is possible but not permitted for this actor. */\n readonly permits?: ConditionMap<TState, TActor>\n /**\n * Optional input schema (any Standard Schema — zod v4 qualifies). Validated\n * by `validateStepInput` before the handler runs.\n */\n readonly input?: StandardSchemaV1<unknown, TInput>\n /**\n * How many times a failed attempt is retried, and how long between\n * attempts. Defaults to three attempts with exponential backoff; set\n * `{ maxAttempts: 1 }` to disable retry for this step.\n */\n readonly retry?: RetryOptions\n /** The step's effect function — the only thing that mutates Case State. */\n readonly handler: StepHandler<TState, TActor, TInput, TCommit>\n}\n\n/** Options for a scoped step: an unscoped step plus the scope declaration. */\nexport interface ScopedStepOptions<\n TState,\n TElement,\n TActor = unknown,\n TInput = undefined,\n TCommit = unknown,\n> {\n readonly name: string\n readonly title?: string\n readonly description?: string\n /** The collection the step ranges over and how each element is identified. */\n readonly scope: ScopeDeclaration<TState, TElement>\n readonly requires?: ScopedConditionMap<TState, TElement, TActor>\n readonly permits?: ScopedConditionMap<TState, TElement, TActor>\n readonly input?: StandardSchemaV1<unknown, TInput>\n readonly retry?: RetryOptions\n readonly handler: ScopedStepHandler<TState, TElement, TActor, TInput, TCommit>\n}\n\n/**\n * A step definition as held by a case type and consumed by the engine — the\n * authoring generics (scope element, input) erased to the case-state level.\n * The options types above carry the precise authoring shapes; this is the\n * machine-facing normal form.\n */\nexport interface StepDefinition<TState, TActor = unknown, TCommit = unknown> {\n readonly name: string\n /** The declared human label, or `null` — clients fall back to `name`. */\n readonly title: string | null\n /** The declared what-and-when sentence, or `null`. */\n readonly description: string | null\n /**\n * The step's guard in the guards module's shape, ready for `evaluateGuard`.\n * For a scoped step the engine evaluates it once per selected element with\n * the element bound as `scope`.\n */\n readonly guard: Guard<TState, TActor>\n /** The scope declaration (element type erased), or `null` for an unscoped step. */\n readonly scope: {\n readonly select: (state: TState) => readonly unknown[]\n readonly key: (element: unknown) => string\n } | null\n /** The declared input schema, or `null` when the step takes no input. */\n readonly input: StandardSchemaV1 | null\n /** The normalized retry policy the execution lifecycle applies to this step. */\n readonly retry: RetryPolicy\n /** The step's handler — invoked only by the execution lifecycle. */\n readonly handler: ErasedStepHandler<TState, TActor, TCommit>\n}\n\n/**\n * A step's declared human metadata, as `Engine.stepMetadataFor` answers it —\n * the serializable slice of a {@link StepDefinition} an adapter puts on the\n * wire.\n */\nexport interface StepMetadata {\n readonly title: string | null\n readonly description: string | null\n}\n\n/** Whether a value is a Standard-Schema instance — the one spelling of the check. */\nexport const isStandardSchema = (value: unknown): value is StandardSchemaV1 =>\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { '~standard'?: unknown })['~standard'] === 'object'\n\n/**\n * Whether a value is plausibly a {@link StepDefinition} — the structural\n * check `caseType()` applies to every step it is given.\n */\nexport const looksLikeStepDefinition = (\n value: unknown,\n): value is StepDefinition<unknown, unknown> =>\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { name?: unknown }).name === 'string' &&\n typeof (value as { handler?: unknown }).handler === 'function' &&\n typeof (value as { guard?: unknown }).guard === 'object'\n\n/**\n * Validate one guard section map at definition time: every entry must be a\n * condition function or an `anyOf(...)` group\n * (unscoped only — scoped maps admit conditions alone).\n *\n * The classification and the `requires.escrowReady` addressing both come from\n * `guardEntries`, so what a definition is allowed to contain is decided\n * against the same walk evaluation uses — a definition `step()` accepts is\n * one `evaluateGuard` can read.\n */\nconst validateConditionMap = (\n stepName: string,\n section: GuardSection,\n map: Readonly<Record<string, unknown>> | undefined,\n allowAnyOf: boolean,\n): void => {\n if (map === undefined) return\n if (typeof map !== 'object' || map === null || Array.isArray(map)) {\n throw new TypeError(\n `step '${stepName}': ${section} must be a plain object of named conditions`,\n )\n }\n const section_ = { [section]: map } as Guard<unknown, unknown>\n for (const entry of guardEntries(section_)) {\n if (entry.kind === 'condition') continue\n if (entry.kind === 'anyOf' && allowAnyOf) continue\n const allowed = allowAnyOf\n ? 'a condition function or an anyOf(...) group'\n : 'a condition function (anyOf is not part of the scoped guard surface)'\n throw new TypeError(\n `step '${stepName}': ${entry.address} must be ${allowed}`,\n )\n }\n}\n\nconst validateCommon = (options: {\n name: unknown\n title?: unknown\n description?: unknown\n input?: unknown\n handler: unknown\n}): string => {\n const { name } = options\n if (typeof name !== 'string' || name.trim() === '') {\n throw new TypeError('step: name must be a non-empty string')\n }\n if (typeof options.handler !== 'function') {\n throw new TypeError(`step '${name}': handler must be an async function`)\n }\n if (options.input !== undefined && !isStandardSchema(options.input)) {\n throw new TypeError(\n `step '${name}': input must be a Standard Schema (e.g. a zod schema)`,\n )\n }\n for (const field of ['title', 'description'] as const) {\n const value = options[field]\n if (\n value !== undefined &&\n (typeof value !== 'string' || value.trim() === '')\n ) {\n throw new TypeError(\n `step '${name}': ${field} must be a non-empty string when given`,\n )\n }\n }\n return name\n}\n\n/**\n * Define a step. Two shapes, discriminated by the presence of `scope`:\n *\n * ```ts\n * // Standalone form (internal — apps author through {@link stepsOf}, which\n * // binds the generics once and takes the same options annotation-free):\n * // type parameters are inferred from annotations in the options — annotate\n * // the state parameter of a condition (and the actor on a permits ctx).\n * step({\n * name: 'issue-funding-call',\n * requires: { escrowReady: (s: Purchase) => s.escrow?.status === 'open' },\n * permits: { isOrganizer: (_s: Purchase, ctx: ConditionContext<Ops>) => ctx.actor.roles.includes('organizer') },\n * handler: async (s, ctx) => s,\n * })\n *\n * // Scoped, standalone form: annotating scope.select anchors both the state\n * // and element types; conditions then read the bound element as ctx.scope,\n * // fully typed (through stepsOf, even the select annotation goes away):\n * step({\n * name: 'escalate-verification',\n * scope: { select: (s: Purchase) => s.buyers.filter(b => b.verification?.status === 'review'), key: b => b.id },\n * requires: { flagged: (s: Purchase, ctx) => (ctx.scope as Buyer).verification?.flaggedAt != null },\n * handler: async (s, ctx) => s,\n * })\n * ```\n *\n * Validates loudly at construction time — malformed guard entries, a\n * malformed scope declaration, a non-function handler, or a non-schema\n * `input` all throw `TypeError` (definition-time validation; a definition\n * bug should fail the deploy, not an evaluation).\n *\n * The scoped overload is declared first: type parameters are meant to be\n * inferred, and inference resolves each call shape against its own overload\n * (explicit type-argument lists interact badly with overloaded\n * context-sensitive options — annotate inside the options instead).\n */\nexport function step<\n TState,\n TElement,\n TActor = unknown,\n TInput = undefined,\n TCommit = unknown,\n>(\n options: ScopedStepOptions<TState, TElement, TActor, TInput, TCommit>,\n): StepDefinition<TState, TActor, TCommit>\nexport function step<\n TState,\n TActor = unknown,\n TInput = undefined,\n TCommit = unknown,\n>(\n options: StepOptions<TState, TActor, TInput, TCommit>,\n): StepDefinition<TState, TActor, TCommit>\nexport function step<TState, TActor, TCommit>(\n options:\n | StepOptions<TState, TActor, unknown, TCommit>\n | ScopedStepOptions<TState, unknown, TActor, unknown, TCommit>,\n): StepDefinition<TState, TActor, TCommit> {\n const name = validateCommon(options)\n const scoped = 'scope' in options && options.scope !== undefined\n let scope: StepDefinition<TState, TActor, TCommit>['scope'] = null\n let guard: Guard<TState, TActor>\n\n if (scoped) {\n const declaration = (\n options as ScopedStepOptions<TState, unknown, TActor, unknown, TCommit>\n ).scope\n if (\n typeof declaration !== 'object' ||\n declaration === null ||\n typeof declaration.select !== 'function' ||\n typeof declaration.key !== 'function'\n ) {\n throw new TypeError(\n `step '${name}': scope must be { select: state => elements, key: element => string }`,\n )\n }\n validateConditionMap(name, 'requires', options.requires, false)\n validateConditionMap(name, 'permits', options.permits, false)\n scope = { select: declaration.select, key: declaration.key }\n // Erasure, not conversion: scoped maps are runtime-identical to unscoped\n // ones; evaluation binds the scope element the scoped types promise.\n const scopedOptions = options as ScopedStepOptions<\n TState,\n unknown,\n TActor,\n unknown,\n TCommit\n >\n guard = {\n ...(scopedOptions.requires !== undefined && {\n requires: eraseScopedConditionMap(scopedOptions.requires),\n }),\n ...(scopedOptions.permits !== undefined && {\n permits: eraseScopedConditionMap(scopedOptions.permits),\n }),\n }\n } else {\n const unscoped = options as StepOptions<TState, TActor, unknown, TCommit>\n validateConditionMap(name, 'requires', unscoped.requires, true)\n validateConditionMap(name, 'permits', unscoped.permits, true)\n guard = {\n ...(unscoped.requires !== undefined && { requires: unscoped.requires }),\n ...(unscoped.permits !== undefined && { permits: unscoped.permits }),\n }\n }\n\n return {\n name,\n title: options.title ?? null,\n description: options.description ?? null,\n guard,\n scope,\n input: options.input ?? null,\n retry: normalizeRetry(name, options.retry),\n // The one erasure cast for handlers: the authored context (typed input,\n // typed scope element) is what the execution lifecycle constructs; see\n // ErasedStepHandler.\n handler: options.handler as unknown as ErasedStepHandler<\n TState,\n TActor,\n TCommit\n >,\n }\n}\n\n/**\n * The `step()` authoring surface with the case's state and actor types fixed.\n *\n * Same two call shapes as `step()` — scoped first, discriminated by the\n * presence of `scope` — but `TState`/`TActor` are already substituted, so\n * only the per-step generics (scope element, input) remain to be inferred.\n * That is what makes annotation-free authoring work: conditions and handlers\n * no longer participate in inferring the state type, they just receive it\n * contextually, and a scoped step's element type anchors on `scope.select`\n * alone (so `ctx.scope` is the element, with no `undefined` to narrow away).\n */\nexport interface BoundStep<TState, TActor = unknown, TCommit = unknown> {\n <TElement, TInput = undefined>(\n options: ScopedStepOptions<TState, TElement, TActor, TInput, TCommit>,\n ): StepDefinition<TState, TActor, TCommit>\n <TInput = undefined>(\n options: StepOptions<TState, TActor, TInput, TCommit>,\n ): StepDefinition<TState, TActor, TCommit>\n}\n\n/**\n * A value-level carrier for `stepsOf`'s actor type — nothing but the type.\n * Exists because `TActor` has no value to be inferred from (a case's actor\n * shape is app-defined and never materializes at definition time), and\n * spelling it as a type argument would force spelling the schema's type too\n * (TypeScript has no partial type-argument inference).\n */\nexport interface ActorMarker<TActor> {\n readonly __actor?: TActor\n}\n\nconst ACTOR_MARKER: ActorMarker<never> = Object.freeze({})\n\n/** Name the actor type of a `stepsOf` factory: `stepsOf(PurchaseState, actor<PurchaseActor>())`. */\nexport const actor = <TActor>(): ActorMarker<TActor> => ACTOR_MARKER\n\n/**\n * Bind `step()` to a case's state schema — the schema-anchored authoring\n * factory.\n *\n * ```ts\n * const purchaseStep = stepsOf(PurchaseState, actor<PurchaseActor>())\n *\n * purchaseStep({\n * name: 'issue-funding-call',\n * requires: { escrowReady: s => s.escrow.status === 'open' }, // s: inferred from the schema\n * permits: { isOrganizer: (_s, ctx) => hasRole(ctx.actor, 'organizer') },\n * handler: async s => s,\n * })\n *\n * purchaseStep({\n * name: 'escalate-verification',\n * scope: { select: s => s.buyers.filter(b => b.verification.status === 'review'), key: b => b.id },\n * requires: { flagged: (_s, ctx) => ctx.scope.verification.flaggedAt !== null }, // ctx.scope: Buyer\n * handler: async s => s,\n * })\n * ```\n *\n * The state type is derived from the schema *value* — the same\n * `InferOutput` derivation `caseType` performs — so the factory and the case\n * type are anchored to one declaration and cannot drift apart: the state a\n * condition sees is definitionally the state the engine validates against.\n * The per-condition annotations the bare `step()` needs\n * (`(s: Purchase) => …`) disappear, because `TState` is no longer inferred\n * from the options.\n *\n * The second argument exists only to name the actor type and carries no\n * runtime information; omit it for an untyped actor. Define one factory per\n * case type module, next to the schema, and author every step of that case\n * type through it.\n *\n * Returns `step` itself, re-typed — a step authored through the factory is\n * bit-for-bit an ordinary step definition. `step` is deliberately not part\n * of the package barrel: this factory is the public authoring surface, and\n * a helper that builds steps generically should accept a\n * {@link BoundStep} rather than reach for the unbound `step`.\n * Throws at definition time when `state` is not a Standard Schema, like\n * every other malformed-definition case in this module.\n */\nexport const stepsOf = <\n S extends StandardSchemaV1,\n TActor = unknown,\n TCommit = unknown,\n>(\n state: S,\n _actor?: ActorMarker<TActor>,\n _commit?: CommitContextMarker<TCommit>,\n): BoundStep<StandardSchemaV1.InferOutput<S>, TActor, TCommit> => {\n if (!isStandardSchema(state)) {\n throw new TypeError(\n 'stepsOf: state must be a Standard Schema (e.g. a zod schema)',\n )\n }\n return step\n}\n\n/** A step's declared input failed validation against its input schema. */\nexport class StepInputValidationError extends AffordanceError {\n readonly stepName: string\n readonly issues: readonly StandardSchemaV1.Issue[]\n\n constructor(stepName: string, issues: readonly StandardSchemaV1.Issue[]) {\n super(\n 'invalid-input',\n `invalid input for step '${stepName}': ${issues.map((issue) => issue.message).join('; ')}`,\n )\n this.name = 'StepInputValidationError'\n this.stepName = stepName\n this.issues = issues\n }\n}\n\n/**\n * Validate a step's input against its declared input schema — the validation\n * plumbing the execution lifecycle runs before invoking the handler (\"validated before the\n * handler runs\"). Returns the schema *output* (defaults applied). A step\n * without an input schema accepts only `undefined` and yields `undefined`;\n * anything else is a caller bug and throws.\n */\nexport const validateStepInput = async <TState, TActor, TCommit>(\n definition: StepDefinition<TState, TActor, TCommit>,\n input: unknown,\n): Promise<unknown> => {\n if (definition.input === null) {\n if (input !== undefined) {\n throw new StepInputValidationError(definition.name, [\n { message: 'step declares no input schema, but input was provided' },\n ])\n }\n return undefined\n }\n const result = await definition.input['~standard'].validate(input)\n if (result.issues)\n throw new StepInputValidationError(definition.name, result.issues)\n return result.value\n}\n\n/** Name the transaction/repository context supplied to onCommit callbacks. */\nexport interface CommitContextMarker<TCommit> {\n readonly __commit?: TCommit\n}\nexport const commitContext = <TCommit>(): CommitContextMarker<TCommit> => ({})\n"]}
|
package/dist/model/target.d.ts
CHANGED
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
* with one deliberate exception — a `defective-selector` failure is
|
|
36
36
|
* *answered*, because the listing published that exact link).
|
|
37
37
|
*/
|
|
38
|
-
import { SCOPE_FAILURE_CONDITION } from '@affordance/contract';
|
|
39
38
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
40
39
|
import type { GuardEvaluation, Instant } from '../guards/index.js';
|
|
41
40
|
import type { CaseTypeDefinition } from './casetype.js';
|
|
@@ -44,11 +43,9 @@ import type { StepDefinition } from './step.js';
|
|
|
44
43
|
/**
|
|
45
44
|
* The synthetic condition name under which a throwing or malformed scope
|
|
46
45
|
* selector is reported. `$`-prefixed so it can never collide with an
|
|
47
|
-
* author's condition names.
|
|
48
|
-
* clients through `blocked[].unmet[].name`, so it is wire vocabulary, and
|
|
49
|
-
* the wire owns it; re-exported here for the engine's own consumers.
|
|
46
|
+
* author's condition names. This is engine guard vocabulary.
|
|
50
47
|
*/
|
|
51
|
-
export
|
|
48
|
+
export declare const SCOPE_FAILURE_CONDITION = "$scope";
|
|
52
49
|
/**
|
|
53
50
|
* A defective selection as a full evaluation record — the
|
|
54
51
|
* possible/permitted/available verdict stated once. The selection failed, so
|
|
@@ -80,8 +77,8 @@ export interface ScopeBinding {
|
|
|
80
77
|
* A step addressed by name (× scope key, if scoped): the step definition and
|
|
81
78
|
* its scope binding, resolved against a given Case State.
|
|
82
79
|
*/
|
|
83
|
-
export interface StepTarget<TState, TActor = unknown> {
|
|
84
|
-
readonly step: StepDefinition<TState, TActor>;
|
|
80
|
+
export interface StepTarget<TState, TActor = unknown, TCommit = unknown> {
|
|
81
|
+
readonly step: StepDefinition<TState, TActor, TCommit>;
|
|
85
82
|
/**
|
|
86
83
|
* The Case State the target was resolved against — the document its guard
|
|
87
84
|
* is evaluated over. Carried on the target so a binding can never be
|
|
@@ -92,8 +89,8 @@ export interface StepTarget<TState, TActor = unknown> {
|
|
|
92
89
|
readonly binding: ScopeBinding | null;
|
|
93
90
|
}
|
|
94
91
|
/** The outcome of scope fan-out: the step's targets, or why selection produced none. */
|
|
95
|
-
export interface TargetSelection<TState, TActor = unknown> {
|
|
96
|
-
readonly targets: readonly StepTarget<TState, TActor>[];
|
|
92
|
+
export interface TargetSelection<TState, TActor = unknown, TCommit = unknown> {
|
|
93
|
+
readonly targets: readonly StepTarget<TState, TActor, TCommit>[];
|
|
97
94
|
/** The selection failure — a defective selector — or `null` when selection succeeded. */
|
|
98
95
|
readonly failure: {
|
|
99
96
|
readonly reason: string;
|
|
@@ -109,7 +106,7 @@ export interface TargetSelection<TState, TActor = unknown> {
|
|
|
109
106
|
* defective. {@link ScopeKeyError} — identity corruption — propagates: it is
|
|
110
107
|
* never a selection failure, and no caller may absorb it into "no targets".
|
|
111
108
|
*/
|
|
112
|
-
export declare const selectTargets: <TState, TActor>(step: StepDefinition<TState, TActor>, state: TState) => TargetSelection<TState, TActor>;
|
|
109
|
+
export declare const selectTargets: <TState, TActor, TCommit>(step: StepDefinition<TState, TActor, TCommit>, state: TState) => TargetSelection<TState, TActor, TCommit>;
|
|
113
110
|
/**
|
|
114
111
|
* Why an address does not resolve, by kind. Each failure carries the error
|
|
115
112
|
* the loud filter would throw, so the diagnosis (including the
|
|
@@ -140,8 +137,8 @@ export type TargetAddressFailure = {
|
|
|
140
137
|
readonly error: ScopeKeyError;
|
|
141
138
|
};
|
|
142
139
|
/** The outcome of addressing a step: the target, or the precise failure. */
|
|
143
|
-
export type TargetAddress<TState, TActor = unknown> = {
|
|
144
|
-
readonly target: StepTarget<TState, TActor>;
|
|
140
|
+
export type TargetAddress<TState, TActor = unknown, TCommit = unknown> = {
|
|
141
|
+
readonly target: StepTarget<TState, TActor, TCommit>;
|
|
145
142
|
readonly failure: null;
|
|
146
143
|
} | {
|
|
147
144
|
readonly target: null;
|
|
@@ -156,7 +153,7 @@ export type TargetAddress<TState, TActor = unknown> = {
|
|
|
156
153
|
* corruption, from {@link selectScope}) still propagates — no filter may
|
|
157
154
|
* absorb it.
|
|
158
155
|
*/
|
|
159
|
-
export declare const addressTarget: <S extends StandardSchemaV1, TActor>(definition: CaseTypeDefinition<S, TActor>, state: StandardSchemaV1.InferOutput<S>, stepName: string, scopeKey?: string) => TargetAddress<StandardSchemaV1.InferOutput<S>, TActor>;
|
|
156
|
+
export declare const addressTarget: <S extends StandardSchemaV1, TActor, TCommit>(definition: CaseTypeDefinition<S, TActor, TCommit>, state: StandardSchemaV1.InferOutput<S>, stepName: string, scopeKey?: string) => TargetAddress<StandardSchemaV1.InferOutput<S>, TActor, TCommit>;
|
|
160
157
|
/**
|
|
161
158
|
* Resolve "step X (of element K) on this state" — the addressing shared by
|
|
162
159
|
* `explain` (a targeted probe) and the execution lifecycle's claim.
|
|
@@ -166,10 +163,10 @@ export declare const addressTarget: <S extends StandardSchemaV1, TActor>(definit
|
|
|
166
163
|
* failing to address throws with its precise message. Addressing a step you
|
|
167
164
|
* cannot name is a caller bug, not a blocked affordance.
|
|
168
165
|
*/
|
|
169
|
-
export declare const resolveTarget: <S extends StandardSchemaV1, TActor>(definition: CaseTypeDefinition<S, TActor>, state: StandardSchemaV1.InferOutput<S>, stepName: string, scopeKey?: string) => StepTarget<StandardSchemaV1.InferOutput<S>, TActor>;
|
|
166
|
+
export declare const resolveTarget: <S extends StandardSchemaV1, TActor, TCommit>(definition: CaseTypeDefinition<S, TActor, TCommit>, state: StandardSchemaV1.InferOutput<S>, stepName: string, scopeKey?: string) => StepTarget<StandardSchemaV1.InferOutput<S>, TActor, TCommit>;
|
|
170
167
|
/**
|
|
171
168
|
* Evaluate one addressed step's guard against the state it was resolved on —
|
|
172
169
|
* the single evaluation shared by `explain` and the claim, so the enforcement
|
|
173
170
|
* moment and the explanation of it can never drift apart.
|
|
174
171
|
*/
|
|
175
|
-
export declare const evaluateTarget: <TState, TActor>(target: StepTarget<TState, TActor>, ctx: ComputationContext<TActor>) => GuardEvaluation;
|
|
172
|
+
export declare const evaluateTarget: <TState, TActor, TCommit>(target: StepTarget<TState, TActor, TCommit>, ctx: ComputationContext<TActor>) => GuardEvaluation;
|
package/dist/model/target.js
CHANGED
|
@@ -35,18 +35,15 @@
|
|
|
35
35
|
* with one deliberate exception — a `defective-selector` failure is
|
|
36
36
|
* *answered*, because the listing published that exact link).
|
|
37
37
|
*/
|
|
38
|
-
import { SCOPE_FAILURE_CONDITION } from '@affordance/contract';
|
|
39
38
|
import { thrownMessage } from '../errors.js';
|
|
40
39
|
import { evaluateGuard } from '../guards/index.js';
|
|
41
40
|
import { ScopeKeyError, UnknownStepError } from './errors.js';
|
|
42
41
|
/**
|
|
43
42
|
* The synthetic condition name under which a throwing or malformed scope
|
|
44
43
|
* selector is reported. `$`-prefixed so it can never collide with an
|
|
45
|
-
* author's condition names.
|
|
46
|
-
* clients through `blocked[].unmet[].name`, so it is wire vocabulary, and
|
|
47
|
-
* the wire owns it; re-exported here for the engine's own consumers.
|
|
44
|
+
* author's condition names. This is engine guard vocabulary.
|
|
48
45
|
*/
|
|
49
|
-
export
|
|
46
|
+
export const SCOPE_FAILURE_CONDITION = '$scope';
|
|
50
47
|
/**
|
|
51
48
|
* The synthetic `$scope` entry as a condition result — the one spelling of
|
|
52
49
|
* how a failed scope selection reports into an evaluation-shaped record: a
|