@boboddy/sdk 0.4.2 → 0.5.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/dist/client.js +0 -23
- package/dist/definitions/advancement-policies/define-advancement-policy.d.ts +30 -5
- package/dist/definitions/advancement-policies/index.js +11 -1
- package/dist/definitions/pipelines/bindings.d.ts +62 -0
- package/dist/definitions/pipelines/builder-helpers.d.ts +36 -94
- package/dist/definitions/pipelines/chain-graph.d.ts +15 -14
- package/dist/definitions/pipelines/compile-node-definitions.d.ts +27 -0
- package/dist/definitions/pipelines/define-default-pipeline-assignment.d.ts +64 -14
- package/dist/definitions/pipelines/define-pipeline.d.ts +101 -154
- package/dist/definitions/pipelines/index.d.ts +1 -2
- package/dist/definitions/pipelines/index.js +485 -585
- package/dist/definitions/pipelines/node-input-ctx.d.ts +31 -0
- package/dist/definitions/pipelines/pipeline-definitions-client.d.ts +42 -12
- package/dist/definitions/pipelines/pipeline-states.d.ts +104 -0
- package/dist/definitions/pipelines/work-item-fields.d.ts +98 -0
- package/dist/definitions/steps/define-code-step.d.ts +50 -0
- package/dist/definitions/steps/define-step.d.ts +40 -8
- package/dist/definitions/steps/index.d.ts +1 -0
- package/dist/definitions/steps/index.js +41 -24
- package/dist/definitions/steps/step-definitions-client.d.ts +47 -11
- package/dist/definitions/validation/index.js +15148 -94
- package/dist/definitions/validation/validate-definition-specs.d.ts +19 -1
- package/dist/generated/index.d.ts +1 -1
- package/dist/generated/sdk.gen.d.ts +1 -4
- package/dist/generated/types.gen.d.ts +469 -875
- package/dist/index.js +525 -585
- package/dist/push/collect-definitions.d.ts +40 -1
- package/dist/push/index.d.ts +2 -2
- package/dist/push/index.js +756 -664
- package/dist/step-execution-plane-client.d.ts +9 -4
- package/package.json +2 -2
- package/dist/definitions/pipelines/builder.d.ts +0 -80
- package/dist/definitions/pipelines/fan-out-builder.d.ts +0 -66
- package/dist/definitions/pipelines/input-accessor.d.ts +0 -25
package/dist/client.js
CHANGED
|
@@ -1093,29 +1093,6 @@ class PipelineDefinitions extends HeyApiClient {
|
|
|
1093
1093
|
unarchivePipelineDefinition(options) {
|
|
1094
1094
|
return (options.client ?? this.client).put({ url: "/api/pipeline-definitions/{pipelineDefinitionId}/unarchive", ...options });
|
|
1095
1095
|
}
|
|
1096
|
-
addPipelineStep(options) {
|
|
1097
|
-
return (options.client ?? this.client).post({
|
|
1098
|
-
url: "/api/pipeline-definitions/{pipelineDefinitionId}/steps",
|
|
1099
|
-
...options,
|
|
1100
|
-
headers: {
|
|
1101
|
-
"Content-Type": "application/json",
|
|
1102
|
-
...options.headers
|
|
1103
|
-
}
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
removePipelineStep(options) {
|
|
1107
|
-
return (options.client ?? this.client).delete({ url: "/api/pipeline-definitions/{pipelineDefinitionId}/steps/{pipelineStepDefinitionId}", ...options });
|
|
1108
|
-
}
|
|
1109
|
-
updatePipelineStep(options) {
|
|
1110
|
-
return (options.client ?? this.client).put({
|
|
1111
|
-
url: "/api/pipeline-definitions/{pipelineDefinitionId}/steps/{pipelineStepDefinitionId}",
|
|
1112
|
-
...options,
|
|
1113
|
-
headers: {
|
|
1114
|
-
"Content-Type": "application/json",
|
|
1115
|
-
...options.headers
|
|
1116
|
-
}
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
1096
|
setPipelineStepAdvancementPolicy(options) {
|
|
1120
1097
|
return (options.client ?? this.client).put({
|
|
1121
1098
|
url: "/api/pipeline-definitions/{pipelineDefinitionId}/steps/{pipelineStepDefinitionId}/advancement-policy",
|
|
@@ -184,13 +184,23 @@ declare function all<TSignalKeys extends string>(conditions: RuleCondition<TSign
|
|
|
184
184
|
declare function any<TSignalKeys extends string>(conditions: RuleCondition<TSignalKeys>[]): AnyCondition<TSignalKeys>;
|
|
185
185
|
declare function any<TSignalKeys extends string>(conditions: RuleCondition<TSignalKeys>[], outcome: AdvancementOutcome): Rule<TSignalKeys>;
|
|
186
186
|
/**
|
|
187
|
-
*
|
|
188
|
-
* `
|
|
187
|
+
* When called with only `signal`/`operator`/`value`: returns a bare
|
|
188
|
+
* `SignalCondition` — used by a `choice` node's `choices[].when`, a `loop`
|
|
189
|
+
* node's `until`, and a `step` node's `blockWhen` (see
|
|
190
|
+
* docs/research/flat-pipeline-sdk-and-visual-designer.md §4/§5), none of
|
|
191
|
+
* which attach an outcome of their own (the routing target / block
|
|
192
|
+
* decision lives on the surrounding state, not on the condition).
|
|
193
|
+
*
|
|
194
|
+
* When called with `signal`/`operator`/`value`/`outcome`: returns a
|
|
195
|
+
* top-level `Rule` — shorthand for
|
|
196
|
+
* `Rule.all([Rule.signal(signal, operator, value)], outcome)`, used by the
|
|
197
|
+
* (legacy) full step-advancement-policy shape.
|
|
189
198
|
*
|
|
190
199
|
* @example
|
|
191
|
-
* Rule.when("
|
|
192
|
-
* Rule.when("
|
|
200
|
+
* Rule.when("severity", "equal", "critical") // bare condition
|
|
201
|
+
* Rule.when("passed", "equal", true, "continue") // full rule
|
|
193
202
|
*/
|
|
203
|
+
declare function when<TSignalKeys extends string>(signal: TSignalKeys | InlineComputedSignal<string, TSignalKeys>, operator: ConditionOperator, value: unknown): SignalCondition<TSignalKeys>;
|
|
194
204
|
declare function when<TSignalKeys extends string>(signal: TSignalKeys | InlineComputedSignal<string, TSignalKeys>, operator: ConditionOperator, value: unknown, outcome: AdvancementOutcome): Rule<TSignalKeys>;
|
|
195
205
|
/**
|
|
196
206
|
* All factories for building advancement rules.
|
|
@@ -252,7 +262,15 @@ type SerializedConditionGroup = {
|
|
|
252
262
|
all?: SerializedCondition[];
|
|
253
263
|
any?: SerializedCondition[];
|
|
254
264
|
};
|
|
255
|
-
|
|
265
|
+
/**
|
|
266
|
+
* The bare-condition wire format (no outcome) — structurally identical to
|
|
267
|
+
* `packages/core`'s `RuleCondition`/`rule-condition.ts` (leaves:
|
|
268
|
+
* `{fact,operator,value}`, groups: `{all:[...]}`/`{any:[...]}`). Reused
|
|
269
|
+
* verbatim for a `choice` node's `choices[].conditionJson` and a `loop`
|
|
270
|
+
* node's `untilConditionJson` (see
|
|
271
|
+
* docs/research/flat-pipeline-sdk-and-visual-designer.md §7.2).
|
|
272
|
+
*/
|
|
273
|
+
export type SerializedCondition = SerializedLeafCondition | SerializedConditionGroup;
|
|
256
274
|
type SerializedRule = {
|
|
257
275
|
conditions: SerializedConditionGroup;
|
|
258
276
|
event: {
|
|
@@ -269,6 +287,13 @@ export type SerializedAdvancementPolicy = {
|
|
|
269
287
|
defaultEventParamsJson: Record<string, unknown> | null;
|
|
270
288
|
allowedEventTypes: AdvancementEventType[];
|
|
271
289
|
};
|
|
290
|
+
/**
|
|
291
|
+
* Serializes an authored `RuleCondition` (leaf or `all`/`any` group) to its
|
|
292
|
+
* bare wire format — no outcome attached. Reused directly by `choice`'s
|
|
293
|
+
* `choices[].when`, `loop`'s `until`, and `step`'s `blockWhen` (see
|
|
294
|
+
* `SerializedCondition`'s own doc comment).
|
|
295
|
+
*/
|
|
296
|
+
export declare function serializeCondition(condition: RuleCondition): SerializedCondition;
|
|
272
297
|
export declare function serializeAdvancementPolicy(policy: AdvancementPolicy | undefined): SerializedAdvancementPolicy;
|
|
273
298
|
/** Wire-format computed signal definition emitted on the pipeline step. */
|
|
274
299
|
export type SerializedComputedSignalDefinition = {
|
|
@@ -52,10 +52,19 @@ function any(conditions, outcome) {
|
|
|
52
52
|
return { _tag: "rule", mode: "any", conditions, outcome };
|
|
53
53
|
}
|
|
54
54
|
function when(signal2, operator, value, outcome) {
|
|
55
|
+
const condition = {
|
|
56
|
+
_tag: "signal",
|
|
57
|
+
signal: signal2,
|
|
58
|
+
operator,
|
|
59
|
+
value
|
|
60
|
+
};
|
|
61
|
+
if (outcome === undefined) {
|
|
62
|
+
return condition;
|
|
63
|
+
}
|
|
55
64
|
return {
|
|
56
65
|
_tag: "rule",
|
|
57
66
|
mode: "all",
|
|
58
|
-
conditions: [
|
|
67
|
+
conditions: [condition],
|
|
59
68
|
outcome
|
|
60
69
|
};
|
|
61
70
|
}
|
|
@@ -479,6 +488,7 @@ function makeAdvanceAllCtx() {
|
|
|
479
488
|
};
|
|
480
489
|
}
|
|
481
490
|
export {
|
|
491
|
+
serializeCondition,
|
|
482
492
|
serializeCohortAdvancementPolicy,
|
|
483
493
|
serializeAdvancementPolicy,
|
|
484
494
|
makeAdvanceEachCtx,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type PipelineInputBinding = {
|
|
2
|
+
source: "pipeline_input";
|
|
3
|
+
path: string;
|
|
4
|
+
};
|
|
5
|
+
export type WorkItemBinding = {
|
|
6
|
+
source: "work_item";
|
|
7
|
+
field: string;
|
|
8
|
+
};
|
|
9
|
+
/** Reads a signal declared by an earlier node, addressed by its state key. */
|
|
10
|
+
export type StepSignalBinding = {
|
|
11
|
+
source: "step_signal";
|
|
12
|
+
nodeKey: string;
|
|
13
|
+
signalKey: string;
|
|
14
|
+
};
|
|
15
|
+
/** Reads an earlier node's whole result output, addressed by its state key. */
|
|
16
|
+
export type StepOutputBinding = {
|
|
17
|
+
source: "step_output";
|
|
18
|
+
nodeKey: string;
|
|
19
|
+
};
|
|
20
|
+
export type LiteralBinding = {
|
|
21
|
+
source: "literal";
|
|
22
|
+
value: unknown;
|
|
23
|
+
};
|
|
24
|
+
/** Reaches a fan-out's whole cohort (every terminal branch's signals + output). */
|
|
25
|
+
export type SignalsListBinding = {
|
|
26
|
+
source: "signals_list";
|
|
27
|
+
nodeKey: string;
|
|
28
|
+
};
|
|
29
|
+
/** A fan-out's own per-branch item — see `FanOutNodeInputCtx`. */
|
|
30
|
+
export type FanOutItemBinding = {
|
|
31
|
+
source: "fan_out_item";
|
|
32
|
+
};
|
|
33
|
+
export type AnyBinding = PipelineInputBinding | WorkItemBinding | StepSignalBinding | StepOutputBinding | LiteralBinding | SignalsListBinding | FanOutItemBinding;
|
|
34
|
+
export type SerializedBinding = {
|
|
35
|
+
source: "pipeline_input";
|
|
36
|
+
path: string;
|
|
37
|
+
} | {
|
|
38
|
+
source: "work_item";
|
|
39
|
+
field: string;
|
|
40
|
+
} | {
|
|
41
|
+
source: "step_signal";
|
|
42
|
+
stepKey: string;
|
|
43
|
+
signalKey: string;
|
|
44
|
+
} | {
|
|
45
|
+
source: "step_output";
|
|
46
|
+
stepKey: string;
|
|
47
|
+
} | {
|
|
48
|
+
source: "literal";
|
|
49
|
+
value: unknown;
|
|
50
|
+
} | {
|
|
51
|
+
source: "signals_list";
|
|
52
|
+
stepKey: string;
|
|
53
|
+
} | {
|
|
54
|
+
source: "fan_out_item";
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Builds the wire `inputBindingsJson` for any working node: `workItemTitle`/
|
|
58
|
+
* `workItemDescription` are always auto-bound (mirroring the retired
|
|
59
|
+
* builder's own behavior), then overridden/extended by whatever the
|
|
60
|
+
* author's `input` mapper returned.
|
|
61
|
+
*/
|
|
62
|
+
export declare function serializeInputBindings(input: Partial<Record<string, AnyBinding | undefined>>): Record<string, SerializedBinding>;
|
|
@@ -1,105 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type
|
|
3
|
-
import { type
|
|
4
|
-
import { type InputAccessor } from "./input-accessor";
|
|
1
|
+
import type { TypedStepDefinitionSpec } from "../steps/define-step";
|
|
2
|
+
import { type AnyBinding, type LiteralBinding, type WorkItemBinding } from "./define-pipeline";
|
|
3
|
+
import { type WorkItemTopLevelField } from "./work-item-fields";
|
|
5
4
|
export type AnyTypedStep = TypedStepDefinitionSpec<any, any, any, any>;
|
|
6
|
-
type ElementOf<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer U> ? U : never;
|
|
7
|
-
export type LastStep<T extends ReadonlyArray<AnyTypedStep>> = T extends readonly [...AnyTypedStep[], infer L] ? L extends AnyTypedStep ? L : never : never;
|
|
8
|
-
export type LastSignalKeys<T extends ReadonlyArray<AnyTypedStep>> = LastStep<T> extends AnyTypedStep ? LastStep<T>["__signalKeys"] : never;
|
|
9
|
-
/**
|
|
10
|
-
* The per-branch `item` type a fan-out's `over` key resolves to (issue
|
|
11
|
-
* #167): `never` unless `K` names a signal on the most recent ordinary step
|
|
12
|
-
* (`LastStep<TSteps>`) whose resolved TS type is itself an array — in which
|
|
13
|
-
* case this is that array's element type. A number-typed signal (count-only
|
|
14
|
-
* mode) resolves to `never`, which is how `FanOutInputCtx` below decides
|
|
15
|
-
* whether `item` exists on the ctx type at all.
|
|
16
|
-
*/
|
|
17
|
-
export type FanOutItemType<TSteps extends ReadonlyArray<AnyTypedStep>, K extends string> = K extends keyof LastStep<TSteps>["__signalTypeMap"] ? LastStep<TSteps>["__signalTypeMap"][K] extends ReadonlyArray<infer Item> ? Item : never : never;
|
|
18
|
-
/**
|
|
19
|
-
* A fan-out's own `input` mapper ctx: everything `StepInputCtx` already
|
|
20
|
-
* offers, plus `item` when `FanOutItemType` resolves to something other
|
|
21
|
-
* than `never`. `item`'s exposed type is intersected with `FanOutItemBinding`
|
|
22
|
-
* (the same "phantom binding" trick `WithWorkItemFields`/`InputAccessor`
|
|
23
|
-
* use elsewhere in this file) so it reads as the real per-item TS type
|
|
24
|
-
* (e.g. `string`) to callers while still structurally satisfying `AnyBinding`
|
|
25
|
-
* when assigned straight into a `FanOutInputMapping` field — at runtime it
|
|
26
|
-
* is always the single `{ source: "fan_out_item" }` binding object,
|
|
27
|
-
* regardless of `Item`'s shape.
|
|
28
|
-
*/
|
|
29
|
-
export type FanOutInputCtx<TInput extends ZodType, TSteps extends ReadonlyArray<AnyTypedStep>, TFanOuts extends ReadonlyArray<AnyTypedStep>, K extends string> = StepInputCtx<TInput, TSteps, TFanOuts> & (FanOutItemType<TSteps, K> extends never ? unknown : {
|
|
30
|
-
item: FanOutItemBinding & FanOutItemType<TSteps, K>;
|
|
31
|
-
});
|
|
32
5
|
export type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
33
|
-
export type WorkItemAccessor = {
|
|
34
|
-
readonly title: WorkItemBinding;
|
|
35
|
-
readonly description: WorkItemBinding;
|
|
36
|
-
readonly field: (fieldName: string) => WorkItemBinding;
|
|
37
|
-
};
|
|
38
|
-
export type PinnedWorkItemComment = {
|
|
39
|
-
createdAt: string;
|
|
40
|
-
body: string;
|
|
41
|
-
};
|
|
42
|
-
export type WithWorkItemFields<T> = {
|
|
43
|
-
workItemTitle: string;
|
|
44
|
-
workItemDescription: string | null;
|
|
45
|
-
workItemComments: PinnedWorkItemComment[];
|
|
46
|
-
} & T;
|
|
47
6
|
export type RequiredInputKeys<T extends object> = {
|
|
48
7
|
[K in keyof T & string]-?: undefined extends T[K] ? never : K;
|
|
49
8
|
}[keyof T & string];
|
|
50
9
|
export type OptionalInputKeys<T extends object> = {
|
|
51
10
|
[K in keyof T & string]-?: undefined extends T[K] ? K : never;
|
|
52
11
|
}[keyof T & string];
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
status?: "draft" | "active";
|
|
80
|
-
additionalPipelineInput?: {
|
|
81
|
-
schema: NoReservedKeys<TInput>;
|
|
82
|
-
bindings: (ctx: {
|
|
83
|
-
workItem: WorkItemAccessor;
|
|
84
|
-
literal: (value: unknown) => LiteralBinding;
|
|
85
|
-
}) => TInput["_output"] extends object ? {
|
|
86
|
-
[K in RequiredInputKeys<TInput["_output"]>]: AnyBinding;
|
|
87
|
-
} & {
|
|
88
|
-
[K in OptionalInputKeys<TInput["_output"]>]?: AnyBinding;
|
|
89
|
-
} : Partial<Record<string, AnyBinding>>;
|
|
90
|
-
};
|
|
91
|
-
additionalStepInput?: {
|
|
92
|
-
schema: ZodType;
|
|
93
|
-
bindings: (ctx: {
|
|
94
|
-
workItemField: (fieldName: string) => WorkItemBinding;
|
|
95
|
-
literal: (value: unknown) => LiteralBinding;
|
|
96
|
-
}) => Partial<Record<string, AdditionalStepInputBinding>>;
|
|
97
|
-
};
|
|
12
|
+
/**
|
|
13
|
+
* Work-item accessor for a `step`/`fanOut`/`loop`/parallel-branch state's
|
|
14
|
+
* `input` mapper (`ctx.workItem`, `NodeInputCtx` in `node-input-ctx.ts`): a
|
|
15
|
+
* top-level property (`workItem.title`, `workItem.platform`, `workItem.url`,
|
|
16
|
+
* ...) for every member of `WORK_ITEM_TOP_LEVEL_FIELDS`, plus
|
|
17
|
+
* `workItem.field(name)` for the platform-specific `fields` bag.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors the default-pipeline-assignment DSL's `WorkItemAssignmentAccessor`
|
|
20
|
+
* (`define-default-pipeline-assignment.ts`) in which properties it exposes —
|
|
21
|
+
* both are generated off the same `WORK_ITEM_TOP_LEVEL_FIELDS` list — but
|
|
22
|
+
* returns a plain `WorkItemBinding` per property rather than a
|
|
23
|
+
* comparator-bound `AssignmentFieldRef`: a regular pipeline input binding
|
|
24
|
+
* has no conditions to compare against, it's just "read this work-item
|
|
25
|
+
* property into this input field."
|
|
26
|
+
*
|
|
27
|
+
* `field`'s optional `TName` type parameter accepts a literal union of known
|
|
28
|
+
* field names (e.g. the `WorkItemFieldName` type `boboddy pipelines pull`
|
|
29
|
+
* generates into `work-item-fields.ts`) for compile-time validation and
|
|
30
|
+
* autocomplete on the field name, exactly like the assignment DSL's
|
|
31
|
+
* `workItem.field<TName>(...)`. Defaults to permissive `string`, so
|
|
32
|
+
* `workItem.field(name)` with no type argument keeps working as before.
|
|
33
|
+
*/
|
|
34
|
+
export type WorkItemAccessor = {
|
|
35
|
+
readonly [K in WorkItemTopLevelField]: WorkItemBinding;
|
|
36
|
+
} & {
|
|
37
|
+
field<TName extends string = string>(fieldName: TName): WorkItemBinding;
|
|
98
38
|
};
|
|
99
39
|
export declare const WORK_ITEM_ACCESSOR: WorkItemAccessor;
|
|
100
|
-
export declare function makeStepInputCtx<TInput extends ZodType>(inputSchema: TInput): StepInputCtx<TInput, ReadonlyArray<AnyTypedStep>, ReadonlyArray<AnyTypedStep>>;
|
|
101
40
|
export declare function literal(value: unknown): LiteralBinding;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Strips `undefined` values out of a node's `input` mapper return value —
|
|
43
|
+
* an author may return `{ foo: someCondition ? binding : undefined }` to
|
|
44
|
+
* conditionally omit a field, which should not become a real
|
|
45
|
+
* `{foo: undefined}` wire entry.
|
|
46
|
+
*/
|
|
47
|
+
export declare function normalizeInputMapping(mapping: Record<string, AnyBinding | undefined> | undefined): Record<string, AnyBinding>;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import type { DependencyEdgeSpec, NodeDefinitionSpec } from "./define-pipeline";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Computes, for every node key, the longest-path-from-root depth ("topo
|
|
4
|
+
* rank") over the given edges, using Kahn's algorithm — identical in
|
|
5
|
+
* approach to `packages/core`'s own `computeTopoRanks`. Returns `null`
|
|
6
|
+
* (does not throw) if the graph contains a cycle, or if `dependencyEdges`
|
|
7
|
+
* references a node key outside `nodeDefinitions` (a malformed/hand-edited
|
|
8
|
+
* spec) — every caller here is a diagnostic tool, not a build-time
|
|
9
|
+
* assertion, so it degrades gracefully rather than crashing the whole
|
|
10
|
+
* validation pass.
|
|
7
11
|
*/
|
|
8
|
-
export declare function
|
|
12
|
+
export declare function tryComputeTopoRanks(nodeDefinitions: readonly Pick<NodeDefinitionSpec, "nodeKey">[], dependencyEdges: readonly DependencyEdgeSpec[]): Map<string, number> | null;
|
|
9
13
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* `tryComputeTopoRanks`, ordering `nodeDefinitions` by ascending rank (ties
|
|
15
|
+
* broken by declaration order) instead of returning the raw rank map —
|
|
16
|
+
* the shape `validate-definition-specs.ts`'s "runs before its consumer"
|
|
17
|
+
* check wants directly. Returns `null` under the same conditions
|
|
18
|
+
* `tryComputeTopoRanks` does.
|
|
13
19
|
*/
|
|
14
|
-
export declare function
|
|
15
|
-
/**
|
|
16
|
-
* `tryOrderChainNodeDefinitions`, but throws a descriptive error instead of
|
|
17
|
-
* returning `null` when the graph isn't a single valid chain.
|
|
18
|
-
*/
|
|
19
|
-
export declare function orderChainNodeDefinitions(nodeDefinitions: readonly NodeDefinitionSpec[], dependencyEdges: readonly DependencyEdgeSpec[]): NodeDefinitionSpec[];
|
|
20
|
+
export declare function tryOrderNodeDefinitionsByTopoRank(nodeDefinitions: readonly NodeDefinitionSpec[], dependencyEdges: readonly DependencyEdgeSpec[]): NodeDefinitionSpec[] | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AnyTypedStep } from "./builder-helpers";
|
|
2
|
+
import type { DependencyEdgeSpec, NodeDefinitionSpec } from "./define-pipeline";
|
|
3
|
+
import type { ChoiceState, FanOutState, LoopState, ParallelState, StepState } from "./pipeline-states";
|
|
4
|
+
export type CompileContext = {
|
|
5
|
+
pipelineKey: string;
|
|
6
|
+
stateKeys: ReadonlySet<string>;
|
|
7
|
+
registerStep: (step: AnyTypedStep) => void;
|
|
8
|
+
};
|
|
9
|
+
export type CompiledState = {
|
|
10
|
+
nodeDefinitions: NodeDefinitionSpec[];
|
|
11
|
+
edges: DependencyEdgeSpec[];
|
|
12
|
+
};
|
|
13
|
+
export declare function assertTargetExists(ctx: CompileContext, fromKey: string, toKey: string): void;
|
|
14
|
+
export declare function compileStepState(stateKey: string, state: StepState, ctx: CompileContext): CompiledState;
|
|
15
|
+
export declare function compileChoiceState(stateKey: string, state: ChoiceState, ctx: CompileContext): CompiledState;
|
|
16
|
+
export declare function compileFanOutState(stateKey: string, state: FanOutState, ctx: CompileContext): CompiledState;
|
|
17
|
+
export declare function compileParallelState(stateKey: string, state: ParallelState, ctx: CompileContext): CompiledState;
|
|
18
|
+
export declare function compileLoopState(stateKey: string, state: LoopState, ctx: CompileContext): CompiledState;
|
|
19
|
+
export declare function compileTerminalState(stateKey: string, kind: "succeed" | "fail"): CompiledState;
|
|
20
|
+
/**
|
|
21
|
+
* The SDK-side mirror of §6's domain invariant: a node may have more than
|
|
22
|
+
* one incoming edge only when every source is a `choice`/`loop` state
|
|
23
|
+
* (never an unconditional `step`/`fanOut`/`parallel`/`cohortGate`
|
|
24
|
+
* successor) — gives authors a fast local error instead of a round-trip to
|
|
25
|
+
* the server.
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertNoIllegalConvergentEdges(pipelineKey: string, nodeKindByKey: ReadonlyMap<string, NodeDefinitionSpec["kind"]>, edges: readonly DependencyEdgeSpec[]): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PipelineDefinitionSpec } from "./define-pipeline";
|
|
2
|
+
import { type WorkItemTopLevelField, type WorkItemTopLevelFieldTypeMap } from "./work-item-fields";
|
|
2
3
|
/**
|
|
3
4
|
* The reserved filename for the default pipeline assignment file.
|
|
4
5
|
* This file is NOT treated as a pipeline definition by the push scanner.
|
|
@@ -47,31 +48,80 @@ export interface AssignmentGroup {
|
|
|
47
48
|
readonly _condition: AssignmentCondition;
|
|
48
49
|
}
|
|
49
50
|
type AssignmentNestable = AssignmentLeaf | AssignmentGroup;
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
/**
|
|
52
|
+
* `.gt`/`.gte`/`.lt`/`.lte` are only ever meaningful for numeric fields.
|
|
53
|
+
* Intersected onto `AssignmentFieldRef<T>` only when `T` is (or includes)
|
|
54
|
+
* `number`, so e.g. `workItem.title.gt(...)` doesn't even type-check, let
|
|
55
|
+
* alone appear in autocomplete.
|
|
56
|
+
*/
|
|
57
|
+
type NumericComparators = {
|
|
54
58
|
gt(value: number): AssignmentLeaf;
|
|
55
59
|
gte(value: number): AssignmentLeaf;
|
|
56
60
|
lt(value: number): AssignmentLeaf;
|
|
57
61
|
lte(value: number): AssignmentLeaf;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Comparator-bound reference to a work-item field or context fact.
|
|
65
|
+
*
|
|
66
|
+
* `T` is the field's value type (defaulting to `unknown`, which preserves
|
|
67
|
+
* the pre-generic behavior for any caller that doesn't otherwise constrain
|
|
68
|
+
* it). `.contains`/`.doesNotContain` narrow to the array element type when
|
|
69
|
+
* `T` is an array — json-rules-engine's `contains`/`doesNotContain`
|
|
70
|
+
* operators check array-membership (or substring-membership for strings)
|
|
71
|
+
* against the fact's actual runtime shape, so an array-typed field's
|
|
72
|
+
* `.contains(...)` should accept one element, not the whole array.
|
|
73
|
+
*/
|
|
74
|
+
export type AssignmentFieldRef<T = unknown> = {
|
|
75
|
+
eq(value: T): AssignmentLeaf;
|
|
76
|
+
ne(value: T): AssignmentLeaf;
|
|
77
|
+
in(values: ReadonlyArray<T>): AssignmentLeaf;
|
|
78
|
+
notIn(values: ReadonlyArray<T>): AssignmentLeaf;
|
|
79
|
+
contains(value: T extends ReadonlyArray<infer U> ? U : T): AssignmentLeaf;
|
|
80
|
+
doesNotContain(value: T extends ReadonlyArray<infer U> ? U : T): AssignmentLeaf;
|
|
81
|
+
} & (T extends number ? NumericComparators : Record<string, never>);
|
|
82
|
+
/** Comparator-bound accessor for the work item passed to `defaultPipelineAssignment`. */
|
|
83
|
+
export type WorkItemAssignmentAccessor = {
|
|
84
|
+
[K in WorkItemTopLevelField]: AssignmentFieldRef<WorkItemTopLevelFieldTypeMap[K]>;
|
|
85
|
+
} & {
|
|
86
|
+
/**
|
|
87
|
+
* Accessor for a platform-specific field (Jira custom field, GitHub label,
|
|
88
|
+
* etc.) stored in the work item's `fields` bag. Since `fields` keys and
|
|
89
|
+
* value shapes are platform- and project-dependent, there is no static
|
|
90
|
+
* schema to check either against by default.
|
|
91
|
+
*
|
|
92
|
+
* Two independent, optional type parameters:
|
|
93
|
+
* - `TName` constrains the `name` argument itself — pass a literal union
|
|
94
|
+
* of known field names (e.g. the `WorkItemFieldName` type
|
|
95
|
+
* `boboddy pipelines pull` generates into `work-item-fields.ts`) to get
|
|
96
|
+
* compile-time validation and autocomplete on the field name.
|
|
97
|
+
* - `TValue` types the comparator (`.eq`/`.gte`/etc.) argument, exactly
|
|
98
|
+
* like the top-level accessors above — pass it for a known-shaped
|
|
99
|
+
* custom field (e.g. a numeric one).
|
|
100
|
+
* Both default to permissive types, so `workItem.field(name)` with no
|
|
101
|
+
* type arguments keeps working exactly as before.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* workItem.field("issueType").eq("bug")
|
|
105
|
+
* workItem.field("labels").contains("regression")
|
|
106
|
+
* workItem.field<WorkItemFieldName>("issueType").eq("bug")
|
|
107
|
+
* workItem.field<WorkItemFieldName, number>("storyPoints").gte(3)
|
|
108
|
+
*/
|
|
109
|
+
field<TName extends string = string, TValue = unknown>(name: TName): AssignmentFieldRef<TValue>;
|
|
110
|
+
};
|
|
63
111
|
export type DefaultPipelineAssignmentCtx = {
|
|
64
112
|
/**
|
|
65
|
-
* Work-item
|
|
113
|
+
* Work-item accessor: top-level properties (`workItem.title`,
|
|
114
|
+
* `workItem.platform`, `workItem.url`, ...) plus `workItem.field(name)`
|
|
115
|
+
* for the platform-specific `fields` bag.
|
|
66
116
|
*
|
|
67
117
|
* @example
|
|
68
118
|
* workItem.field("issueType").eq("bug").then(assign(bugTriage))
|
|
69
119
|
* workItem.field("labels").contains("regression").then(assign(regressionReview))
|
|
70
120
|
* workItem.field("status").eq("resolved").then(skip())
|
|
121
|
+
* workItem.platform.eq("github").then(assign(githubTriage))
|
|
122
|
+
* workItem.title.contains("[urgent]").then(assign(urgentTriage))
|
|
71
123
|
*/
|
|
72
|
-
workItem:
|
|
73
|
-
field(name: string): AssignmentFieldRef;
|
|
74
|
-
};
|
|
124
|
+
workItem: WorkItemAssignmentAccessor;
|
|
75
125
|
/**
|
|
76
126
|
* Context facts for the current ingestion event.
|
|
77
127
|
* `context.isNew` is `true` when the work item is being created for the first time.
|
|
@@ -80,7 +130,7 @@ export type DefaultPipelineAssignmentCtx = {
|
|
|
80
130
|
* context.isNew.eq(true).then(assign(onboarding))
|
|
81
131
|
*/
|
|
82
132
|
context: {
|
|
83
|
-
isNew: AssignmentFieldRef
|
|
133
|
+
isNew: AssignmentFieldRef<boolean>;
|
|
84
134
|
};
|
|
85
135
|
/**
|
|
86
136
|
* Outcome: assign the work item to the given pipeline.
|