@notionhq/apps 0.0.16 → 0.0.18
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/AGENTS.md +27 -0
- package/README.md +86 -36
- package/dist/connections.d.ts +39 -11
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +85 -21
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/notion-as-code/database.d.ts +89 -48
- package/dist/notion-as-code/database.d.ts.map +1 -1
- package/dist/notion-as-code/database.js +132 -45
- package/dist/notion-as-code/database.test.d.ts +2 -0
- package/dist/notion-as-code/database.test.d.ts.map +1 -0
- package/dist/notion-as-code/handles.d.ts +2 -0
- package/dist/notion-as-code/handles.d.ts.map +1 -1
- package/dist/notion-as-code/handles.js +2 -0
- package/dist/notion-as-code/index.d.ts +5 -2
- package/dist/notion-as-code/index.d.ts.map +1 -1
- package/dist/notion-as-code/intents.d.ts +36 -3
- package/dist/notion-as-code/intents.d.ts.map +1 -1
- package/dist/notion-as-code/schema.d.ts +8 -4
- package/dist/notion-as-code/schema.d.ts.map +1 -1
- package/dist/notion-as-code/view.d.ts +12 -0
- package/dist/notion-as-code/view.d.ts.map +1 -0
- package/dist/notion-as-code/view.js +13 -0
- package/dist/notion-as-code/views-types.test.d.ts +2 -0
- package/dist/notion-as-code/views-types.test.d.ts.map +1 -0
- package/dist/notion-as-code/views.d.ts +488 -0
- package/dist/notion-as-code/views.d.ts.map +1 -0
- package/dist/notion-as-code/views.js +0 -0
- package/dist/oauth.d.ts +11 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +26 -0
- package/dist/providers.generated.d.ts +26 -86
- package/dist/providers.generated.d.ts.map +1 -1
- package/dist/providers.generated.js +54 -66
- package/dist/sync.d.ts +33 -15
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +12 -0
- package/dist/triggers.generated.d.ts +3 -3
- package/dist/triggers.generated.d.ts.map +1 -1
- package/dist/workflow-state.d.ts +13 -0
- package/dist/workflow-state.d.ts.map +1 -0
- package/dist/workflow-state.js +107 -0
- package/dist/workflow.d.ts +56 -11
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +123 -10
- package/docs/BUILD.md +96 -0
- package/docs/CONNECTIONS.md +70 -22
- package/package.json +1 -1
- package/skills/connections/SKILL.md +7 -8
- package/skills/notion-as-code/SKILL.md +88 -50
- package/skills/sync/SKILL.md +21 -18
- package/skills/workflow/SKILL.md +52 -3
- package/src/cli/build.test.ts +124 -64
- package/src/connections.test.ts +205 -41
- package/src/connections.ts +144 -38
- package/src/index.ts +2 -0
- package/src/notion-as-code/database.test.ts +661 -0
- package/src/notion-as-code/database.ts +346 -129
- package/src/notion-as-code/handles.ts +2 -0
- package/src/notion-as-code/index.ts +71 -1
- package/src/notion-as-code/intents.ts +41 -4
- package/src/notion-as-code/schema.ts +11 -3
- package/src/notion-as-code/view.ts +23 -0
- package/src/notion-as-code/views-types.test.ts +59 -0
- package/src/notion-as-code/views.ts +573 -0
- package/src/oauth.ts +40 -0
- package/src/providers.generated.ts +68 -163
- package/src/sync.test.ts +295 -0
- package/src/sync.ts +85 -21
- package/src/triggers.generated.ts +4 -4
- package/src/workflow-connections-types.test.ts +16 -10
- package/src/workflow-state.ts +152 -0
- package/src/workflow-types.test.ts +88 -16
- package/src/workflow.test.ts +374 -18
- package/src/workflow.ts +211 -23
package/src/workflow.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWorkflowConnections,
|
|
3
|
-
|
|
3
|
+
normalizeConnectionRequirements,
|
|
4
4
|
validateTriggerConnections,
|
|
5
|
-
type
|
|
5
|
+
type WorkflowConnectionDeclarations,
|
|
6
|
+
type WorkflowConnectionRequirement,
|
|
6
7
|
type WorkflowConnections,
|
|
7
8
|
} from "./connections.js";
|
|
8
9
|
import { createHash } from "node:crypto";
|
|
@@ -16,6 +17,7 @@ import { writeOutput } from "./output.js";
|
|
|
16
17
|
import { resolveRuntimeInput } from "./runtime-input.js";
|
|
17
18
|
import { readRunMetadata, type RunMetadata } from "./runtime-metadata.js";
|
|
18
19
|
import { createWorkflowTriggers } from "./triggers.generated.js";
|
|
20
|
+
import { createWorkflowStepState, type WorkflowState } from "./workflow-state.js";
|
|
19
21
|
import type {
|
|
20
22
|
WorkflowEventMap,
|
|
21
23
|
WorkflowTrigger,
|
|
@@ -28,6 +30,45 @@ type HandlerOptions = {
|
|
|
28
30
|
concreteOutput?: true;
|
|
29
31
|
};
|
|
30
32
|
|
|
33
|
+
type WorkflowWaitDurationValues = {
|
|
34
|
+
milliseconds?: number;
|
|
35
|
+
seconds?: number;
|
|
36
|
+
minutes?: number;
|
|
37
|
+
hours?: number;
|
|
38
|
+
days?: number;
|
|
39
|
+
weeks?: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type WorkflowWaitDuration = {
|
|
43
|
+
[Unit in keyof WorkflowWaitDurationValues]-?: Required<Pick<WorkflowWaitDurationValues, Unit>> &
|
|
44
|
+
WorkflowWaitDurationValues;
|
|
45
|
+
}[keyof WorkflowWaitDurationValues];
|
|
46
|
+
|
|
47
|
+
type WorkflowWaitKey = string | string[];
|
|
48
|
+
|
|
49
|
+
export type WorkflowWaitUntilOptions =
|
|
50
|
+
| {
|
|
51
|
+
at: Date;
|
|
52
|
+
key?: WorkflowWaitKey;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
after: WorkflowWaitDuration;
|
|
56
|
+
key?: WorkflowWaitKey;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type WorkflowWaitUntilResult = {
|
|
60
|
+
status: "waiting";
|
|
61
|
+
wait: {
|
|
62
|
+
type: "until";
|
|
63
|
+
name: string;
|
|
64
|
+
resumeAtMs: number;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type WorkflowHandlerResult = { status: "success" } | WorkflowWaitUntilResult;
|
|
69
|
+
|
|
70
|
+
const MAX_WORKFLOW_WAIT_MS = 7 * 24 * 60 * 60 * 1_000;
|
|
71
|
+
|
|
31
72
|
export type WorkflowEvent = WorkflowEventMap[keyof WorkflowEventMap];
|
|
32
73
|
|
|
33
74
|
export type WorkflowEventForTrigger<T extends WorkflowTrigger> = WorkflowEventMap[T["type"]];
|
|
@@ -35,14 +76,22 @@ export type WorkflowEventForTrigger<T extends WorkflowTrigger> = WorkflowEventMa
|
|
|
35
76
|
export type WorkflowEventForTriggers<T extends readonly WorkflowTrigger[]> =
|
|
36
77
|
WorkflowEventForTrigger<T[number]>;
|
|
37
78
|
|
|
38
|
-
export {
|
|
79
|
+
export {
|
|
80
|
+
connections,
|
|
81
|
+
type WorkflowConnection,
|
|
82
|
+
type WorkflowConnectionDeclarations,
|
|
83
|
+
type WorkflowConnectionRequirement,
|
|
84
|
+
type WorkflowConnections,
|
|
85
|
+
type OAuthConfiguration,
|
|
86
|
+
type OAuthConnection,
|
|
87
|
+
} from "./connections.js";
|
|
39
88
|
|
|
40
89
|
/**
|
|
41
90
|
* Configuration passed to {@link workflow}.
|
|
42
91
|
*/
|
|
43
92
|
export type WorkflowConfiguration<
|
|
44
93
|
TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
|
|
45
|
-
TConnections extends
|
|
94
|
+
TConnections extends WorkflowConnectionDeclarations = WorkflowConnectionDeclarations,
|
|
46
95
|
> = {
|
|
47
96
|
/**
|
|
48
97
|
* A human-readable name for the workflow, shown in the UI when viewing workflows
|
|
@@ -75,7 +124,7 @@ export type WorkflowConfiguration<
|
|
|
75
124
|
/** Configuration that checks trigger keys against declared connections. */
|
|
76
125
|
export type WorkflowTriggerConfiguration<
|
|
77
126
|
TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
|
|
78
|
-
TConnections extends
|
|
127
|
+
TConnections extends WorkflowConnectionDeclarations,
|
|
79
128
|
> = Omit<WorkflowConfiguration<TTriggers, TConnections>, "triggers"> & {
|
|
80
129
|
triggers: (context: { triggers: WorkflowTriggerCreators<NoInfer<TConnections>> }) => TTriggers;
|
|
81
130
|
};
|
|
@@ -97,12 +146,12 @@ export type Workflow<
|
|
|
97
146
|
name: string;
|
|
98
147
|
description: string;
|
|
99
148
|
triggers: TTriggers;
|
|
100
|
-
connections?: readonly
|
|
149
|
+
connections?: readonly WorkflowConnectionRequirement[];
|
|
101
150
|
};
|
|
102
151
|
handler: (
|
|
103
152
|
event: WorkflowEventForTriggers<TTriggers>,
|
|
104
153
|
options?: HandlerOptions,
|
|
105
|
-
) => Promise<
|
|
154
|
+
) => Promise<WorkflowHandlerResult | undefined>;
|
|
106
155
|
};
|
|
107
156
|
|
|
108
157
|
/**
|
|
@@ -136,26 +185,30 @@ export function workflow<
|
|
|
136
185
|
WorkflowTriggerForConnections<NoInfer<TConnections>>,
|
|
137
186
|
...WorkflowTriggerForConnections<NoInfer<TConnections>>[],
|
|
138
187
|
],
|
|
139
|
-
const TConnections extends
|
|
188
|
+
const TConnections extends WorkflowConnectionDeclarations = Record<never, never>,
|
|
140
189
|
>(configuration: WorkflowTriggerConfiguration<TTriggers, TConnections>): Workflow<TTriggers>;
|
|
141
190
|
export function workflow<
|
|
142
191
|
const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
|
|
143
|
-
const TConnections extends
|
|
192
|
+
const TConnections extends WorkflowConnectionDeclarations = Record<never, never>,
|
|
144
193
|
>(configuration: WorkflowConfiguration<TTriggers, TConnections>): Workflow<TTriggers>;
|
|
145
194
|
export function workflow<
|
|
146
195
|
const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
|
|
147
|
-
const TConnections extends
|
|
196
|
+
const TConnections extends WorkflowConnectionDeclarations = Record<never, never>,
|
|
148
197
|
>(
|
|
149
198
|
configuration:
|
|
150
199
|
| WorkflowConfiguration<TTriggers, TConnections>
|
|
151
200
|
| WorkflowTriggerConfiguration<TTriggers, TConnections>,
|
|
152
201
|
): Workflow<TTriggers> {
|
|
202
|
+
const requirements = normalizeConnectionRequirements(configuration.connections ?? {});
|
|
203
|
+
const connectionDeclarations =
|
|
204
|
+
configuration.connections === undefined
|
|
205
|
+
? undefined
|
|
206
|
+
: structuredClone(configuration.connections);
|
|
153
207
|
const triggers =
|
|
154
208
|
typeof configuration.triggers === "function"
|
|
155
209
|
? configuration.triggers({ triggers: createWorkflowTriggers<TConnections>() })
|
|
156
210
|
: configuration.triggers;
|
|
157
|
-
|
|
158
|
-
validateTriggerConnections(triggers, configuration.connections ?? []);
|
|
211
|
+
validateTriggerConnections(triggers, requirements);
|
|
159
212
|
|
|
160
213
|
return {
|
|
161
214
|
_tag: "workflow",
|
|
@@ -163,23 +216,26 @@ export function workflow<
|
|
|
163
216
|
name: configuration.name,
|
|
164
217
|
description: configuration.description,
|
|
165
218
|
triggers,
|
|
166
|
-
...(configuration.connections === undefined
|
|
167
|
-
? {}
|
|
168
|
-
: { connections: configuration.connections }),
|
|
219
|
+
...(configuration.connections === undefined ? {} : { connections: requirements }),
|
|
169
220
|
},
|
|
170
221
|
async handler(
|
|
171
222
|
event: WorkflowEventForTriggers<TTriggers>,
|
|
172
223
|
options?: HandlerOptions,
|
|
173
|
-
): Promise<
|
|
224
|
+
): Promise<WorkflowHandlerResult | undefined> {
|
|
174
225
|
try {
|
|
175
226
|
event = await resolveRuntimeInput(event);
|
|
176
227
|
const runMetadata = readRunMetadata();
|
|
177
228
|
const baseContext = createCapabilityContext();
|
|
178
|
-
const
|
|
229
|
+
const step = createStep(runMetadata);
|
|
230
|
+
const capabilityContext: WorkflowContext<TConnections> = {
|
|
179
231
|
...baseContext,
|
|
180
|
-
connections: createWorkflowConnections(
|
|
232
|
+
connections: createWorkflowConnections(
|
|
233
|
+
baseContext.notion,
|
|
234
|
+
connectionDeclarations,
|
|
235
|
+
),
|
|
181
236
|
...runMetadata,
|
|
182
|
-
step
|
|
237
|
+
step,
|
|
238
|
+
wait: createWait(step),
|
|
183
239
|
};
|
|
184
240
|
await configuration.handler(event, capabilityContext);
|
|
185
241
|
|
|
@@ -189,6 +245,18 @@ export function workflow<
|
|
|
189
245
|
|
|
190
246
|
writeOutput({ _tag: "success", value: { status: "success" } });
|
|
191
247
|
} catch (err) {
|
|
248
|
+
if (err instanceof WorkflowWaitInterrupt) {
|
|
249
|
+
if (options?.concreteOutput) {
|
|
250
|
+
return err.result;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
writeOutput({
|
|
254
|
+
_tag: "wait",
|
|
255
|
+
wait: err.result.wait,
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
192
260
|
const error = new ExecutionError(err);
|
|
193
261
|
|
|
194
262
|
if (!options?.concreteOutput) {
|
|
@@ -215,6 +283,13 @@ export type StepContext = {
|
|
|
215
283
|
* Use this to make side effects idempotent when the workflow is retried.
|
|
216
284
|
*/
|
|
217
285
|
id: string;
|
|
286
|
+
/**
|
|
287
|
+
* Durable key-value state shared by every run of this workflow.
|
|
288
|
+
*
|
|
289
|
+
* State is exposed on the step context because every operation participates
|
|
290
|
+
* in the active step's durability and replay boundary.
|
|
291
|
+
*/
|
|
292
|
+
state: WorkflowState;
|
|
218
293
|
};
|
|
219
294
|
|
|
220
295
|
type WorkflowStepResult<T> = T extends void ? null : T;
|
|
@@ -238,9 +313,17 @@ type WorkflowStep = {
|
|
|
238
313
|
): Promise<WorkflowStepResult<T>>;
|
|
239
314
|
};
|
|
240
315
|
|
|
316
|
+
type WorkflowWait = {
|
|
317
|
+
/**
|
|
318
|
+
* Suspend the workflow until a relative duration has elapsed or an absolute
|
|
319
|
+
* date has been reached.
|
|
320
|
+
*/
|
|
321
|
+
until(name: string, options: WorkflowWaitUntilOptions): Promise<void>;
|
|
322
|
+
};
|
|
323
|
+
|
|
241
324
|
/** Context passed to a workflow handler. */
|
|
242
325
|
export type WorkflowContext<
|
|
243
|
-
TConnections extends
|
|
326
|
+
TConnections extends WorkflowConnectionDeclarations = WorkflowConnectionDeclarations,
|
|
244
327
|
> = CapabilityContext & {
|
|
245
328
|
connections: WorkflowConnections<TConnections>;
|
|
246
329
|
} & RunMetadata & {
|
|
@@ -265,6 +348,8 @@ export type WorkflowContext<
|
|
|
265
348
|
* ```
|
|
266
349
|
*/
|
|
267
350
|
step: WorkflowStep;
|
|
351
|
+
/** Suspend this workflow without consuming compute resources. */
|
|
352
|
+
wait: WorkflowWait;
|
|
268
353
|
};
|
|
269
354
|
|
|
270
355
|
type StepCompletionType = "success" | "failure";
|
|
@@ -300,7 +385,10 @@ function writeStepEvent(
|
|
|
300
385
|
);
|
|
301
386
|
}
|
|
302
387
|
|
|
303
|
-
function createStep(
|
|
388
|
+
function createStep(
|
|
389
|
+
metadata: Pick<RunMetadata, "runGroupId" | "workerId">,
|
|
390
|
+
): WorkflowContext["step"] {
|
|
391
|
+
const { runGroupId } = metadata;
|
|
304
392
|
const stepNameByKey = new Map<string, string>();
|
|
305
393
|
|
|
306
394
|
async function step<T>(
|
|
@@ -324,7 +412,9 @@ function createStep({ runGroupId }: Pick<RunMetadata, "runGroupId">): WorkflowCo
|
|
|
324
412
|
}
|
|
325
413
|
stepNameByKey.set(key, name);
|
|
326
414
|
|
|
327
|
-
const
|
|
415
|
+
const id = `${runGroupId}:${key}`;
|
|
416
|
+
const stepState = createWorkflowStepState(metadata, id);
|
|
417
|
+
const context: StepContext = { id, state: stepState.state };
|
|
328
418
|
const event = { id: context.id, name, key };
|
|
329
419
|
const startedAt = performance.now();
|
|
330
420
|
|
|
@@ -353,7 +443,7 @@ function createStep({ runGroupId }: Pick<RunMetadata, "runGroupId">): WorkflowCo
|
|
|
353
443
|
return checkpoint.value as WorkflowStepResult<T>;
|
|
354
444
|
}
|
|
355
445
|
|
|
356
|
-
const value = normalizeWorkflowStepResult(await fn(context));
|
|
446
|
+
const value = normalizeWorkflowStepResult(await stepState.run(() => fn(context)));
|
|
357
447
|
writeStepEvent("success", { ...event, value }, startedAt);
|
|
358
448
|
writeStepEvent(
|
|
359
449
|
"completed",
|
|
@@ -393,6 +483,104 @@ function createStep({ runGroupId }: Pick<RunMetadata, "runGroupId">): WorkflowCo
|
|
|
393
483
|
return step;
|
|
394
484
|
}
|
|
395
485
|
|
|
486
|
+
class WorkflowWaitInterrupt extends Error {
|
|
487
|
+
constructor(readonly result: WorkflowWaitUntilResult) {
|
|
488
|
+
super(`Workflow is waiting until ${new Date(result.wait.resumeAtMs).toISOString()}`);
|
|
489
|
+
this.name = "WorkflowWaitInterrupt";
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function createWait(step: WorkflowStep): WorkflowWait {
|
|
494
|
+
return {
|
|
495
|
+
async until(name, options) {
|
|
496
|
+
const keyInput = options.key ?? name;
|
|
497
|
+
const keySegments = [
|
|
498
|
+
"wait",
|
|
499
|
+
"until",
|
|
500
|
+
...(typeof keyInput === "string" ? [keyInput] : keyInput),
|
|
501
|
+
];
|
|
502
|
+
const resumeAtMs = await step(name, { key: keySegments }, () =>
|
|
503
|
+
resolveWaitUntilMs(options),
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
if (Date.now() >= resumeAtMs) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
throw new WorkflowWaitInterrupt({
|
|
511
|
+
status: "waiting",
|
|
512
|
+
wait: {
|
|
513
|
+
type: "until",
|
|
514
|
+
name,
|
|
515
|
+
resumeAtMs,
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function resolveWaitUntilMs(options: WorkflowWaitUntilOptions): number {
|
|
523
|
+
const now = Date.now();
|
|
524
|
+
if ("at" in options) {
|
|
525
|
+
const resumeAtMs = options.at.getTime();
|
|
526
|
+
if (!Number.isFinite(resumeAtMs)) {
|
|
527
|
+
throw new Error("Workflow wait date must be valid.");
|
|
528
|
+
}
|
|
529
|
+
assertWaitFitsCheckpointRetention(resumeAtMs - now);
|
|
530
|
+
return resumeAtMs;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const durationMs = workflowWaitDurationMs(options.after);
|
|
534
|
+
assertWaitFitsCheckpointRetention(durationMs);
|
|
535
|
+
return now + durationMs;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function workflowWaitDurationMs(duration: WorkflowWaitDuration): number {
|
|
539
|
+
const entries = Object.entries(duration);
|
|
540
|
+
if (entries.length === 0) {
|
|
541
|
+
throw new Error("Workflow wait duration must specify at least one unit.");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
let durationMs = 0;
|
|
545
|
+
for (const [unit, value] of entries) {
|
|
546
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
547
|
+
throw new Error("Workflow wait duration values must be positive finite integers.");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
durationMs += value * waitDurationUnitMultiplier(unit);
|
|
551
|
+
if (!Number.isSafeInteger(durationMs)) {
|
|
552
|
+
throw new Error("Workflow wait duration is too large.");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return durationMs;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function assertWaitFitsCheckpointRetention(durationMs: number): void {
|
|
560
|
+
if (durationMs >= MAX_WORKFLOW_WAIT_MS) {
|
|
561
|
+
throw new Error("Workflow waits must be shorter than 7 days.");
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function waitDurationUnitMultiplier(unit: string): number {
|
|
566
|
+
switch (unit) {
|
|
567
|
+
case "milliseconds":
|
|
568
|
+
return 1;
|
|
569
|
+
case "seconds":
|
|
570
|
+
return 1_000;
|
|
571
|
+
case "minutes":
|
|
572
|
+
return 60_000;
|
|
573
|
+
case "hours":
|
|
574
|
+
return 3_600_000;
|
|
575
|
+
case "days":
|
|
576
|
+
return 86_400_000;
|
|
577
|
+
case "weeks":
|
|
578
|
+
return 604_800_000;
|
|
579
|
+
default:
|
|
580
|
+
throw new Error(`Unsupported workflow wait duration unit: ${unit}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
396
584
|
function createWorkflowStepKey(segments: readonly unknown[]): string {
|
|
397
585
|
if (segments.length === 0) {
|
|
398
586
|
throw new Error("Workflow step keys must contain at least one segment.");
|