deepline 0.1.79 → 0.1.80

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.
Files changed (26) hide show
  1. package/dist/cli/index.js +68 -31
  2. package/dist/cli/index.mjs +68 -31
  3. package/dist/index.d.mts +9 -1
  4. package/dist/index.d.ts +9 -1
  5. package/dist/index.js +7 -4
  6. package/dist/index.mjs +7 -4
  7. package/dist/repo/apps/play-runner-workers/src/child-play-await.ts +192 -0
  8. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +1102 -1616
  9. package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +506 -654
  10. package/dist/repo/apps/play-runner-workers/src/entry.ts +896 -354
  11. package/dist/repo/apps/play-runner-workers/src/workflow-retry-state.ts +8 -2
  12. package/dist/repo/sdk/src/client.ts +9 -2
  13. package/dist/repo/sdk/src/release.ts +2 -2
  14. package/dist/repo/sdk/src/types.ts +5 -0
  15. package/dist/repo/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +231 -0
  16. package/dist/repo/shared_libs/play-runtime/governor/governor.ts +376 -0
  17. package/dist/repo/shared_libs/play-runtime/governor/policy.ts +179 -0
  18. package/dist/repo/shared_libs/play-runtime/governor/rate-state-backend.ts +87 -0
  19. package/dist/repo/shared_libs/play-runtime/run-failure.ts +12 -0
  20. package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +24 -0
  21. package/dist/repo/shared_libs/play-runtime/submit-limits.ts +35 -0
  22. package/dist/repo/shared_libs/plays/bundling/index.ts +4 -12
  23. package/dist/repo/shared_libs/plays/bundling/limits.ts +29 -0
  24. package/dist/repo/shared_libs/plays/static-pipeline.ts +56 -3
  25. package/dist/repo/shared_libs/temporal/constants.ts +38 -0
  26. package/package.json +1 -1
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Child-play terminal await — the race between a Cloudflare Workflow
3
+ * `waitForEvent('child_play_terminal:...')` and a coordinator terminal-state
4
+ * poll, extracted from entry.ts so the runtime keeps a small, testable surface
5
+ * for "block until this child run reaches a terminal state".
6
+ *
7
+ * Behavior is identical to the inline implementation it replaces: arm the
8
+ * workflow event wait, race it against `pollParentChildTerminalState`, and
9
+ * resolve on whichever reports the child's terminal status first. The poll
10
+ * never resolves on a non-terminal/absent state (it returns a never-settling
11
+ * promise on timeout) so the event wait remains the primary path and the poll
12
+ * is a coordinator-side safety net for missed signals.
13
+ */
14
+
15
+ export type WorkflowStepLike = {
16
+ waitForEvent: (
17
+ name: string,
18
+ options: { type: string; timeout: string },
19
+ ) => Promise<{ payload: unknown }>;
20
+ };
21
+
22
+ export type ChildTerminalCoordinator = {
23
+ readChildTerminalState(
24
+ parentRunId: string,
25
+ eventKey: string,
26
+ timeoutMs?: number,
27
+ ): Promise<{ data?: unknown } | null>;
28
+ };
29
+
30
+ export type ChildPlayTerminalWaitResult = {
31
+ output: unknown;
32
+ source: 'workflow_event' | 'parent_child_terminal_cache';
33
+ attempts?: number;
34
+ waitMs: number;
35
+ };
36
+
37
+ export interface AwaitChildTerminalInput {
38
+ parentRunId: string;
39
+ workflowStep: WorkflowStepLike | undefined;
40
+ workflowId: string;
41
+ playName: string;
42
+ key: string;
43
+ timeoutMs: number;
44
+ /** Coordinator binding for the terminal-state poll. Null disables the poll. */
45
+ coordinator: ChildTerminalCoordinator | null;
46
+ now: () => number;
47
+ /** SHA-256 hex digest helper (same canonical hash entry.ts uses). */
48
+ hashJson: (value: unknown) => Promise<string>;
49
+ }
50
+
51
+ function isRecord(value: unknown): value is Record<string, unknown> {
52
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
53
+ }
54
+
55
+ function workflowEventType(name: string): string {
56
+ const normalized = name
57
+ .trim()
58
+ .replace(/[^A-Za-z0-9_-]+/g, '_')
59
+ .replace(/^_+|_+$/g, '')
60
+ .slice(0, 100);
61
+ return normalized || 'deepline_event';
62
+ }
63
+
64
+ function integrationEventType(eventKey: string): string {
65
+ return workflowEventType(`integration_event_${eventKey}`);
66
+ }
67
+
68
+ function workflowTimeoutFromMs(timeoutMs: number): string {
69
+ const seconds = Math.max(1, Math.ceil(timeoutMs / 1000));
70
+ return `${seconds} second${seconds === 1 ? '' : 's'}`;
71
+ }
72
+
73
+ function readChildTerminalPayload(value: unknown): Record<string, unknown> {
74
+ if (!isRecord(value)) return {};
75
+ const data = value.data;
76
+ return isRecord(data) ? data : value;
77
+ }
78
+
79
+ function extractChildPlayOutput(status: Record<string, unknown>): unknown {
80
+ const result = status.result;
81
+ if (isRecord(result) && 'output' in result) {
82
+ return result.output;
83
+ }
84
+ return result ?? null;
85
+ }
86
+
87
+ async function childPlayEventKey(input: {
88
+ key: string;
89
+ workflowId: string;
90
+ hashJson: (value: unknown) => Promise<string>;
91
+ }): Promise<string> {
92
+ const readableKey = workflowEventType(input.key).slice(0, 40);
93
+ const digest = (
94
+ await input.hashJson({ key: input.key, workflowId: input.workflowId })
95
+ ).slice(0, 32);
96
+ return `child_play_${digest}_${readableKey}`;
97
+ }
98
+
99
+ async function pollParentChildTerminalState(input: {
100
+ coordinator: ChildTerminalCoordinator | null;
101
+ parentRunId: string;
102
+ eventKey: string;
103
+ timeoutMs: number;
104
+ now: () => number;
105
+ }): Promise<{
106
+ source: 'parent_child_terminal_cache';
107
+ payload: Record<string, unknown>;
108
+ attempts: number;
109
+ }> {
110
+ const { coordinator, parentRunId } = input;
111
+ if (!coordinator?.readChildTerminalState || !parentRunId) {
112
+ return await new Promise(() => undefined);
113
+ }
114
+ const startedAt = input.now();
115
+ let attempts = 0;
116
+ while (input.now() - startedAt < input.timeoutMs) {
117
+ attempts += 1;
118
+ const remainingMs = Math.max(
119
+ 0,
120
+ input.timeoutMs - (input.now() - startedAt),
121
+ );
122
+ const waitMs = Math.min(remainingMs, 30_000);
123
+ const state = await coordinator
124
+ .readChildTerminalState(parentRunId, input.eventKey, waitMs)
125
+ .catch(() => null);
126
+ if (isRecord(state?.data)) {
127
+ return {
128
+ source: 'parent_child_terminal_cache',
129
+ payload: state.data,
130
+ attempts,
131
+ };
132
+ }
133
+ }
134
+ return await new Promise(() => undefined);
135
+ }
136
+
137
+ /**
138
+ * Block until the child run reaches a terminal state, via the workflow event
139
+ * wait raced against the coordinator terminal-state poll. Throws on a non-
140
+ * completed terminal status.
141
+ */
142
+ export async function awaitChildTerminal(
143
+ input: AwaitChildTerminalInput,
144
+ ): Promise<ChildPlayTerminalWaitResult> {
145
+ if (!input.workflowStep) {
146
+ throw new Error(
147
+ 'ctx.runPlay child waits require the cf-workflows runtime event scheduler.',
148
+ );
149
+ }
150
+ const waitStartedAt = input.now();
151
+ const eventKey = await childPlayEventKey({
152
+ key: input.key,
153
+ workflowId: input.workflowId,
154
+ hashJson: input.hashJson,
155
+ });
156
+ const eventPromise = input.workflowStep
157
+ .waitForEvent(`child_play_terminal:${eventKey}`, {
158
+ type: integrationEventType(eventKey),
159
+ timeout: workflowTimeoutFromMs(input.timeoutMs),
160
+ })
161
+ .then((event) => ({
162
+ source: 'workflow_event' as const,
163
+ payload: readChildTerminalPayload(event.payload),
164
+ attempts: undefined as number | undefined,
165
+ }));
166
+ const terminal = await Promise.race([
167
+ eventPromise,
168
+ pollParentChildTerminalState({
169
+ coordinator: input.coordinator,
170
+ parentRunId: input.parentRunId,
171
+ eventKey,
172
+ timeoutMs: Math.min(input.timeoutMs, 30_000),
173
+ now: input.now,
174
+ }),
175
+ ]);
176
+ const payload = terminal.payload;
177
+ const status = String(payload.status ?? '').toLowerCase();
178
+ if (status === 'completed') {
179
+ return {
180
+ output: extractChildPlayOutput(payload),
181
+ source: terminal.source,
182
+ attempts: terminal.attempts,
183
+ waitMs: input.now() - waitStartedAt,
184
+ };
185
+ }
186
+ const error = isRecord(payload.error) ? payload.error : null;
187
+ const message =
188
+ (typeof error?.message === 'string' && error.message.trim()) ||
189
+ (typeof payload.error === 'string' && payload.error.trim()) ||
190
+ `Child play ${input.playName} (${input.workflowId}) finished with status ${status || 'unknown'}.`;
191
+ throw new Error(message);
192
+ }