deepline 0.1.0 → 0.1.1
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/cli/index.js +212 -54
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +198 -40
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +3256 -0
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +710 -0
- package/dist/repo/apps/play-runner-workers/src/entry.ts +5070 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/README.md +21 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +177 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +52 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +100 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/tool-result.ts +184 -0
- package/dist/repo/sdk/src/cli/commands/auth.ts +482 -0
- package/dist/repo/sdk/src/cli/commands/billing.ts +188 -0
- package/dist/repo/sdk/src/cli/commands/csv.ts +123 -0
- package/dist/repo/sdk/src/cli/commands/db.ts +119 -0
- package/dist/repo/sdk/src/cli/commands/feedback.ts +40 -0
- package/dist/repo/sdk/src/cli/commands/org.ts +117 -0
- package/dist/repo/sdk/src/cli/commands/play.ts +3200 -0
- package/dist/repo/sdk/src/cli/commands/tools.ts +687 -0
- package/dist/repo/sdk/src/cli/dataset-stats.ts +341 -0
- package/dist/repo/sdk/src/cli/index.ts +138 -0
- package/dist/repo/sdk/src/cli/progress.ts +135 -0
- package/dist/repo/sdk/src/cli/trace.ts +61 -0
- package/dist/repo/sdk/src/cli/utils.ts +145 -0
- package/dist/repo/sdk/src/client.ts +1188 -0
- package/dist/repo/sdk/src/compat.ts +77 -0
- package/dist/repo/sdk/src/config.ts +285 -0
- package/dist/repo/sdk/src/errors.ts +125 -0
- package/dist/repo/sdk/src/http.ts +391 -0
- package/dist/repo/sdk/src/index.ts +139 -0
- package/dist/repo/sdk/src/play.ts +1330 -0
- package/dist/repo/sdk/src/plays/bundle-play-file.ts +133 -0
- package/dist/repo/sdk/src/plays/harness-stub.ts +210 -0
- package/dist/repo/sdk/src/plays/local-file-discovery.ts +326 -0
- package/dist/repo/sdk/src/tool-output.ts +489 -0
- package/dist/repo/sdk/src/types.ts +669 -0
- package/dist/repo/sdk/src/version.ts +2 -0
- package/dist/repo/sdk/src/worker-play-entry.ts +286 -0
- package/dist/repo/shared_libs/observability/node-tracing.ts +129 -0
- package/dist/repo/shared_libs/observability/tracing.ts +98 -0
- package/dist/repo/shared_libs/play-runtime/backend.ts +139 -0
- package/dist/repo/shared_libs/play-runtime/batch-runtime.ts +182 -0
- package/dist/repo/shared_libs/play-runtime/batching-types.ts +91 -0
- package/dist/repo/shared_libs/play-runtime/context.ts +3999 -0
- package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +78 -0
- package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +250 -0
- package/dist/repo/shared_libs/play-runtime/ctx-types.ts +713 -0
- package/dist/repo/shared_libs/play-runtime/dataset-id.ts +10 -0
- package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +304 -0
- package/dist/repo/shared_libs/play-runtime/db-session.ts +462 -0
- package/dist/repo/shared_libs/play-runtime/dedup-backend.ts +0 -0
- package/dist/repo/shared_libs/play-runtime/default-batch-strategies.ts +124 -0
- package/dist/repo/shared_libs/play-runtime/execution-plan.ts +262 -0
- package/dist/repo/shared_libs/play-runtime/live-events.ts +214 -0
- package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +50 -0
- package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +114 -0
- package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +158 -0
- package/dist/repo/shared_libs/play-runtime/profiles.ts +90 -0
- package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +172 -0
- package/dist/repo/shared_libs/play-runtime/protocol.ts +121 -0
- package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +42 -0
- package/dist/repo/shared_libs/play-runtime/result-normalization.ts +33 -0
- package/dist/repo/shared_libs/play-runtime/runtime-actions.ts +208 -0
- package/dist/repo/shared_libs/play-runtime/runtime-api.ts +1873 -0
- package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +2 -0
- package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +201 -0
- package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +48 -0
- package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +84 -0
- package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +174 -0
- package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +147 -0
- package/dist/repo/shared_libs/play-runtime/suspension.ts +68 -0
- package/dist/repo/shared_libs/play-runtime/tool-batch-executor.ts +146 -0
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +387 -0
- package/dist/repo/shared_libs/play-runtime/tracing.ts +31 -0
- package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +75 -0
- package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +140 -0
- package/dist/repo/shared_libs/plays/artifact-transport.ts +14 -0
- package/dist/repo/shared_libs/plays/artifact-types.ts +49 -0
- package/dist/repo/shared_libs/plays/bundling/index.ts +1346 -0
- package/dist/repo/shared_libs/plays/compiler-manifest.ts +186 -0
- package/dist/repo/shared_libs/plays/contracts.ts +51 -0
- package/dist/repo/shared_libs/plays/dataset.ts +308 -0
- package/dist/repo/shared_libs/plays/definition.ts +264 -0
- package/dist/repo/shared_libs/plays/file-refs.ts +11 -0
- package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +206 -0
- package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +164 -0
- package/dist/repo/shared_libs/plays/row-identity.ts +302 -0
- package/dist/repo/shared_libs/plays/runtime-validation.ts +415 -0
- package/dist/repo/shared_libs/plays/static-pipeline.ts +560 -0
- package/dist/repo/shared_libs/temporal/constants.ts +39 -0
- package/dist/repo/shared_libs/temporal/preview-config.ts +153 -0
- package/package.json +4 -4
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCompiledPipelineSubsteps,
|
|
3
|
+
type PlayStaticPipeline,
|
|
4
|
+
type PlayStaticSubstep,
|
|
5
|
+
} from '../plays/static-pipeline';
|
|
6
|
+
|
|
7
|
+
export const EXECUTION_PLAN_DEFAULTS = {
|
|
8
|
+
inlineRowsLimit: 1_000,
|
|
9
|
+
largeMapChunkSize: 5_000,
|
|
10
|
+
workflowSoftStepBudget: 20_000,
|
|
11
|
+
workflowHardStepBudget: 25_000,
|
|
12
|
+
ingestStepCount: 1,
|
|
13
|
+
finalizationStepCount: 2,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export type DatasetHandleReference = {
|
|
17
|
+
kind: 'dataset_handle';
|
|
18
|
+
datasetId: string;
|
|
19
|
+
datasetKind: 'csv' | 'map';
|
|
20
|
+
tableNamespace: string;
|
|
21
|
+
count: number;
|
|
22
|
+
previewRows: Record<string, unknown>[];
|
|
23
|
+
backing: 'inline' | 'neon_sheet' | 'r2_file';
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ExecutionPlanMap = {
|
|
27
|
+
mapName: string;
|
|
28
|
+
tableNamespace: string;
|
|
29
|
+
outputFields: string[];
|
|
30
|
+
waterfallStages: Array<{
|
|
31
|
+
waterfallId: string;
|
|
32
|
+
stageIds: string[];
|
|
33
|
+
}>;
|
|
34
|
+
defaultChunkSize: number;
|
|
35
|
+
stepsPerChunk: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ExecutionPlanChunkPlan = {
|
|
39
|
+
inlineRowsLimit: number;
|
|
40
|
+
defaultLargeMapChunkSize: number;
|
|
41
|
+
softWorkflowStepBudget: number;
|
|
42
|
+
hardWorkflowStepBudget: number;
|
|
43
|
+
estimatedWorkflowSteps: number | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type ExecutionPlan = {
|
|
47
|
+
graphHash: string;
|
|
48
|
+
artifactHash: string;
|
|
49
|
+
entrypoint: string;
|
|
50
|
+
assets: Array<{ playPath: string; storageKey: string }>;
|
|
51
|
+
maps: ExecutionPlanMap[];
|
|
52
|
+
toolDeclarations: Array<{ toolId: string; field?: string | null }>;
|
|
53
|
+
chunkPlan: ExecutionPlanChunkPlan;
|
|
54
|
+
persistencePlan: {
|
|
55
|
+
datasetBacking: 'neon_sheet';
|
|
56
|
+
compactWorkflowState: true;
|
|
57
|
+
};
|
|
58
|
+
sdkContract: {
|
|
59
|
+
apiVersion: number;
|
|
60
|
+
artifactVersion: number;
|
|
61
|
+
minRunnerVersion: number;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type BuildExecutionPlanInput = {
|
|
66
|
+
graphHash: string | null | undefined;
|
|
67
|
+
artifactHash: string | null | undefined;
|
|
68
|
+
entrypoint?: string | null;
|
|
69
|
+
assets?: Array<{ playPath?: string | null; storageKey: string }>;
|
|
70
|
+
staticPipeline?: PlayStaticPipeline | null;
|
|
71
|
+
totalRows?: number | null;
|
|
72
|
+
sdkContract?: {
|
|
73
|
+
apiVersion?: number | null;
|
|
74
|
+
artifactVersion?: number | null;
|
|
75
|
+
minRunnerVersion?: number | null;
|
|
76
|
+
} | null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export function buildExecutionPlan(
|
|
80
|
+
input: BuildExecutionPlanInput,
|
|
81
|
+
): ExecutionPlan {
|
|
82
|
+
const maps = extractPlanMaps(input.staticPipeline ?? null);
|
|
83
|
+
const toolDeclarations = extractToolDeclarations(
|
|
84
|
+
input.staticPipeline ?? null,
|
|
85
|
+
);
|
|
86
|
+
const estimatedWorkflowSteps =
|
|
87
|
+
typeof input.totalRows === 'number' && Number.isFinite(input.totalRows)
|
|
88
|
+
? estimateWorkflowSteps({
|
|
89
|
+
totalRows: Math.max(0, Math.floor(input.totalRows)),
|
|
90
|
+
maps,
|
|
91
|
+
})
|
|
92
|
+
: null;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
graphHash: input.graphHash?.trim() ?? '',
|
|
96
|
+
artifactHash: input.artifactHash?.trim() ?? '',
|
|
97
|
+
entrypoint: input.entrypoint?.trim() || 'default',
|
|
98
|
+
assets: (input.assets ?? [])
|
|
99
|
+
.map((asset) => ({
|
|
100
|
+
playPath: String(asset.playPath ?? '').replace(/^\.\//, ''),
|
|
101
|
+
storageKey: asset.storageKey,
|
|
102
|
+
}))
|
|
103
|
+
.filter(
|
|
104
|
+
(asset) => asset.playPath.length > 0 && asset.storageKey.length > 0,
|
|
105
|
+
),
|
|
106
|
+
maps,
|
|
107
|
+
toolDeclarations,
|
|
108
|
+
chunkPlan: {
|
|
109
|
+
inlineRowsLimit: EXECUTION_PLAN_DEFAULTS.inlineRowsLimit,
|
|
110
|
+
defaultLargeMapChunkSize: EXECUTION_PLAN_DEFAULTS.largeMapChunkSize,
|
|
111
|
+
softWorkflowStepBudget: EXECUTION_PLAN_DEFAULTS.workflowSoftStepBudget,
|
|
112
|
+
hardWorkflowStepBudget: EXECUTION_PLAN_DEFAULTS.workflowHardStepBudget,
|
|
113
|
+
estimatedWorkflowSteps,
|
|
114
|
+
},
|
|
115
|
+
persistencePlan: {
|
|
116
|
+
datasetBacking: 'neon_sheet',
|
|
117
|
+
compactWorkflowState: true,
|
|
118
|
+
},
|
|
119
|
+
sdkContract: {
|
|
120
|
+
apiVersion: input.sdkContract?.apiVersion ?? 1,
|
|
121
|
+
artifactVersion: input.sdkContract?.artifactVersion ?? 1,
|
|
122
|
+
minRunnerVersion: input.sdkContract?.minRunnerVersion ?? 1,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function chooseMapChunkSize(input: {
|
|
128
|
+
totalRows: number;
|
|
129
|
+
mapCount: number;
|
|
130
|
+
stepsPerChunk: number;
|
|
131
|
+
preferredChunkSize?: number | null;
|
|
132
|
+
softWorkflowStepBudget?: number | null;
|
|
133
|
+
}): number {
|
|
134
|
+
const totalRows = Math.max(0, Math.floor(input.totalRows));
|
|
135
|
+
if (totalRows <= EXECUTION_PLAN_DEFAULTS.inlineRowsLimit) {
|
|
136
|
+
return Math.max(1, totalRows || 1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const mapCount = Math.max(1, Math.floor(input.mapCount));
|
|
140
|
+
const stepsPerChunk = Math.max(1, Math.floor(input.stepsPerChunk));
|
|
141
|
+
const softBudget =
|
|
142
|
+
input.softWorkflowStepBudget ??
|
|
143
|
+
EXECUTION_PLAN_DEFAULTS.workflowSoftStepBudget;
|
|
144
|
+
const nonChunkSteps =
|
|
145
|
+
EXECUTION_PLAN_DEFAULTS.ingestStepCount +
|
|
146
|
+
EXECUTION_PLAN_DEFAULTS.finalizationStepCount;
|
|
147
|
+
const maxChunksAcrossMaps = Math.max(
|
|
148
|
+
mapCount,
|
|
149
|
+
Math.floor((softBudget - nonChunkSteps) / stepsPerChunk),
|
|
150
|
+
);
|
|
151
|
+
const maxChunksPerMap = Math.max(
|
|
152
|
+
1,
|
|
153
|
+
Math.floor(maxChunksAcrossMaps / mapCount),
|
|
154
|
+
);
|
|
155
|
+
const minimumSurvivalChunkSize = Math.max(
|
|
156
|
+
1,
|
|
157
|
+
Math.ceil(totalRows / maxChunksPerMap),
|
|
158
|
+
);
|
|
159
|
+
const preferred = Math.max(
|
|
160
|
+
1,
|
|
161
|
+
Math.floor(
|
|
162
|
+
input.preferredChunkSize ?? EXECUTION_PLAN_DEFAULTS.largeMapChunkSize,
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
return Math.max(preferred, minimumSurvivalChunkSize);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function deterministicMapChunkStepName(input: {
|
|
169
|
+
mapName: string;
|
|
170
|
+
chunkIndex: number;
|
|
171
|
+
phase?: 'prepare' | 'execute' | 'persist' | string;
|
|
172
|
+
}): string {
|
|
173
|
+
const phase = input.phase?.trim() || 'execute';
|
|
174
|
+
return `map:${input.mapName}:chunk:${String(input.chunkIndex).padStart(4, '0')}:${phase}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function extractPlanMaps(
|
|
178
|
+
pipeline: PlayStaticPipeline | null,
|
|
179
|
+
): ExecutionPlanMap[] {
|
|
180
|
+
if (!pipeline) return [];
|
|
181
|
+
const substeps = getCompiledPipelineSubsteps(pipeline);
|
|
182
|
+
const fallbackWaterfalls = substeps.filter(
|
|
183
|
+
(substep): substep is Extract<PlayStaticSubstep, { type: 'waterfall' }> =>
|
|
184
|
+
substep.type === 'waterfall',
|
|
185
|
+
);
|
|
186
|
+
return substeps
|
|
187
|
+
.filter(
|
|
188
|
+
(substep): substep is Extract<PlayStaticSubstep, { type: 'map' }> =>
|
|
189
|
+
substep.type === 'map',
|
|
190
|
+
)
|
|
191
|
+
.map((mapSubstep) => {
|
|
192
|
+
const waterfalls = fallbackWaterfalls.filter((waterfall) => {
|
|
193
|
+
if (!mapSubstep.waterfallIds?.length) return true;
|
|
194
|
+
return (
|
|
195
|
+
(waterfall.id && mapSubstep.waterfallIds.includes(waterfall.id)) ||
|
|
196
|
+
mapSubstep.waterfallIds.includes(waterfall.field)
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
const waterfallStages = waterfalls.map((waterfall) => ({
|
|
200
|
+
waterfallId: waterfall.id ?? waterfall.field,
|
|
201
|
+
stageIds: waterfall.steps?.map((step) => step.id) ?? [],
|
|
202
|
+
}));
|
|
203
|
+
return {
|
|
204
|
+
mapName: mapSubstep.name ?? mapSubstep.field,
|
|
205
|
+
tableNamespace: mapSubstep.tableNamespace ?? mapSubstep.field,
|
|
206
|
+
outputFields: mapSubstep.outputFields ?? [],
|
|
207
|
+
waterfallStages,
|
|
208
|
+
defaultChunkSize: EXECUTION_PLAN_DEFAULTS.largeMapChunkSize,
|
|
209
|
+
stepsPerChunk: 1,
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function extractToolDeclarations(
|
|
215
|
+
pipeline: PlayStaticPipeline | null,
|
|
216
|
+
): ExecutionPlan['toolDeclarations'] {
|
|
217
|
+
if (!pipeline) return [];
|
|
218
|
+
const seen = new Set<string>();
|
|
219
|
+
const declarations: ExecutionPlan['toolDeclarations'] = [];
|
|
220
|
+
for (const substep of getCompiledPipelineSubsteps(pipeline)) {
|
|
221
|
+
if (substep.type === 'tool') {
|
|
222
|
+
const key = `${substep.toolId}:${substep.field}`;
|
|
223
|
+
if (!seen.has(key)) {
|
|
224
|
+
seen.add(key);
|
|
225
|
+
declarations.push({ toolId: substep.toolId, field: substep.field });
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (substep.type === 'waterfall') {
|
|
230
|
+
for (const step of substep.steps ?? []) {
|
|
231
|
+
if (!step.toolId) continue;
|
|
232
|
+
const key = `${step.toolId}:${substep.field}`;
|
|
233
|
+
if (!seen.has(key)) {
|
|
234
|
+
seen.add(key);
|
|
235
|
+
declarations.push({ toolId: step.toolId, field: substep.field });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return declarations;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function estimateWorkflowSteps(input: {
|
|
244
|
+
totalRows: number;
|
|
245
|
+
maps: ExecutionPlanMap[];
|
|
246
|
+
}): number {
|
|
247
|
+
const maps = input.maps.length > 0 ? input.maps : [];
|
|
248
|
+
const chunkSteps = maps.reduce((sum, map) => {
|
|
249
|
+
const chunkSize = chooseMapChunkSize({
|
|
250
|
+
totalRows: input.totalRows,
|
|
251
|
+
mapCount: maps.length,
|
|
252
|
+
stepsPerChunk: map.stepsPerChunk,
|
|
253
|
+
preferredChunkSize: map.defaultChunkSize,
|
|
254
|
+
});
|
|
255
|
+
return sum + Math.ceil(input.totalRows / chunkSize) * map.stepsPerChunk;
|
|
256
|
+
}, 0);
|
|
257
|
+
return (
|
|
258
|
+
EXECUTION_PLAN_DEFAULTS.ingestStepCount +
|
|
259
|
+
chunkSteps +
|
|
260
|
+
EXECUTION_PLAN_DEFAULTS.finalizationStepCount
|
|
261
|
+
);
|
|
262
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
export type PlayLiveEventSource =
|
|
2
|
+
| 'worker'
|
|
3
|
+
| 'temporal'
|
|
4
|
+
| 'convex'
|
|
5
|
+
| 'play'
|
|
6
|
+
| 'system';
|
|
7
|
+
export type PlayRunTimelineEventType =
|
|
8
|
+
| 'play.run.status'
|
|
9
|
+
| 'play.run.snapshot'
|
|
10
|
+
| 'play.step.status'
|
|
11
|
+
| 'play.step.progress'
|
|
12
|
+
| 'play.run.health'
|
|
13
|
+
| 'play.run.log'
|
|
14
|
+
| 'play.sheet.summary'
|
|
15
|
+
| 'play.sheet.delta';
|
|
16
|
+
|
|
17
|
+
export type PlayRunTimelineLogEntry = {
|
|
18
|
+
kind: 'log';
|
|
19
|
+
at: string;
|
|
20
|
+
source: PlayLiveEventSource;
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type PlayRunTimelineEventEntry = {
|
|
25
|
+
kind: 'event';
|
|
26
|
+
at: string;
|
|
27
|
+
eventType: PlayRunTimelineEventType;
|
|
28
|
+
summary: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type PlayRunTimelineEntry =
|
|
32
|
+
| PlayRunTimelineLogEntry
|
|
33
|
+
| PlayRunTimelineEventEntry;
|
|
34
|
+
|
|
35
|
+
export function makePlayRunTimelineLogEntry(input: {
|
|
36
|
+
at?: string;
|
|
37
|
+
source: PlayLiveEventSource;
|
|
38
|
+
message: string;
|
|
39
|
+
}): PlayRunTimelineLogEntry {
|
|
40
|
+
return {
|
|
41
|
+
kind: 'log',
|
|
42
|
+
at: input.at ?? new Date().toISOString(),
|
|
43
|
+
source: input.source,
|
|
44
|
+
message: input.message,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function makePlayRunTimelineEventEntry(input: {
|
|
49
|
+
at?: string;
|
|
50
|
+
eventType: PlayRunTimelineEventType;
|
|
51
|
+
summary: string;
|
|
52
|
+
}): PlayRunTimelineEventEntry {
|
|
53
|
+
return {
|
|
54
|
+
kind: 'event',
|
|
55
|
+
at: input.at ?? new Date().toISOString(),
|
|
56
|
+
eventType: input.eventType,
|
|
57
|
+
summary: input.summary,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type PlayRuntimeTimingWindow = {
|
|
62
|
+
startedAt?: number | null;
|
|
63
|
+
completedAt?: number | null;
|
|
64
|
+
updatedAt?: number | null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type PlayStepStatusEventPayload = {
|
|
68
|
+
runId: string;
|
|
69
|
+
stepId: string;
|
|
70
|
+
status: 'running' | 'completed' | 'failed' | 'skipped';
|
|
71
|
+
label?: string;
|
|
72
|
+
artifactTableNamespace?: string | null;
|
|
73
|
+
} & PlayRuntimeTimingWindow;
|
|
74
|
+
|
|
75
|
+
export type PlayStepProgressEventPayload = {
|
|
76
|
+
runId: string;
|
|
77
|
+
stepId: string;
|
|
78
|
+
completed?: number;
|
|
79
|
+
total?: number;
|
|
80
|
+
failed?: number;
|
|
81
|
+
message?: string;
|
|
82
|
+
artifactTableNamespace?: string | null;
|
|
83
|
+
} & PlayRuntimeTimingWindow;
|
|
84
|
+
|
|
85
|
+
export type PlayRunHealthEventPayload = {
|
|
86
|
+
runId: string;
|
|
87
|
+
state: 'healthy' | 'degraded';
|
|
88
|
+
reasons: string[];
|
|
89
|
+
message: string;
|
|
90
|
+
taskQueue: string | null;
|
|
91
|
+
workflowPollerCount?: number;
|
|
92
|
+
activityPollerCount?: number;
|
|
93
|
+
lastHeartbeatAgeMs?: number | null;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type PlayRunLogEventPayload = {
|
|
97
|
+
runId: string;
|
|
98
|
+
lines: string[];
|
|
99
|
+
source: PlayLiveEventSource;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type PlaySheetStats = {
|
|
103
|
+
total: number;
|
|
104
|
+
queued: number;
|
|
105
|
+
running: number;
|
|
106
|
+
completed: number;
|
|
107
|
+
failed: number;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type PlaySheetColumnStats = {
|
|
111
|
+
queued: number;
|
|
112
|
+
running: number;
|
|
113
|
+
completed: number;
|
|
114
|
+
failed: number;
|
|
115
|
+
cached: number;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export type PlaySheetSummaryPayload = {
|
|
119
|
+
runId: string;
|
|
120
|
+
tableNamespace: string;
|
|
121
|
+
deltaCursor: number;
|
|
122
|
+
summary: {
|
|
123
|
+
stats: PlaySheetStats;
|
|
124
|
+
columns: Record<string, PlaySheetColumnStats>;
|
|
125
|
+
} | null;
|
|
126
|
+
previewRows: Array<{
|
|
127
|
+
key: string;
|
|
128
|
+
status: string;
|
|
129
|
+
data: Record<string, unknown>;
|
|
130
|
+
cellMeta: Record<string, unknown>;
|
|
131
|
+
}>;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type PlaySheetDeltaEventPayload = {
|
|
135
|
+
runId: string;
|
|
136
|
+
tableNamespace: string;
|
|
137
|
+
nextCursor: number;
|
|
138
|
+
updates: Array<{
|
|
139
|
+
key: string;
|
|
140
|
+
status: string;
|
|
141
|
+
data: Record<string, unknown>;
|
|
142
|
+
cellMeta: Record<string, unknown>;
|
|
143
|
+
inputIndex?: number;
|
|
144
|
+
runId?: string;
|
|
145
|
+
error?: string;
|
|
146
|
+
stage?: string;
|
|
147
|
+
provider?: string;
|
|
148
|
+
seq?: number;
|
|
149
|
+
createdAt: string;
|
|
150
|
+
updatedAt: string;
|
|
151
|
+
}>;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export function resolveTimingWindow(input: {
|
|
155
|
+
startedAt?: number | null;
|
|
156
|
+
completedAt?: number | null;
|
|
157
|
+
updatedAt?: number | null;
|
|
158
|
+
}): PlayRuntimeTimingWindow {
|
|
159
|
+
const startedAt =
|
|
160
|
+
typeof input.startedAt === 'number' && Number.isFinite(input.startedAt)
|
|
161
|
+
? input.startedAt
|
|
162
|
+
: null;
|
|
163
|
+
const completedAt =
|
|
164
|
+
typeof input.completedAt === 'number' && Number.isFinite(input.completedAt)
|
|
165
|
+
? input.completedAt
|
|
166
|
+
: null;
|
|
167
|
+
const updatedAt =
|
|
168
|
+
typeof input.updatedAt === 'number' && Number.isFinite(input.updatedAt)
|
|
169
|
+
? input.updatedAt
|
|
170
|
+
: null;
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
startedAt,
|
|
174
|
+
completedAt,
|
|
175
|
+
updatedAt,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getTimingDurationMs(input: {
|
|
180
|
+
startedAt?: number | null;
|
|
181
|
+
completedAt?: number | null;
|
|
182
|
+
}): number | null {
|
|
183
|
+
return typeof input.startedAt === 'number' &&
|
|
184
|
+
Number.isFinite(input.startedAt) &&
|
|
185
|
+
typeof input.completedAt === 'number' &&
|
|
186
|
+
Number.isFinite(input.completedAt)
|
|
187
|
+
? Math.max(0, input.completedAt - input.startedAt)
|
|
188
|
+
: null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function getTimingActiveDurationMs(input: {
|
|
192
|
+
startedAt?: number | null;
|
|
193
|
+
completedAt?: number | null;
|
|
194
|
+
updatedAt?: number | null;
|
|
195
|
+
now?: number;
|
|
196
|
+
}): number | null {
|
|
197
|
+
if (
|
|
198
|
+
typeof input.startedAt !== 'number' ||
|
|
199
|
+
!Number.isFinite(input.startedAt)
|
|
200
|
+
) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (
|
|
204
|
+
typeof input.completedAt === 'number' &&
|
|
205
|
+
Number.isFinite(input.completedAt)
|
|
206
|
+
) {
|
|
207
|
+
return Math.max(0, input.completedAt - input.startedAt);
|
|
208
|
+
}
|
|
209
|
+
const referenceAt =
|
|
210
|
+
typeof input.updatedAt === 'number' && Number.isFinite(input.updatedAt)
|
|
211
|
+
? input.updatedAt
|
|
212
|
+
: input.now ?? Date.now();
|
|
213
|
+
return Math.max(0, referenceAt - input.startedAt);
|
|
214
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type PlayRunLiveStatus =
|
|
2
|
+
| 'queued'
|
|
3
|
+
| 'running'
|
|
4
|
+
| 'completed'
|
|
5
|
+
| 'failed'
|
|
6
|
+
| 'cancelled'
|
|
7
|
+
| 'terminated'
|
|
8
|
+
| 'timed_out'
|
|
9
|
+
| 'unknown';
|
|
10
|
+
|
|
11
|
+
export type PlayVisualNodeStatus =
|
|
12
|
+
| 'idle'
|
|
13
|
+
| 'running'
|
|
14
|
+
| 'completed'
|
|
15
|
+
| 'failed'
|
|
16
|
+
| 'skipped';
|
|
17
|
+
|
|
18
|
+
export type PlayStepLiveStatus = Exclude<PlayVisualNodeStatus, 'idle'>;
|
|
19
|
+
|
|
20
|
+
export type PlayVisualNodeProgressSnapshot = {
|
|
21
|
+
completed?: number;
|
|
22
|
+
total?: number;
|
|
23
|
+
failed?: number;
|
|
24
|
+
message?: string;
|
|
25
|
+
updatedAt?: number | null;
|
|
26
|
+
startedAt?: number | null;
|
|
27
|
+
completedAt?: number | null;
|
|
28
|
+
artifactTableNamespace?: string | null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type PlayVisualNodeStateSnapshot = {
|
|
32
|
+
nodeId: string;
|
|
33
|
+
status: PlayVisualNodeStatus;
|
|
34
|
+
artifactTableNamespace?: string | null;
|
|
35
|
+
progress?: PlayVisualNodeProgressSnapshot | null;
|
|
36
|
+
startedAt?: number | null;
|
|
37
|
+
completedAt?: number | null;
|
|
38
|
+
updatedAt?: number | null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type PlayRunLiveSnapshot = {
|
|
42
|
+
runId: string;
|
|
43
|
+
status: PlayRunLiveStatus;
|
|
44
|
+
updatedAt: number | null;
|
|
45
|
+
logs: string[];
|
|
46
|
+
activeArtifactTableNamespace: string | null;
|
|
47
|
+
resultTableNamespace: string | null;
|
|
48
|
+
nodeStates: PlayVisualNodeStateSnapshot[];
|
|
49
|
+
activeNodeId: string | null;
|
|
50
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MapExecutionFrame,
|
|
3
|
+
MapExecutionScope,
|
|
4
|
+
PlayCheckpoint,
|
|
5
|
+
PlayExecutionEvent,
|
|
6
|
+
} from './ctx-types';
|
|
7
|
+
|
|
8
|
+
export function cloneMapFrame(frame: MapExecutionFrame): MapExecutionFrame {
|
|
9
|
+
return {
|
|
10
|
+
...frame,
|
|
11
|
+
completedRowKeys: [...frame.completedRowKeys],
|
|
12
|
+
pendingRowKeys: [...frame.pendingRowKeys],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class MapExecutionFrameStore {
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly checkpoint: PlayCheckpoint,
|
|
19
|
+
private readonly emitExecutionEvent: (event: PlayExecutionEvent) => void,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
set(frame: MapExecutionFrame): void {
|
|
23
|
+
this.checkpoint.mapFrames = {
|
|
24
|
+
...(this.checkpoint.mapFrames ?? {}),
|
|
25
|
+
[frame.mapInvocationId]: cloneMapFrame(frame),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
start(input: {
|
|
30
|
+
scope: MapExecutionScope;
|
|
31
|
+
totalRows: number;
|
|
32
|
+
completedRowKeys: readonly string[];
|
|
33
|
+
pendingRowKeys: readonly string[];
|
|
34
|
+
}): void {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
this.set({
|
|
37
|
+
mapInvocationId: input.scope.mapInvocationId,
|
|
38
|
+
mapNodeId: input.scope.mapNodeId ?? null,
|
|
39
|
+
logicalNamespace: input.scope.logicalNamespace,
|
|
40
|
+
artifactTableNamespace: input.scope.artifactTableNamespace,
|
|
41
|
+
status: 'running',
|
|
42
|
+
totalRows: input.totalRows,
|
|
43
|
+
completedRowKeys: [...input.completedRowKeys],
|
|
44
|
+
pendingRowKeys: [...input.pendingRowKeys],
|
|
45
|
+
startedAt: now,
|
|
46
|
+
updatedAt: now,
|
|
47
|
+
});
|
|
48
|
+
this.emitExecutionEvent({
|
|
49
|
+
type: 'map.started',
|
|
50
|
+
mapInvocationId: input.scope.mapInvocationId,
|
|
51
|
+
mapNodeId: input.scope.mapNodeId ?? null,
|
|
52
|
+
logicalNamespace: input.scope.logicalNamespace,
|
|
53
|
+
artifactTableNamespace: input.scope.artifactTableNamespace,
|
|
54
|
+
totalRows: input.totalRows,
|
|
55
|
+
completedRows: input.completedRowKeys.length,
|
|
56
|
+
pendingRows: input.pendingRowKeys.length,
|
|
57
|
+
at: Date.now(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
updateProgress(input: {
|
|
62
|
+
scope: MapExecutionScope;
|
|
63
|
+
totalRows: number;
|
|
64
|
+
status?: MapExecutionFrame['status'];
|
|
65
|
+
completedRowKey?: string | null;
|
|
66
|
+
pendingRowKey?: string | null;
|
|
67
|
+
activeBoundaryId?: string | null;
|
|
68
|
+
emitEventType?: PlayExecutionEvent['type'];
|
|
69
|
+
}): void {
|
|
70
|
+
const existing =
|
|
71
|
+
this.checkpoint.mapFrames?.[input.scope.mapInvocationId] ?? null;
|
|
72
|
+
if (!existing) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const completedRowKeys = new Set(existing.completedRowKeys);
|
|
76
|
+
const pendingRowKeys = new Set(existing.pendingRowKeys);
|
|
77
|
+
if (input.completedRowKey?.trim()) {
|
|
78
|
+
completedRowKeys.add(input.completedRowKey.trim());
|
|
79
|
+
pendingRowKeys.delete(input.completedRowKey.trim());
|
|
80
|
+
}
|
|
81
|
+
if (input.pendingRowKey?.trim()) {
|
|
82
|
+
pendingRowKeys.add(input.pendingRowKey.trim());
|
|
83
|
+
}
|
|
84
|
+
const nextFrame: MapExecutionFrame = {
|
|
85
|
+
...existing,
|
|
86
|
+
status: input.status ?? existing.status,
|
|
87
|
+
completedRowKeys: [...completedRowKeys],
|
|
88
|
+
pendingRowKeys: [...pendingRowKeys],
|
|
89
|
+
...(input.activeBoundaryId !== undefined
|
|
90
|
+
? { activeBoundaryId: input.activeBoundaryId }
|
|
91
|
+
: {}),
|
|
92
|
+
updatedAt: Date.now(),
|
|
93
|
+
};
|
|
94
|
+
this.set(nextFrame);
|
|
95
|
+
if (input.emitEventType) {
|
|
96
|
+
this.emitExecutionEvent({
|
|
97
|
+
type: input.emitEventType,
|
|
98
|
+
mapInvocationId: input.scope.mapInvocationId,
|
|
99
|
+
mapNodeId: input.scope.mapNodeId ?? null,
|
|
100
|
+
logicalNamespace: input.scope.logicalNamespace,
|
|
101
|
+
artifactTableNamespace: input.scope.artifactTableNamespace,
|
|
102
|
+
completedRows: nextFrame.completedRowKeys.length,
|
|
103
|
+
failedRows: Math.max(
|
|
104
|
+
0,
|
|
105
|
+
input.totalRows -
|
|
106
|
+
nextFrame.completedRowKeys.length -
|
|
107
|
+
nextFrame.pendingRowKeys.length,
|
|
108
|
+
),
|
|
109
|
+
totalRows: input.totalRows,
|
|
110
|
+
at: Date.now(),
|
|
111
|
+
} as PlayExecutionEvent);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|