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.
@@ -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
- | { action: 'recompute'; reason: 'missing' | 'failed' | 'stale' }
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 { dynamicStaleAfterSeconds: true };
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 && pipelineSubstep.staleAfterSeconds !== undefined
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
- if (columns.some((existing) => existing.id === column.id)) return;
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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.106",
3
+ "version": "0.1.108",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {