deepline 0.1.73 → 0.1.76
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/README.md +7 -6
- package/dist/cli/index.js +1297 -350
- package/dist/cli/index.mjs +1299 -352
- package/dist/index.d.mts +52 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.js +16 -5
- package/dist/index.mjs +16 -5
- package/dist/repo/apps/play-runner-workers/src/entry.ts +141 -63
- package/dist/repo/sdk/src/client.ts +8 -0
- package/dist/repo/sdk/src/play.ts +59 -9
- package/dist/repo/sdk/src/plays/harness-stub.ts +1 -0
- package/dist/repo/sdk/src/release.ts +3 -3
- package/dist/repo/sdk/src/worker-play-entry.ts +39 -3
- package/dist/repo/shared_libs/play-runtime/cell-staleness.ts +88 -0
- package/dist/repo/shared_libs/play-runtime/step-program-dataset-builder.ts +130 -0
- package/dist/repo/shared_libs/plays/row-identity.ts +0 -40
- package/package.json +1 -1
|
@@ -68,6 +68,7 @@ import type {
|
|
|
68
68
|
} from './types.js';
|
|
69
69
|
import type { PlayStagedFileRef } from './plays/local-file-discovery.js';
|
|
70
70
|
import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest.js';
|
|
71
|
+
import type { EnrichCompiledConfig } from './cli/enrich-play-compiler.js';
|
|
71
72
|
|
|
72
73
|
const TERMINAL_PLAY_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
73
74
|
const INCLUDE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
|
|
@@ -1019,6 +1020,13 @@ export class DeeplineClient {
|
|
|
1019
1020
|
return this.http.post('/api/v2/plays/check', input);
|
|
1020
1021
|
}
|
|
1021
1022
|
|
|
1023
|
+
async compileEnrichPlan(input: {
|
|
1024
|
+
plan_args?: string[];
|
|
1025
|
+
config?: unknown;
|
|
1026
|
+
}): Promise<{ config: EnrichCompiledConfig }> {
|
|
1027
|
+
return this.http.post('/api/v2/enrich/compile', input);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1022
1030
|
async startPlayRunFromBundle(input: {
|
|
1023
1031
|
name: string;
|
|
1024
1032
|
sourceCode: string;
|
|
@@ -222,6 +222,11 @@ export type ConditionalStepResolver<Row, Value, Else = null> = {
|
|
|
222
222
|
): ConditionalStepResolver<Row, Value, ValueElse>;
|
|
223
223
|
};
|
|
224
224
|
|
|
225
|
+
export type StepOptions<Row> = {
|
|
226
|
+
readonly runIf?: (row: Row, index: number) => boolean | Promise<boolean>;
|
|
227
|
+
readonly staleAfterSeconds?: number;
|
|
228
|
+
};
|
|
229
|
+
|
|
225
230
|
export type StepProgram<Input, Output, Return = Output> = {
|
|
226
231
|
readonly kind: 'steps';
|
|
227
232
|
readonly steps: readonly PlayStepProgramStep[];
|
|
@@ -234,6 +239,11 @@ export type StepProgram<Input, Output, Return = Output> = {
|
|
|
234
239
|
| ConditionalStepResolver<Output, Value>
|
|
235
240
|
| StepProgramResolver<Output, Value>,
|
|
236
241
|
): StepProgram<Input, Output & Record<Name, Value>, Return>;
|
|
242
|
+
step<Name extends string, Value>(
|
|
243
|
+
name: Name,
|
|
244
|
+
resolver: StepResolver<Output, Value> | StepProgramResolver<Output, Value>,
|
|
245
|
+
options: StepOptions<Output>,
|
|
246
|
+
): StepProgram<Input, Output & Record<Name, Value | null>, Return>;
|
|
237
247
|
return<Value>(
|
|
238
248
|
resolver: StepResolver<Output, Value>,
|
|
239
249
|
): StepProgram<Input, Output, Value>;
|
|
@@ -248,6 +258,7 @@ export type StepProgramResolver<Input, Return> = {
|
|
|
248
258
|
|
|
249
259
|
export type PlayStepProgramStep = {
|
|
250
260
|
readonly name: string;
|
|
261
|
+
readonly staleAfterSeconds?: number;
|
|
251
262
|
readonly resolver:
|
|
252
263
|
| StepResolver<Record<string, unknown>, unknown>
|
|
253
264
|
| ConditionalStepResolver<Record<string, unknown>, unknown>
|
|
@@ -259,6 +270,9 @@ export type ColumnResolver<Row, Value> =
|
|
|
259
270
|
| ConditionalStepResolver<Row, Value>
|
|
260
271
|
| StepProgramResolver<Row, Value>;
|
|
261
272
|
|
|
273
|
+
export type StepProgramOutput<TProgram> =
|
|
274
|
+
TProgram extends StepProgram<any, infer Output, any> ? Output : never;
|
|
275
|
+
|
|
262
276
|
export type DatasetBuilder<
|
|
263
277
|
InputRow extends object,
|
|
264
278
|
OutputRow extends object,
|
|
@@ -280,6 +294,16 @@ export type DatasetBuilder<
|
|
|
280
294
|
name: Name,
|
|
281
295
|
resolver: ColumnResolver<OutputRow, Value>,
|
|
282
296
|
): DatasetBuilder<InputRow, OutputRow & Record<Name, Value>>;
|
|
297
|
+
withColumn<Name extends string, Value>(
|
|
298
|
+
name: Name,
|
|
299
|
+
resolver:
|
|
300
|
+
| StepResolver<OutputRow, Value>
|
|
301
|
+
| StepProgramResolver<OutputRow, Value>,
|
|
302
|
+
options: StepOptions<OutputRow>,
|
|
303
|
+
): DatasetBuilder<InputRow, OutputRow & Record<Name, Value | null>>;
|
|
304
|
+
withColumns<Program extends StepProgram<OutputRow, object, unknown>>(
|
|
305
|
+
program: Program,
|
|
306
|
+
): DatasetBuilder<InputRow, StepProgramOutput<Program>>;
|
|
283
307
|
/** @deprecated Dataset `.step(...)` was replaced by `.withColumn(...)`. */
|
|
284
308
|
step<Name extends string, Value>(
|
|
285
309
|
name: Name,
|
|
@@ -296,7 +320,6 @@ export type DatasetBuilder<
|
|
|
296
320
|
*/
|
|
297
321
|
run(options?: {
|
|
298
322
|
description?: string;
|
|
299
|
-
staleAfterSeconds?: number;
|
|
300
323
|
key?:
|
|
301
324
|
| (keyof InputRow & string)
|
|
302
325
|
| readonly (keyof InputRow & string)[]
|
|
@@ -307,7 +330,6 @@ export type DatasetBuilder<
|
|
|
307
330
|
}): Promise<PlayDataset<OutputRow>>;
|
|
308
331
|
};
|
|
309
332
|
|
|
310
|
-
|
|
311
333
|
export type CsvRenameMap = Record<string, string | readonly string[]>;
|
|
312
334
|
|
|
313
335
|
/**
|
|
@@ -478,10 +500,7 @@ export interface DeeplinePlayRuntimeContext {
|
|
|
478
500
|
/**
|
|
479
501
|
* @deprecated `ctx.map(...)` was replaced by `ctx.dataset(...)`.
|
|
480
502
|
*/
|
|
481
|
-
map<TItem extends object>(
|
|
482
|
-
key: string,
|
|
483
|
-
items: PlayDatasetInput<TItem>,
|
|
484
|
-
): never;
|
|
503
|
+
map<TItem extends object>(key: string, items: PlayDatasetInput<TItem>): never;
|
|
485
504
|
|
|
486
505
|
/** Tool execution namespace. */
|
|
487
506
|
tools: {
|
|
@@ -863,22 +882,43 @@ class DeeplineStepProgram<Input, Output, ReturnValue> implements StepProgram<
|
|
|
863
882
|
| StepResolver<Output, Value>
|
|
864
883
|
| ConditionalStepResolver<Output, Value>
|
|
865
884
|
| StepProgramResolver<Output, Value>,
|
|
866
|
-
): StepProgram<Input, Output & Record<Name, Value>, ReturnValue
|
|
885
|
+
): StepProgram<Input, Output & Record<Name, Value>, ReturnValue>;
|
|
886
|
+
step<Name extends string, Value>(
|
|
887
|
+
name: Name,
|
|
888
|
+
resolver: StepResolver<Output, Value> | StepProgramResolver<Output, Value>,
|
|
889
|
+
options: StepOptions<Output>,
|
|
890
|
+
): StepProgram<Input, Output & Record<Name, Value | null>, ReturnValue>;
|
|
891
|
+
step<Name extends string, Value>(
|
|
892
|
+
name: Name,
|
|
893
|
+
resolver:
|
|
894
|
+
| StepResolver<Output, Value>
|
|
895
|
+
| ConditionalStepResolver<Output, Value>
|
|
896
|
+
| StepProgramResolver<Output, Value>,
|
|
897
|
+
options?: StepOptions<Output>,
|
|
898
|
+
): StepProgram<Input, Output & Record<Name, Value | null>, ReturnValue> {
|
|
867
899
|
if (!name.trim()) {
|
|
868
900
|
throw new Error(
|
|
869
901
|
'steps().step(name, ...) requires a non-empty step name.',
|
|
870
902
|
);
|
|
871
903
|
}
|
|
904
|
+
const stepResolver =
|
|
905
|
+
options?.runIf && !isConditionalStepResolver(resolver)
|
|
906
|
+
? new DeeplineConditionalStepResolver(
|
|
907
|
+
options.runIf,
|
|
908
|
+
resolver as StepResolver<Output, Value>,
|
|
909
|
+
null,
|
|
910
|
+
)
|
|
911
|
+
: resolver;
|
|
872
912
|
return new DeeplineStepProgram(
|
|
873
913
|
[
|
|
874
914
|
...this.steps,
|
|
875
915
|
{
|
|
876
916
|
name,
|
|
877
|
-
resolver:
|
|
917
|
+
resolver: stepResolver as PlayStepProgramStep['resolver'],
|
|
878
918
|
},
|
|
879
919
|
],
|
|
880
920
|
this.returnResolver as StepResolver<
|
|
881
|
-
Output & Record<Name, Value>,
|
|
921
|
+
Output & Record<Name, Value | null>,
|
|
882
922
|
ReturnValue
|
|
883
923
|
>,
|
|
884
924
|
);
|
|
@@ -891,6 +931,16 @@ class DeeplineStepProgram<Input, Output, ReturnValue> implements StepProgram<
|
|
|
891
931
|
}
|
|
892
932
|
}
|
|
893
933
|
|
|
934
|
+
function isConditionalStepResolver(
|
|
935
|
+
value: unknown,
|
|
936
|
+
): value is ConditionalStepResolver<unknown, unknown> {
|
|
937
|
+
return (
|
|
938
|
+
value !== null &&
|
|
939
|
+
typeof value === 'object' &&
|
|
940
|
+
(value as { kind?: unknown }).kind === 'conditional'
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
894
944
|
export function steps<TInput>(): StepProgram<TInput, TInput, TInput> {
|
|
895
945
|
return new DeeplineStepProgram<TInput, TInput, TInput>([]);
|
|
896
946
|
}
|
|
@@ -191,6 +191,7 @@ export async function harnessStartSheetDataset(input: {
|
|
|
191
191
|
runId: string;
|
|
192
192
|
inputOffset?: number;
|
|
193
193
|
userEmail?: string | null;
|
|
194
|
+
cellPolicies?: Record<string, { staleAfterSeconds?: number }>;
|
|
194
195
|
}): Promise<{
|
|
195
196
|
inserted: number;
|
|
196
197
|
skipped: number;
|
|
@@ -50,10 +50,10 @@ export type SdkRelease = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export const SDK_RELEASE = {
|
|
53
|
-
version: '0.1.
|
|
54
|
-
apiContract: '2026-06-dataset-column-
|
|
53
|
+
version: '0.1.76',
|
|
54
|
+
apiContract: '2026-06-dataset-column-cell-stale-hard-cutover',
|
|
55
55
|
supportPolicy: {
|
|
56
|
-
latest: '0.1.
|
|
56
|
+
latest: '0.1.76',
|
|
57
57
|
minimumSupported: '0.1.53',
|
|
58
58
|
deprecatedBelow: '0.1.53',
|
|
59
59
|
},
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
PlayStepProgramStep,
|
|
20
20
|
StepProgram,
|
|
21
21
|
StepProgramResolver,
|
|
22
|
+
StepOptions,
|
|
22
23
|
StepResolver,
|
|
23
24
|
} from './play.js';
|
|
24
25
|
|
|
@@ -43,6 +44,7 @@ export type {
|
|
|
43
44
|
PrebuiltPlayRef,
|
|
44
45
|
StepProgram,
|
|
45
46
|
StepProgramResolver,
|
|
47
|
+
StepOptions,
|
|
46
48
|
StepResolver,
|
|
47
49
|
ToolExecuteResult,
|
|
48
50
|
} from './play.js';
|
|
@@ -95,20 +97,44 @@ class WorkerStepProgram<Input, Output, ReturnValue> implements StepProgram<
|
|
|
95
97
|
| StepResolver<Output, Value>
|
|
96
98
|
| ConditionalStepResolver<Output, Value>
|
|
97
99
|
| StepProgramResolver<Output, Value>,
|
|
98
|
-
): StepProgram<Input, Output & Record<Name, Value>, ReturnValue
|
|
100
|
+
): StepProgram<Input, Output & Record<Name, Value>, ReturnValue>;
|
|
101
|
+
step<Name extends string, Value>(
|
|
102
|
+
name: Name,
|
|
103
|
+
resolver: StepResolver<Output, Value> | StepProgramResolver<Output, Value>,
|
|
104
|
+
options: StepOptions<Output>,
|
|
105
|
+
): StepProgram<Input, Output & Record<Name, Value | null>, ReturnValue>;
|
|
106
|
+
step<Name extends string, Value>(
|
|
107
|
+
name: Name,
|
|
108
|
+
resolver:
|
|
109
|
+
| StepResolver<Output, Value>
|
|
110
|
+
| ConditionalStepResolver<Output, Value>
|
|
111
|
+
| StepProgramResolver<Output, Value>,
|
|
112
|
+
options?: StepOptions<Output>,
|
|
113
|
+
): StepProgram<Input, Output & Record<Name, Value | null>, ReturnValue> {
|
|
99
114
|
if (!name.trim()) {
|
|
100
115
|
throw new Error('Step name required.');
|
|
101
116
|
}
|
|
117
|
+
const stepResolver =
|
|
118
|
+
options?.runIf && !isConditionalStepResolver(resolver)
|
|
119
|
+
? new WorkerConditionalStepResolver(
|
|
120
|
+
options.runIf,
|
|
121
|
+
resolver as StepResolver<Output, Value>,
|
|
122
|
+
null,
|
|
123
|
+
)
|
|
124
|
+
: resolver;
|
|
102
125
|
return new WorkerStepProgram(
|
|
103
126
|
[
|
|
104
127
|
...this.steps,
|
|
105
128
|
{
|
|
106
129
|
name,
|
|
107
|
-
resolver:
|
|
130
|
+
resolver: stepResolver as PlayStepProgramStep['resolver'],
|
|
131
|
+
...(options?.staleAfterSeconds !== undefined
|
|
132
|
+
? { staleAfterSeconds: options.staleAfterSeconds }
|
|
133
|
+
: {}),
|
|
108
134
|
},
|
|
109
135
|
],
|
|
110
136
|
this.returnResolver as StepResolver<
|
|
111
|
-
Output & Record<Name, Value>,
|
|
137
|
+
Output & Record<Name, Value | null>,
|
|
112
138
|
ReturnValue
|
|
113
139
|
>,
|
|
114
140
|
);
|
|
@@ -121,6 +147,16 @@ class WorkerStepProgram<Input, Output, ReturnValue> implements StepProgram<
|
|
|
121
147
|
}
|
|
122
148
|
}
|
|
123
149
|
|
|
150
|
+
function isConditionalStepResolver(
|
|
151
|
+
value: unknown,
|
|
152
|
+
): value is ConditionalStepResolver<unknown, unknown> {
|
|
153
|
+
return (
|
|
154
|
+
value !== null &&
|
|
155
|
+
typeof value === 'object' &&
|
|
156
|
+
(value as { kind?: unknown }).kind === 'conditional'
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
124
160
|
export function steps<TInput>(): StepProgram<TInput, TInput, TInput> {
|
|
125
161
|
return new WorkerStepProgram<TInput, TInput, TInput>([]);
|
|
126
162
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type CellStalenessPolicy = {
|
|
2
|
+
staleAfterSeconds?: number;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export type CellStalenessMeta = {
|
|
6
|
+
status?: string | null;
|
|
7
|
+
completedAt?: number | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type CellStalenessDecision =
|
|
11
|
+
| { action: 'recompute'; reason: 'missing' | 'failed' | 'stale' }
|
|
12
|
+
| { action: 'reuse'; reason: 'fresh' | 'no_policy' };
|
|
13
|
+
|
|
14
|
+
export type CellStalenessPolicyByField = Record<string, CellStalenessPolicy>;
|
|
15
|
+
|
|
16
|
+
export const DEEPLINE_CELL_META_FIELD = '__deeplineCellMeta';
|
|
17
|
+
|
|
18
|
+
export function validateStaleAfterSeconds(
|
|
19
|
+
staleAfterSeconds: number | undefined,
|
|
20
|
+
label = 'staleAfterSeconds',
|
|
21
|
+
): void {
|
|
22
|
+
if (staleAfterSeconds === undefined) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (
|
|
26
|
+
!Number.isFinite(staleAfterSeconds) ||
|
|
27
|
+
!Number.isInteger(staleAfterSeconds) ||
|
|
28
|
+
staleAfterSeconds <= 0
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(`${label} must be a positive whole number of seconds.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function normalizeCellStalenessPolicy(
|
|
35
|
+
policy: CellStalenessPolicy | undefined,
|
|
36
|
+
): CellStalenessPolicy {
|
|
37
|
+
validateStaleAfterSeconds(policy?.staleAfterSeconds);
|
|
38
|
+
return policy?.staleAfterSeconds === undefined
|
|
39
|
+
? {}
|
|
40
|
+
: { staleAfterSeconds: policy.staleAfterSeconds };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function shouldRecomputeCell(input: {
|
|
44
|
+
hasValue: boolean;
|
|
45
|
+
meta?: CellStalenessMeta | null;
|
|
46
|
+
policy?: CellStalenessPolicy;
|
|
47
|
+
nowMs?: number;
|
|
48
|
+
}): CellStalenessDecision {
|
|
49
|
+
if (!input.hasValue) {
|
|
50
|
+
return { action: 'recompute', reason: 'missing' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const status = String(input.meta?.status ?? '').trim();
|
|
54
|
+
if (status === 'failed') {
|
|
55
|
+
return { action: 'recompute', reason: 'failed' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const staleAfterSeconds = input.policy?.staleAfterSeconds;
|
|
59
|
+
validateStaleAfterSeconds(staleAfterSeconds);
|
|
60
|
+
if (staleAfterSeconds === undefined) {
|
|
61
|
+
return { action: 'reuse', reason: 'no_policy' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const completedAt =
|
|
65
|
+
typeof input.meta?.completedAt === 'number' &&
|
|
66
|
+
Number.isFinite(input.meta.completedAt)
|
|
67
|
+
? input.meta.completedAt
|
|
68
|
+
: null;
|
|
69
|
+
if (completedAt === null) {
|
|
70
|
+
return { action: 'recompute', reason: 'missing' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ageMs = Math.max(0, (input.nowMs ?? Date.now()) - completedAt);
|
|
74
|
+
return ageMs > staleAfterSeconds * 1000
|
|
75
|
+
? { action: 'recompute', reason: 'stale' }
|
|
76
|
+
: { action: 'reuse', reason: 'fresh' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function cellPolicyFields(
|
|
80
|
+
policies: CellStalenessPolicyByField | undefined,
|
|
81
|
+
): string[] {
|
|
82
|
+
if (!policies) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
return Object.entries(policies)
|
|
86
|
+
.filter(([, policy]) => policy.staleAfterSeconds !== undefined)
|
|
87
|
+
.map(([field]) => field);
|
|
88
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export type StepProgramDatasetOptions = {
|
|
2
|
+
runIf?: (
|
|
3
|
+
row: Record<string, unknown>,
|
|
4
|
+
index: number,
|
|
5
|
+
) => boolean | Promise<boolean>;
|
|
6
|
+
staleAfterSeconds?: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type StepProgramDatasetStep<TResolver> = {
|
|
10
|
+
name: string;
|
|
11
|
+
resolver: TResolver;
|
|
12
|
+
staleAfterSeconds?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type StepProgramDatasetProgram<TStep> = {
|
|
16
|
+
kind: 'steps';
|
|
17
|
+
steps: readonly TStep[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type StepProgramDatasetConditionalResolver<TResolver> = {
|
|
21
|
+
kind: 'conditional';
|
|
22
|
+
when: (
|
|
23
|
+
row: Record<string, unknown>,
|
|
24
|
+
index: number,
|
|
25
|
+
) => boolean | Promise<boolean>;
|
|
26
|
+
run: TResolver;
|
|
27
|
+
elseValue?: unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function isStepProgramDatasetProgram<TStep>(
|
|
31
|
+
value: unknown,
|
|
32
|
+
): value is StepProgramDatasetProgram<TStep> {
|
|
33
|
+
return (
|
|
34
|
+
value !== null &&
|
|
35
|
+
typeof value === 'object' &&
|
|
36
|
+
(value as { kind?: unknown }).kind === 'steps' &&
|
|
37
|
+
Array.isArray((value as { steps?: unknown }).steps)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isStepProgramDatasetConditionalResolver<TResolver>(
|
|
42
|
+
value: unknown,
|
|
43
|
+
): value is StepProgramDatasetConditionalResolver<TResolver> {
|
|
44
|
+
return (
|
|
45
|
+
value !== null &&
|
|
46
|
+
typeof value === 'object' &&
|
|
47
|
+
(value as { kind?: unknown }).kind === 'conditional' &&
|
|
48
|
+
typeof (value as { when?: unknown }).when === 'function' &&
|
|
49
|
+
typeof (value as { run?: unknown }).run === 'function'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class StepProgramDatasetBuilder<
|
|
54
|
+
TStep extends StepProgramDatasetStep<TResolver>,
|
|
55
|
+
TResolver,
|
|
56
|
+
TRunOptions,
|
|
57
|
+
TResult,
|
|
58
|
+
> {
|
|
59
|
+
private readonly program: StepProgramDatasetProgram<TStep> = {
|
|
60
|
+
kind: 'steps',
|
|
61
|
+
steps: [],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
constructor(
|
|
65
|
+
private readonly runProgram: (
|
|
66
|
+
program: StepProgramDatasetProgram<TStep>,
|
|
67
|
+
options?: TRunOptions,
|
|
68
|
+
) => TResult,
|
|
69
|
+
private readonly messages: {
|
|
70
|
+
emptyColumnName: string;
|
|
71
|
+
invalidColumnsProgram: string;
|
|
72
|
+
legacyStep: string;
|
|
73
|
+
},
|
|
74
|
+
) {}
|
|
75
|
+
|
|
76
|
+
withColumn(
|
|
77
|
+
name: string,
|
|
78
|
+
resolver: TResolver,
|
|
79
|
+
options?: StepProgramDatasetOptions,
|
|
80
|
+
): this {
|
|
81
|
+
if (!name.trim()) {
|
|
82
|
+
throw new Error(this.messages.emptyColumnName);
|
|
83
|
+
}
|
|
84
|
+
this.program.steps = [
|
|
85
|
+
...this.program.steps,
|
|
86
|
+
{
|
|
87
|
+
name,
|
|
88
|
+
resolver: this.applyDerivationOptions(resolver, options),
|
|
89
|
+
...(options?.staleAfterSeconds !== undefined
|
|
90
|
+
? { staleAfterSeconds: options.staleAfterSeconds }
|
|
91
|
+
: {}),
|
|
92
|
+
} as TStep,
|
|
93
|
+
];
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
withColumns(program: StepProgramDatasetProgram<TStep>): this {
|
|
98
|
+
if (!isStepProgramDatasetProgram<TStep>(program)) {
|
|
99
|
+
throw new Error(this.messages.invalidColumnsProgram);
|
|
100
|
+
}
|
|
101
|
+
this.program.steps = [...this.program.steps, ...program.steps];
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
step(): never {
|
|
106
|
+
throw new Error(this.messages.legacyStep);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
run(options?: TRunOptions): TResult {
|
|
110
|
+
return this.runProgram(this.program, options);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private applyDerivationOptions(
|
|
114
|
+
resolver: TResolver,
|
|
115
|
+
options?: StepProgramDatasetOptions,
|
|
116
|
+
): TResolver {
|
|
117
|
+
if (
|
|
118
|
+
!options?.runIf ||
|
|
119
|
+
isStepProgramDatasetConditionalResolver<TResolver>(resolver)
|
|
120
|
+
) {
|
|
121
|
+
return resolver;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
kind: 'conditional',
|
|
125
|
+
when: options.runIf,
|
|
126
|
+
run: resolver,
|
|
127
|
+
elseValue: null,
|
|
128
|
+
} as TResolver;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -192,46 +192,6 @@ export function normalizeTableNamespace(value: string): string {
|
|
|
192
192
|
);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
export function resolveStaleMapTableNamespace(
|
|
196
|
-
mapKey: string,
|
|
197
|
-
staleAfterSeconds?: number | null,
|
|
198
|
-
nowMs: number = Date.now(),
|
|
199
|
-
): string {
|
|
200
|
-
const normalizedMapKey = normalizeTableNamespace(mapKey);
|
|
201
|
-
if (staleAfterSeconds === undefined || staleAfterSeconds === null) {
|
|
202
|
-
return normalizedMapKey;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
!Number.isFinite(staleAfterSeconds) ||
|
|
207
|
-
!Number.isInteger(staleAfterSeconds) ||
|
|
208
|
-
staleAfterSeconds <= 0
|
|
209
|
-
) {
|
|
210
|
-
throw new Error(
|
|
211
|
-
'ctx.dataset() staleAfterSeconds must be a positive whole number of seconds.',
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const bucket = Math.floor(nowMs / (staleAfterSeconds * 1000));
|
|
216
|
-
const stalePartitionKey = `stale_${staleAfterSeconds}_${bucket}`;
|
|
217
|
-
const candidate = `${normalizedMapKey}_${stalePartitionKey}`;
|
|
218
|
-
if (candidate.length <= MAP_KEY_NAMESPACE_MAX_LENGTH) {
|
|
219
|
-
return candidate;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const digest = sha256Hex(`${normalizedMapKey}\n${stalePartitionKey}`).slice(
|
|
223
|
-
0,
|
|
224
|
-
12,
|
|
225
|
-
);
|
|
226
|
-
const prefixLength = Math.max(
|
|
227
|
-
1,
|
|
228
|
-
MAP_KEY_NAMESPACE_MAX_LENGTH - digest.length - 1,
|
|
229
|
-
);
|
|
230
|
-
const prefix =
|
|
231
|
-
normalizedMapKey.slice(0, prefixLength).replace(/_+$/g, '') || 'map';
|
|
232
|
-
return `${prefix}_${digest}`;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
195
|
export function validatePlaySheetTableName(
|
|
236
196
|
playName: string,
|
|
237
197
|
tableNamespace: string,
|