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.
@@ -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: resolver as PlayStepProgramStep['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.73',
54
- apiContract: '2026-06-dataset-column-syntax-cutover',
53
+ version: '0.1.76',
54
+ apiContract: '2026-06-dataset-column-cell-stale-hard-cutover',
55
55
  supportPolicy: {
56
- latest: '0.1.73',
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: resolver as PlayStepProgramStep['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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.73",
3
+ "version": "0.1.76",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {