@boboddy/sdk 0.3.1 → 0.4.2

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.
@@ -1,6 +1,7 @@
1
1
  import { type ZodType } from "zod";
2
2
  import type { StepDefinitionSpec, TypedStepDefinitionSpec } from "../steps/define-step";
3
3
  import { type AdvancementPolicy, type SerializedAdvancementPolicy, type SerializedComputedSignalDefinition } from "../advancement-policies/define-advancement-policy";
4
+ import { type CohortAdvancementPolicy, type SerializedCohortAdvancementPolicy, type SerializedStepSignalsListDefinition } from "../advancement-policies/cohort-advancement-policy";
4
5
  export type { AdvancementPolicy, PipelineStepComputedSignalType, } from "../advancement-policies/define-advancement-policy";
5
6
  export { Computed, Rule, } from "../advancement-policies/define-advancement-policy";
6
7
  type AnyTypedStep = TypedStepDefinitionSpec<any, any, any, any>;
@@ -25,7 +26,31 @@ export type LiteralBinding = {
25
26
  source: "literal";
26
27
  value: unknown;
27
28
  };
28
- export type AnyBinding = PipelineInputBinding | WorkItemBinding | StepSignalBinding | StepOutputBinding | LiteralBinding;
29
+ /**
30
+ * `ctx.signalsList(fanOutStep)`'s binding (issue #167): reaches a fan-out's
31
+ * whole cohort — every terminal branch's own signals + output — from a
32
+ * later, non-adjacent step's input mapper. Resolved server-side against
33
+ * `ResolvedNodeInputContext.cohorts[stepKey]` (an array of
34
+ * `{ branchIndex, signals, outputJson }`, sorted by `branchIndex`).
35
+ */
36
+ export type SignalsListBinding = {
37
+ source: "signals_list";
38
+ fanOutStep: AnyTypedStep;
39
+ };
40
+ /**
41
+ * `.fanOutStep(step, config)`'s own `input` ctx's `item` binding (issue
42
+ * #167): resolves server-side, per branch, to that branch's own item value
43
+ * — the element of the array `config.over` names, when `over` resolves to
44
+ * an array (count-only mode has no `item` to bind, both at the type level
45
+ * — see `FanOutItemType` — and at the wire level, since no `fanOut` node
46
+ * config would carry an `item` binding for it). Carries no extra fields:
47
+ * the branch index alone (implicit in which branch is executing) is enough
48
+ * to resolve the right element server-side.
49
+ */
50
+ export type FanOutItemBinding = {
51
+ source: "fan_out_item";
52
+ };
53
+ export type AnyBinding = PipelineInputBinding | WorkItemBinding | StepSignalBinding | StepOutputBinding | LiteralBinding | SignalsListBinding | FanOutItemBinding;
29
54
  export type PipelineStepConfig<TStep extends AnyTypedStep = AnyTypedStep> = {
30
55
  step: TStep;
31
56
  /** Maps each step input field to an input source. Extra keys are ignored at runtime. */
@@ -43,7 +68,50 @@ export type PipelineStepConfig<TStep extends AnyTypedStep = AnyTypedStep> = {
43
68
  */
44
69
  advancement?: AdvancementPolicy<TStep["__signalKeys"]>;
45
70
  };
46
- type SerializedBinding = {
71
+ /**
72
+ * `.fanOutStep(step, config)`'s node config (issue #167): a `fanOut` node
73
+ * whose `stepDefinitionId`/`stepDefinitionVersion` template is `fanOutStep`
74
+ * — the template every branch executes — and whose `advanceEach` policy
75
+ * each branch's own result is evaluated against. Pushes exactly one
76
+ * `fanOut` node onto the pipeline's node sequence (paired with exactly one
77
+ * `PipelineCohortGateNodeConfig` immediately after it — see `.advanceAll()`
78
+ * — never more than the fan-out+gate pair itself).
79
+ *
80
+ * Named `*StepConfig` (rather than a bare `PipelineFanOutConfig`) to reserve
81
+ * room for a future sibling `PipelineFanOutSubPipelineConfig` — a fan-out
82
+ * whose template is a whole sub-pipeline rather than a single step. That
83
+ * sibling is out of scope for issue #167 and is not implemented here.
84
+ */
85
+ export type PipelineFanOutStepConfig<TStep extends AnyTypedStep = AnyTypedStep> = {
86
+ nodeType: "fanOut";
87
+ fanOutStep: TStep;
88
+ overSignalKey: string;
89
+ input?: Partial<{
90
+ [K in keyof NonNullable<TStep["__inputType"]> & string]: AnyBinding;
91
+ }>;
92
+ timeout?: number | null;
93
+ advanceEach?: CohortAdvancementPolicy;
94
+ };
95
+ /**
96
+ * `.advanceAll(callback)`'s node config (issue #167): the pure decision
97
+ * gate that aggregates a fan-out's cohort back together — no work of its
98
+ * own, so no `step`/`input`/`timeout`. `nodeKey` is derived by the builder
99
+ * (`${fanOutStep.key}__cohortGate`), not user-supplied.
100
+ */
101
+ export type PipelineCohortGateNodeConfig = {
102
+ nodeType: "cohortGate";
103
+ nodeKey: string;
104
+ advanceAll?: CohortAdvancementPolicy;
105
+ stepSignalsListDefinitions?: SerializedStepSignalsListDefinition[];
106
+ };
107
+ /**
108
+ * A single entry in a pipeline's declaration-order node sequence: an
109
+ * ordinary step, or one half of a fan-out+cohort-gate pair. Discriminated
110
+ * by the presence/value of `nodeType` (absent means a plain step) rather
111
+ * than a `kind` field, so `PipelineStepConfig` itself needs no change.
112
+ */
113
+ export type PipelineNodeConfig = PipelineStepConfig | PipelineFanOutStepConfig | PipelineCohortGateNodeConfig;
114
+ export type SerializedBinding = {
47
115
  source: "pipeline_input";
48
116
  path: string;
49
117
  } | {
@@ -59,6 +127,39 @@ type SerializedBinding = {
59
127
  } | {
60
128
  source: "literal";
61
129
  value: unknown;
130
+ } | {
131
+ source: "signals_list";
132
+ stepKey: string;
133
+ } | {
134
+ source: "fan_out_item";
135
+ };
136
+ export type NodeDefinitionKind = "step" | "fanOut" | "cohortGate";
137
+ export type NodeDefinitionSpec = {
138
+ /** Unique within the pipeline; equals `stepKey` for a step/fanOut node, or the builder-derived gate key for a cohortGate node. */
139
+ nodeKey: string;
140
+ kind: NodeDefinitionKind;
141
+ /** `step`/`fanOut` only — the step template's key/version. Absent on a `cohortGate` node (it produces no work of its own). */
142
+ stepKey?: string;
143
+ stepName?: string;
144
+ stepDescription?: string | null;
145
+ inputBindingsJson?: Record<string, SerializedBinding>;
146
+ timeoutSeconds?: number | null;
147
+ /** `step` only. */
148
+ advancementPolicyDefinition?: SerializedAdvancementPolicy;
149
+ /** `step` only. */
150
+ computedSignalDefinitions?: SerializedComputedSignalDefinition[];
151
+ /** `fanOut` only — the signal its branch cardinality is resolved from. */
152
+ overSignalKey?: string;
153
+ /** `fanOut` only — each branch's own continue/block decision. */
154
+ advanceEachPolicyDefinition?: SerializedCohortAdvancementPolicy;
155
+ /** `cohortGate` only — the whole cohort's continue/block decision. */
156
+ advanceAllPolicyDefinition?: SerializedCohortAdvancementPolicy;
157
+ /** `cohortGate` only — every `ctx.stepSignalsList`-derived value `advanceAll`'s rules may reference. */
158
+ stepSignalsListDefinitions?: SerializedStepSignalsListDefinition[];
159
+ };
160
+ export type DependencyEdgeSpec = {
161
+ fromNodeKey: string;
162
+ toNodeKey: string;
62
163
  };
63
164
  export type PipelineDefinitionSpec = {
64
165
  key: string;
@@ -67,16 +168,8 @@ export type PipelineDefinitionSpec = {
67
168
  version: number;
68
169
  status: "draft" | "active" | "archived";
69
170
  inputSchemaJson?: Record<string, unknown> | null;
70
- steps: Array<{
71
- stepKey: string;
72
- stepName: string;
73
- stepDescription: string | null;
74
- position: number;
75
- inputBindingsJson: Record<string, SerializedBinding>;
76
- timeoutSeconds: number | null;
77
- advancementPolicyDefinition: SerializedAdvancementPolicy;
78
- computedSignalDefinitions: SerializedComputedSignalDefinition[];
79
- }>;
171
+ nodeDefinitions: NodeDefinitionSpec[];
172
+ dependencyEdges: DependencyEdgeSpec[];
80
173
  /** Step specs referenced by this pipeline. Used by the push command to auto-push steps that aren't explicitly exported. */
81
174
  _stepDefinitions?: StepDefinitionSpec[];
82
175
  };
@@ -87,7 +180,7 @@ export type DefinePipelineInput = {
87
180
  version?: number;
88
181
  status?: "draft" | "active";
89
182
  input?: ZodType | null;
90
- steps: ReadonlyArray<PipelineStepConfig>;
183
+ nodes: ReadonlyArray<PipelineNodeConfig>;
91
184
  pipelineInputBindings?: Record<string, AnyBinding>;
92
185
  };
93
186
  export declare function buildPipelineSpec(config: DefinePipelineInput): PipelineDefinitionSpec;
@@ -0,0 +1,66 @@
1
+ import type { ZodType } from "zod";
2
+ import { type AdvanceAllCtx, type AdvanceAllResult, type AdvanceEachCtx, type AdvanceEachResult } from "../advancement-policies/cohort-fluent-rules";
3
+ import { type AnyTypedStep, type FanOutInputCtx, type IsAny, type LastSignalKeys, type PipelineMeta, type RequiredInputKeys } from "./builder-helpers";
4
+ import type { AnyBinding, PipelineNodeConfig } from "./define-pipeline";
5
+ import { PipelineStepBuilder } from "./builder";
6
+ /**
7
+ * `.fanOutStep()` (issue #167) split out of `builder.ts` (which would
8
+ * otherwise exceed this repo's `max-lines` limit) — genuinely mutually
9
+ * referential with `PipelineStepBuilder` (`beginFanOut` both is called from
10
+ * `PipelineStepBuilder.fanOutStep()` and returns a new one), so the import
11
+ * cycle is structural, not accidental.
12
+ */
13
+ export type FanOutInputMapping<S extends AnyTypedStep> = Partial<Record<string, AnyBinding>> & (IsAny<S["__inputType"]> extends true ? unknown : S["__inputType"] extends object ? {
14
+ [K in RequiredInputKeys<S["__inputType"]>]: AnyBinding;
15
+ } : unknown);
16
+ /**
17
+ * `.fanOutStep(step, config)`'s sole config argument — `step` itself is a
18
+ * separate positional argument (mirroring `.step(step, options)`), not a
19
+ * field on this object. `K` is inferred from the literal value of
20
+ * `config.over` in the same pass TypeScript type-checks `config.input`,
21
+ * the same mechanism `builder.ts`'s `StepOptions` already relies on for
22
+ * `.step()` — see that type's doc comment for why overloads are avoided
23
+ * here too.
24
+ */
25
+ export type FanOutStepConfig<TInput extends ZodType, TSteps extends ReadonlyArray<AnyTypedStep>, TFanOuts extends ReadonlyArray<AnyTypedStep>, S extends AnyTypedStep, K extends LastSignalKeys<TSteps> = LastSignalKeys<TSteps>> = {
26
+ /**
27
+ * The signal to resolve each branch from — constrained to the most
28
+ * recent ordinary step's declared signal keys (`TSteps`'s last element).
29
+ * Note this is the last *step*, not necessarily the fan-out's immediate
30
+ * graph predecessor: chaining a second `.fanOutStep()` right after
31
+ * `.advanceAll()` (with no intervening `.step()`) still only offers this
32
+ * same step's signals, since a `cohortGate` node produces no signals of
33
+ * its own to resolve from.
34
+ *
35
+ * A number-typed signal resolves a fixed branch count with no `item` on
36
+ * the input ctx (count-only mode); an array-typed signal resolves branch
37
+ * count from the array's length and adds a typed `item` to the input
38
+ * ctx for each branch.
39
+ */
40
+ over: K;
41
+ input?: (ctx: FanOutInputCtx<TInput, TSteps, TFanOuts, K>) => FanOutInputMapping<S>;
42
+ /**
43
+ * Every branch's own continue/block decision (even the default "always
44
+ * continue" must be declared explicitly) — evaluated once per branch
45
+ * against that branch's own signals.
46
+ */
47
+ advance: (ctx: AdvanceEachCtx<S["__signalKeys"], S["__signalTypeMap"]>) => AdvanceEachResult;
48
+ /**
49
+ * The whole-cohort decision, evaluated once the fan-out's branches have
50
+ * settled — a pure gate, not a step: nothing besides the fan-out+gate
51
+ * pair itself is appended to the pipeline's node sequence.
52
+ */
53
+ advanceAll: (ctx: AdvanceAllCtx) => AdvanceAllResult;
54
+ timeout?: number | null;
55
+ };
56
+ /**
57
+ * Builds and pushes the `fanOut`+`cohortGate` node pair, resolves both the
58
+ * per-branch (`advance`) and whole-cohort (`advanceAll`) policies from
59
+ * `config`, and returns straight into a `PipelineStepBuilder` — mirroring
60
+ * how `pushStep` in `builder.ts` resolves a step's `advance` policy inline
61
+ * rather than through a chained `.advance()` call on an intermediate
62
+ * builder. Called from `PipelineStepBuilder.fanOutStep()` — factored out
63
+ * here (rather than inlined there) purely to keep the node-construction
64
+ * logic next to the types it feeds.
65
+ */
66
+ export declare function beginFanOut<TInput extends ZodType, TSteps extends ReadonlyArray<AnyTypedStep>, TFanOuts extends ReadonlyArray<AnyTypedStep>, S extends AnyTypedStep, K extends LastSignalKeys<TSteps> = LastSignalKeys<TSteps>>(inputSchema: TInput, meta: Omit<PipelineMeta<TInput>, "additionalPipelineInput" | "additionalStepInput">, nodes: PipelineNodeConfig[], pipelineInputBindings: Record<string, AnyBinding>, pipelineStepInputBindings: Record<string, AnyBinding>, step: S, config: FanOutStepConfig<TInput, TSteps, TFanOuts, S, K>): PipelineStepBuilder<TInput, TSteps, [...TFanOuts, S]>;