deepline 0.1.106 → 0.1.108
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 +499 -55
- package/dist/cli/index.mjs +495 -51
- package/dist/index.d.mts +21 -5
- package/dist/index.d.ts +21 -5
- package/dist/index.js +20 -4
- package/dist/index.mjs +20 -4
- package/dist/repo/apps/play-runner-workers/src/entry.ts +55 -44
- package/dist/repo/sdk/src/play.ts +21 -4
- package/dist/repo/sdk/src/release.ts +49 -7
- package/dist/repo/sdk/src/worker-play-entry.ts +4 -0
- package/dist/repo/shared_libs/play-runtime/cell-staleness.ts +64 -5
- package/dist/repo/shared_libs/play-runtime/run-ledger.ts +6 -0
- package/dist/repo/shared_libs/play-runtime/step-program-dataset-builder.ts +8 -0
- package/dist/repo/shared_libs/plays/static-pipeline.ts +87 -3
- package/package.json +1 -1
|
@@ -15,11 +15,15 @@ export type AuthoredStaleAfterSeconds<Value = unknown> =
|
|
|
15
15
|
|
|
16
16
|
/** Freshness policy as written by play authors before runtime normalization. */
|
|
17
17
|
export type AuthoredCellStalenessPolicy<Value = unknown> = {
|
|
18
|
+
recompute?: boolean;
|
|
19
|
+
recomputeOnError?: boolean;
|
|
18
20
|
staleAfterSeconds?: AuthoredStaleAfterSeconds<Value>;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
/** Runtime-normalized freshness policy stored in execution plans. */
|
|
22
24
|
export type CellStalenessPolicy = {
|
|
25
|
+
recompute?: boolean;
|
|
26
|
+
recomputeOnError?: boolean;
|
|
23
27
|
staleAfterSeconds?: number;
|
|
24
28
|
dynamicStaleAfterSeconds?: boolean;
|
|
25
29
|
};
|
|
@@ -53,7 +57,10 @@ export type PreviousCell<Value = unknown> = {
|
|
|
53
57
|
};
|
|
54
58
|
|
|
55
59
|
export type CellStalenessDecision =
|
|
56
|
-
| {
|
|
60
|
+
| {
|
|
61
|
+
action: 'recompute';
|
|
62
|
+
reason: 'missing' | 'failed' | 'forced' | 'stale';
|
|
63
|
+
}
|
|
57
64
|
| { action: 'reuse'; reason: 'fresh' | 'no_policy' | 'no_expiry' };
|
|
58
65
|
|
|
59
66
|
export type CellStalenessPolicyByField = Record<string, CellStalenessPolicy>;
|
|
@@ -83,15 +90,36 @@ export function validateStaleAfterSeconds(
|
|
|
83
90
|
export function normalizeCellStalenessPolicy(
|
|
84
91
|
policy: AuthoredCellStalenessPolicy | undefined,
|
|
85
92
|
): CellStalenessPolicy {
|
|
93
|
+
if (
|
|
94
|
+
policy?.recompute !== undefined &&
|
|
95
|
+
typeof policy.recompute !== 'boolean'
|
|
96
|
+
) {
|
|
97
|
+
throw new Error('recompute must be a boolean.');
|
|
98
|
+
}
|
|
99
|
+
if (
|
|
100
|
+
policy?.recomputeOnError !== undefined &&
|
|
101
|
+
typeof policy.recomputeOnError !== 'boolean'
|
|
102
|
+
) {
|
|
103
|
+
throw new Error('recomputeOnError must be a boolean.');
|
|
104
|
+
}
|
|
105
|
+
const recompute = policy?.recompute === true;
|
|
106
|
+
const recomputeOnError = policy?.recomputeOnError === true;
|
|
107
|
+
const flags = {
|
|
108
|
+
...(recompute ? { recompute: true } : {}),
|
|
109
|
+
...(recomputeOnError ? { recomputeOnError: true } : {}),
|
|
110
|
+
};
|
|
86
111
|
const staleAfterSeconds = policy?.staleAfterSeconds;
|
|
87
112
|
if (staleAfterSeconds === undefined) {
|
|
88
|
-
return
|
|
113
|
+
return flags;
|
|
89
114
|
}
|
|
90
115
|
if (typeof staleAfterSeconds === 'function') {
|
|
91
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
...flags,
|
|
118
|
+
dynamicStaleAfterSeconds: true,
|
|
119
|
+
};
|
|
92
120
|
}
|
|
93
121
|
validateStaleAfterSeconds(staleAfterSeconds);
|
|
94
|
-
return { staleAfterSeconds };
|
|
122
|
+
return { ...flags, staleAfterSeconds };
|
|
95
123
|
}
|
|
96
124
|
|
|
97
125
|
export function resolveCompletedCellStalenessMeta<Value>(input: {
|
|
@@ -162,6 +190,7 @@ export function previousCellFromValue<Value>(input: {
|
|
|
162
190
|
|
|
163
191
|
export function shouldRecomputeCell(input: {
|
|
164
192
|
hasValue: boolean;
|
|
193
|
+
value?: unknown;
|
|
165
194
|
meta?: CellStalenessMeta | null;
|
|
166
195
|
policy?: CellStalenessPolicy;
|
|
167
196
|
nowMs?: number;
|
|
@@ -175,6 +204,17 @@ export function shouldRecomputeCell(input: {
|
|
|
175
204
|
return { action: 'recompute', reason: 'failed' };
|
|
176
205
|
}
|
|
177
206
|
|
|
207
|
+
if (input.policy?.recompute === true) {
|
|
208
|
+
return { action: 'recompute', reason: 'forced' };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (
|
|
212
|
+
input.policy?.recomputeOnError === true &&
|
|
213
|
+
isErrorLikeCellValue(input.value)
|
|
214
|
+
) {
|
|
215
|
+
return { action: 'recompute', reason: 'failed' };
|
|
216
|
+
}
|
|
217
|
+
|
|
178
218
|
const staleAt =
|
|
179
219
|
input.meta && Object.prototype.hasOwnProperty.call(input.meta, 'staleAt')
|
|
180
220
|
? input.meta.staleAt
|
|
@@ -254,7 +294,26 @@ export function cellPolicyFields(
|
|
|
254
294
|
.filter(
|
|
255
295
|
([, policy]) =>
|
|
256
296
|
policy.staleAfterSeconds !== undefined ||
|
|
257
|
-
policy.dynamicStaleAfterSeconds === true
|
|
297
|
+
policy.dynamicStaleAfterSeconds === true ||
|
|
298
|
+
policy.recompute === true ||
|
|
299
|
+
policy.recomputeOnError === true,
|
|
258
300
|
)
|
|
259
301
|
.map(([field]) => field);
|
|
260
302
|
}
|
|
303
|
+
|
|
304
|
+
function isErrorLikeCellValue(value: unknown): boolean {
|
|
305
|
+
if (typeof value === 'string') {
|
|
306
|
+
return /(^|: )error(:|$)/i.test(value.trim());
|
|
307
|
+
}
|
|
308
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
const record = value as Record<string, unknown>;
|
|
312
|
+
const raw = (record.toolResponse as { raw?: unknown } | undefined)?.raw;
|
|
313
|
+
return (
|
|
314
|
+
(raw !== undefined && isErrorLikeCellValue(raw)) ||
|
|
315
|
+
(typeof record.error === 'string' && record.error.trim().length > 0) ||
|
|
316
|
+
record.status === 'failed' ||
|
|
317
|
+
record.status === 'error'
|
|
318
|
+
);
|
|
319
|
+
}
|
|
@@ -438,6 +438,12 @@ function normalizeStepProgress(
|
|
|
438
438
|
),
|
|
439
439
|
}
|
|
440
440
|
: {}),
|
|
441
|
+
...(finiteNumber(value.startedAt) !== null
|
|
442
|
+
? { startedAt: finiteNumber(value.startedAt) }
|
|
443
|
+
: {}),
|
|
444
|
+
...(finiteNumber(value.completedAt) !== null
|
|
445
|
+
? { completedAt: finiteNumber(value.completedAt) }
|
|
446
|
+
: {}),
|
|
441
447
|
...(finiteNumber(value.updatedAt) !== null
|
|
442
448
|
? { updatedAt: finiteNumber(value.updatedAt) }
|
|
443
449
|
: {}),
|
|
@@ -5,6 +5,8 @@ export type StepProgramDatasetOptions = {
|
|
|
5
5
|
row: Record<string, unknown>,
|
|
6
6
|
index: number,
|
|
7
7
|
) => boolean | Promise<boolean>;
|
|
8
|
+
recompute?: boolean;
|
|
9
|
+
recomputeOnError?: boolean;
|
|
8
10
|
staleAfterSeconds?: AuthoredStaleAfterSeconds;
|
|
9
11
|
};
|
|
10
12
|
|
|
@@ -48,6 +50,8 @@ function isPreviousCell(value: unknown): value is PreviousCell {
|
|
|
48
50
|
|
|
49
51
|
export type StepProgramDatasetStep<TResolver> = {
|
|
50
52
|
name: string;
|
|
53
|
+
recompute?: boolean;
|
|
54
|
+
recomputeOnError?: boolean;
|
|
51
55
|
resolver: TResolver;
|
|
52
56
|
staleAfterSeconds?: AuthoredStaleAfterSeconds;
|
|
53
57
|
};
|
|
@@ -133,6 +137,10 @@ export class StepProgramDatasetBuilder<
|
|
|
133
137
|
...(normalized.options?.staleAfterSeconds !== undefined
|
|
134
138
|
? { staleAfterSeconds: normalized.options.staleAfterSeconds }
|
|
135
139
|
: {}),
|
|
140
|
+
...(normalized.options?.recompute === true ? { recompute: true } : {}),
|
|
141
|
+
...(normalized.options?.recomputeOnError === true
|
|
142
|
+
? { recomputeOnError: true }
|
|
143
|
+
: {}),
|
|
136
144
|
} as TStep,
|
|
137
145
|
];
|
|
138
146
|
return this;
|
|
@@ -32,6 +32,61 @@ export interface PlayStaticPipeline {
|
|
|
32
32
|
sheetContractErrors?: string[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function dedupeStaticFieldNames(
|
|
36
|
+
fields: Array<string | null | undefined>,
|
|
37
|
+
): string[] {
|
|
38
|
+
const seen = new Set<string>();
|
|
39
|
+
const out: string[] = [];
|
|
40
|
+
for (const field of fields) {
|
|
41
|
+
const trimmed = typeof field === 'string' ? field.trim() : '';
|
|
42
|
+
if (!trimmed || seen.has(trimmed)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
seen.add(trimmed);
|
|
46
|
+
out.push(trimmed);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function inputFieldFromStaticCsvArg(csvArg: unknown): string | null {
|
|
52
|
+
if (typeof csvArg !== 'string') return null;
|
|
53
|
+
const match = /^input\.([A-Za-z_$][\w$]*)$/.exec(csvArg.trim());
|
|
54
|
+
return match?.[1] ?? null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function deriveStaticPipelineFileInputFields(
|
|
58
|
+
pipeline: PlayStaticPipeline | null | undefined,
|
|
59
|
+
): string[] {
|
|
60
|
+
if (!pipeline) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const explicitCsvFields = getCompiledPipelineSubsteps(pipeline)
|
|
64
|
+
.filter((substep) => substep.type === 'csv')
|
|
65
|
+
.map((substep) =>
|
|
66
|
+
substep.type === 'csv'
|
|
67
|
+
? (inputFieldFromStaticCsvArg(substep.path) ?? substep.field)
|
|
68
|
+
: null,
|
|
69
|
+
);
|
|
70
|
+
const inferredCsvField =
|
|
71
|
+
explicitCsvFields.length > 0
|
|
72
|
+
? null
|
|
73
|
+
: (inputFieldFromStaticCsvArg(pipeline.csvArg) ??
|
|
74
|
+
(pipeline.csvArg ? 'csv' : null));
|
|
75
|
+
return dedupeStaticFieldNames([...explicitCsvFields, inferredCsvField]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function deriveStaticPipelineEntryInputFields(
|
|
79
|
+
pipeline: PlayStaticPipeline | null | undefined,
|
|
80
|
+
): string[] {
|
|
81
|
+
if (!pipeline) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const fileInputs = deriveStaticPipelineFileInputFields(pipeline);
|
|
85
|
+
return fileInputs.length > 0
|
|
86
|
+
? fileInputs
|
|
87
|
+
: dedupeStaticFieldNames(pipeline.inputFields ?? []);
|
|
88
|
+
}
|
|
89
|
+
|
|
35
90
|
export type PlaySheetColumnSource =
|
|
36
91
|
| 'input'
|
|
37
92
|
| 'datasetColumn'
|
|
@@ -73,6 +128,8 @@ export interface PlayStaticColumnProducer {
|
|
|
73
128
|
toolId?: string;
|
|
74
129
|
playId?: string;
|
|
75
130
|
dependsOnFields?: string[];
|
|
131
|
+
recompute?: boolean;
|
|
132
|
+
recomputeOnError?: boolean;
|
|
76
133
|
staleAfterSeconds?: number;
|
|
77
134
|
conditional?: boolean;
|
|
78
135
|
sourceRange?: PlayStaticSourceRange;
|
|
@@ -84,6 +141,8 @@ export interface PlayStaticDatasetColumn {
|
|
|
84
141
|
id: string;
|
|
85
142
|
source: PlaySheetColumnSource;
|
|
86
143
|
sqlName?: string;
|
|
144
|
+
recompute?: boolean;
|
|
145
|
+
recomputeOnError?: boolean;
|
|
87
146
|
staleAfterSeconds?: number;
|
|
88
147
|
producers: PlayStaticColumnProducer[];
|
|
89
148
|
}
|
|
@@ -275,6 +334,8 @@ export interface PlayStaticSourceRange {
|
|
|
275
334
|
type PlayStaticSubstepMetadata = {
|
|
276
335
|
conditional?: boolean;
|
|
277
336
|
dependsOnFields?: string[];
|
|
337
|
+
recompute?: boolean;
|
|
338
|
+
recomputeOnError?: boolean;
|
|
278
339
|
staleAfterSeconds?: number;
|
|
279
340
|
};
|
|
280
341
|
|
|
@@ -600,13 +661,18 @@ function compileDatasetColumns(
|
|
|
600
661
|
);
|
|
601
662
|
if (!column) continue;
|
|
602
663
|
const pipelineSubstep =
|
|
603
|
-
substep.staleAfterSeconds === undefined
|
|
664
|
+
substep.staleAfterSeconds === undefined &&
|
|
665
|
+
substep.recompute !== true &&
|
|
666
|
+
substep.recomputeOnError !== true
|
|
604
667
|
? pipelineSubsteps.find(
|
|
605
668
|
(candidate) => fieldForColumnProducer(candidate) === field,
|
|
606
669
|
)
|
|
607
670
|
: undefined;
|
|
608
671
|
const producer = columnProducerFromSubstep(
|
|
609
|
-
pipelineSubstep &&
|
|
672
|
+
pipelineSubstep &&
|
|
673
|
+
(pipelineSubstep.staleAfterSeconds !== undefined ||
|
|
674
|
+
pipelineSubstep.recompute === true ||
|
|
675
|
+
pipelineSubstep.recomputeOnError === true)
|
|
610
676
|
? pipelineSubstep
|
|
611
677
|
: substep,
|
|
612
678
|
field,
|
|
@@ -618,6 +684,15 @@ function compileDatasetColumns(
|
|
|
618
684
|
) {
|
|
619
685
|
column.staleAfterSeconds = producer.staleAfterSeconds;
|
|
620
686
|
}
|
|
687
|
+
if (column.recompute !== true && producer.recompute === true) {
|
|
688
|
+
column.recompute = true;
|
|
689
|
+
}
|
|
690
|
+
if (
|
|
691
|
+
column.recomputeOnError !== true &&
|
|
692
|
+
producer.recomputeOnError === true
|
|
693
|
+
) {
|
|
694
|
+
column.recomputeOnError = true;
|
|
695
|
+
}
|
|
621
696
|
}
|
|
622
697
|
|
|
623
698
|
return [...columnsById.values()];
|
|
@@ -667,6 +742,8 @@ function columnProducerFromSubstep(
|
|
|
667
742
|
...(substep.dependsOnFields?.length
|
|
668
743
|
? { dependsOnFields: [...substep.dependsOnFields] }
|
|
669
744
|
: {}),
|
|
745
|
+
...(substep.recompute === true ? { recompute: true } : {}),
|
|
746
|
+
...(substep.recomputeOnError === true ? { recomputeOnError: true } : {}),
|
|
670
747
|
...(substep.staleAfterSeconds !== undefined
|
|
671
748
|
? { staleAfterSeconds: substep.staleAfterSeconds }
|
|
672
749
|
: {}),
|
|
@@ -828,7 +905,14 @@ export function compileSheetContract(pipeline: PlayStaticPipeline): {
|
|
|
828
905
|
errors.push('Sheet contract produced an empty column id.');
|
|
829
906
|
return;
|
|
830
907
|
}
|
|
831
|
-
|
|
908
|
+
const existing = columns.find((candidate) => candidate.id === column.id);
|
|
909
|
+
if (existing) {
|
|
910
|
+
if (existing.source === 'input' && column.source === 'datasetColumn') {
|
|
911
|
+
existing.source = 'datasetColumn';
|
|
912
|
+
existing.field = column.field;
|
|
913
|
+
}
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
832
916
|
columns.push({
|
|
833
917
|
...column,
|
|
834
918
|
sqlName: sqlSafePlayColumnName(column.id),
|