deepline 0.1.82 → 0.1.85

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.
@@ -1,15 +1,55 @@
1
+ import type { AuthoredStaleAfterSeconds, PreviousCell } from './cell-staleness';
2
+
1
3
  export type StepProgramDatasetOptions = {
2
4
  runIf?: (
3
5
  row: Record<string, unknown>,
4
6
  index: number,
5
7
  ) => boolean | Promise<boolean>;
6
- staleAfterSeconds?: number;
8
+ staleAfterSeconds?: AuthoredStaleAfterSeconds;
9
+ };
10
+
11
+ export type StepProgramDatasetColumnRunInput<Value = unknown> = {
12
+ row: Record<string, unknown>;
13
+ ctx: unknown;
14
+ index: number;
15
+ previousCell?: PreviousCell<Value>;
7
16
  };
8
17
 
18
+ export type StepProgramDatasetColumnDefinition<Value = unknown> =
19
+ StepProgramDatasetOptions & {
20
+ run: (
21
+ input: StepProgramDatasetColumnRunInput<Value>,
22
+ ) => Value | Promise<Value>;
23
+ };
24
+
25
+ export type StepProgramDatasetColumnInput<TResolver> =
26
+ | TResolver
27
+ | StepProgramDatasetColumnDefinition;
28
+
29
+ function isStepProgramDatasetColumnDefinition(
30
+ value: unknown,
31
+ ): value is StepProgramDatasetColumnDefinition {
32
+ return (
33
+ value !== null &&
34
+ typeof value === 'object' &&
35
+ typeof (value as { run?: unknown }).run === 'function'
36
+ );
37
+ }
38
+
39
+ function isRecord(value: unknown): value is Record<string, unknown> {
40
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
41
+ }
42
+
43
+ function isPreviousCell(value: unknown): value is PreviousCell {
44
+ return (
45
+ isRecord(value) && Object.prototype.hasOwnProperty.call(value, 'value')
46
+ );
47
+ }
48
+
9
49
  export type StepProgramDatasetStep<TResolver> = {
10
50
  name: string;
11
51
  resolver: TResolver;
12
- staleAfterSeconds?: number;
52
+ staleAfterSeconds?: AuthoredStaleAfterSeconds;
13
53
  };
14
54
 
15
55
  export type StepProgramDatasetProgram<TStep> = {
@@ -75,19 +115,23 @@ export class StepProgramDatasetBuilder<
75
115
 
76
116
  withColumn(
77
117
  name: string,
78
- resolver: TResolver,
118
+ columnInput: StepProgramDatasetColumnInput<TResolver>,
79
119
  options?: StepProgramDatasetOptions,
80
120
  ): this {
81
121
  if (!name.trim()) {
82
122
  throw new Error(this.messages.emptyColumnName);
83
123
  }
124
+ const normalized = this.normalizeColumnInput(columnInput, options);
84
125
  this.program.steps = [
85
126
  ...this.program.steps,
86
127
  {
87
128
  name,
88
- resolver: this.applyDerivationOptions(resolver, options),
89
- ...(options?.staleAfterSeconds !== undefined
90
- ? { staleAfterSeconds: options.staleAfterSeconds }
129
+ resolver: this.applyDerivationOptions(
130
+ normalized.resolver,
131
+ normalized.options,
132
+ ),
133
+ ...(normalized.options?.staleAfterSeconds !== undefined
134
+ ? { staleAfterSeconds: normalized.options.staleAfterSeconds }
91
135
  : {}),
92
136
  } as TStep,
93
137
  ];
@@ -127,4 +171,50 @@ export class StepProgramDatasetBuilder<
127
171
  elseValue: null,
128
172
  } as TResolver;
129
173
  }
174
+
175
+ private normalizeColumnInput(
176
+ columnInput: StepProgramDatasetColumnInput<TResolver>,
177
+ options?: StepProgramDatasetOptions,
178
+ ): { resolver: TResolver; options?: StepProgramDatasetOptions } {
179
+ if (!isStepProgramDatasetColumnDefinition(columnInput)) {
180
+ return { resolver: columnInput, options };
181
+ }
182
+
183
+ const { run, ...definitionOptions } = columnInput;
184
+ const resolver = ((
185
+ row: Record<string, unknown>,
186
+ ctx: unknown,
187
+ third?: unknown,
188
+ fourth?: unknown,
189
+ fifth?: unknown,
190
+ ) => {
191
+ const index =
192
+ typeof third === 'number'
193
+ ? third
194
+ : typeof fourth === 'number'
195
+ ? fourth
196
+ : 0;
197
+ const rowForRun =
198
+ isRecord(third) && typeof fourth === 'number' ? third : row;
199
+ const previousCell = isPreviousCell(fifth)
200
+ ? fifth
201
+ : isPreviousCell(fourth)
202
+ ? fourth
203
+ : undefined;
204
+ return run({
205
+ row: rowForRun,
206
+ ctx,
207
+ index,
208
+ ...(previousCell ? { previousCell } : {}),
209
+ });
210
+ }) as TResolver;
211
+
212
+ return {
213
+ resolver,
214
+ options: {
215
+ ...definitionOptions,
216
+ ...(options ?? {}),
217
+ },
218
+ };
219
+ }
130
220
  }
@@ -18,6 +18,11 @@ export type {
18
18
  } from './email-status';
19
19
 
20
20
  import { buildEmailStatus } from './email-status';
21
+ import {
22
+ JOB_CHANGE_STATUS_VALUES,
23
+ type JobChangeGetterValue,
24
+ type JobChangeStatus,
25
+ } from './extractor-targets';
21
26
  import type {
22
27
  SerializedToolExecuteResult,
23
28
  ToolExecuteResult,
@@ -428,6 +433,80 @@ function normalizePhoneStatus(value: unknown): unknown {
428
433
  return normalized;
429
434
  }
430
435
 
436
+ function normalizeJobChangeStatus(value: unknown): unknown {
437
+ if (typeof value === 'boolean') return value ? 'moved' : 'no_change';
438
+ const normalized = normalizeString(value)
439
+ ?.toLowerCase()
440
+ .replace(/[\s-]+/g, '_');
441
+ if (!normalized) return 'unknown';
442
+ if (['true', 'yes', 'moved', 'changed', 'new_company'].includes(normalized)) {
443
+ return 'moved';
444
+ }
445
+ if (['false', 'no', 'same', 'no_change'].includes(normalized))
446
+ return 'no_change';
447
+ if (['left', 'left_company'].includes(normalized)) return 'left_company';
448
+ if (
449
+ (JOB_CHANGE_STATUS_VALUES as readonly string[]).includes(normalized)
450
+ ) {
451
+ return normalized;
452
+ }
453
+ return 'unknown';
454
+ }
455
+
456
+ function firstExperienceDate(value: unknown): string | null {
457
+ if (!Array.isArray(value)) return null;
458
+ for (const entry of value) {
459
+ if (!isRecord(entry)) continue;
460
+ const date = normalizeString(
461
+ entry.start_date ?? entry.started_at ?? entry.startDate,
462
+ );
463
+ if (date) return date;
464
+ }
465
+ return null;
466
+ }
467
+
468
+ function normalizeJobChange(value: unknown): JobChangeGetterValue {
469
+ const record = isRecord(value) ? value : {};
470
+ const nested = isRecord(record.job_change) ? record.job_change : record;
471
+ const output = isRecord(nested.output) ? nested.output : nested;
472
+ const person = isRecord(output.person) ? output.person : {};
473
+ const status = normalizeJobChangeStatus(
474
+ output.status ??
475
+ output.job_change_status ??
476
+ output.job_changed ??
477
+ output.changed,
478
+ ) as JobChangeStatus;
479
+ const moved = status === 'moved';
480
+
481
+ return {
482
+ status,
483
+ date: moved
484
+ ? normalizeString(
485
+ output.date ??
486
+ output.job_change_date ??
487
+ output.change_date ??
488
+ output.changed_at,
489
+ ) ?? firstExperienceDate(person.experiences)
490
+ : null,
491
+ new_company: moved
492
+ ? normalizeString(
493
+ output.new_company ??
494
+ output.current_company ??
495
+ person.company_name ??
496
+ person.current_company,
497
+ )
498
+ : null,
499
+ new_title: moved
500
+ ? normalizeString(
501
+ output.new_title ??
502
+ output.current_title ??
503
+ person.title ??
504
+ person.headline,
505
+ )
506
+ : null,
507
+ };
508
+ }
509
+
431
510
  function applyExtractorTransforms(
432
511
  value: unknown,
433
512
  descriptor: ToolResultExtractorDescriptor,
@@ -435,6 +514,9 @@ function applyExtractorTransforms(
435
514
  return (descriptor.transforms ?? []).reduce((current, transform) => {
436
515
  if (transform.endsWith('emailStatus')) return normalizeEmailStatus(current);
437
516
  if (transform.endsWith('phoneStatus')) return normalizePhoneStatus(current);
517
+ if (transform === 'jobChange') return normalizeJobChange(current);
518
+ if (transform === 'jobChangeStatus')
519
+ return normalizeJobChangeStatus(current);
438
520
  return current;
439
521
  }, value);
440
522
  }
@@ -63,6 +63,7 @@ export type PlayStaticColumnProducerKind =
63
63
  | 'waterfall'
64
64
  | 'stepProgram'
65
65
  | 'playCall'
66
+ | 'controlFlow'
66
67
  | 'transform';
67
68
 
68
69
  export interface PlayStaticColumnProducer {
@@ -71,6 +72,7 @@ export interface PlayStaticColumnProducer {
71
72
  field: string;
72
73
  toolId?: string;
73
74
  playId?: string;
75
+ dependsOnFields?: string[];
74
76
  staleAfterSeconds?: number;
75
77
  conditional?: boolean;
76
78
  sourceRange?: PlayStaticSourceRange;
@@ -154,10 +156,16 @@ function truncateStaticSubstepsForStorage(
154
156
  ...column,
155
157
  producers: column.producers.map((producer) => ({
156
158
  ...producer,
159
+ dependsOnFields: producer.dependsOnFields
160
+ ? [...producer.dependsOnFields]
161
+ : undefined,
157
162
  sourceRange: cloneStorageSafeSourceRange(producer.sourceRange),
158
163
  steps: producer.steps
159
164
  ? producer.steps.map((stepProducer) => ({
160
165
  ...stepProducer,
166
+ dependsOnFields: stepProducer.dependsOnFields
167
+ ? [...stepProducer.dependsOnFields]
168
+ : undefined,
161
169
  sourceRange: cloneStorageSafeSourceRange(
162
170
  stepProducer.sourceRange,
163
171
  ),
@@ -179,6 +187,13 @@ function truncateStaticSubstepsForStorage(
179
187
  };
180
188
  }
181
189
 
190
+ if (base.type === 'control_flow') {
191
+ return {
192
+ ...base,
193
+ steps: truncateStaticSubstepsForStorage(base.steps, input),
194
+ };
195
+ }
196
+
182
197
  if (base.type !== 'play_call') {
183
198
  return base;
184
199
  }
@@ -259,6 +274,7 @@ export interface PlayStaticSourceRange {
259
274
 
260
275
  type PlayStaticSubstepMetadata = {
261
276
  conditional?: boolean;
277
+ dependsOnFields?: string[];
262
278
  staleAfterSeconds?: number;
263
279
  };
264
280
 
@@ -346,6 +362,16 @@ export type PlayStaticSubstep = PlayStaticSubstepMetadata &
346
362
  callDepth?: number;
347
363
  callPath?: string[];
348
364
  }
365
+ | {
366
+ type: 'control_flow';
367
+ kind: 'conditional' | 'loop';
368
+ field: string;
369
+ steps: PlayStaticSubstep[];
370
+ description?: string;
371
+ sourceRange?: PlayStaticSourceRange;
372
+ callDepth?: number;
373
+ callPath?: string[];
374
+ }
349
375
  | {
350
376
  type: 'run_javascript';
351
377
  alias: string;
@@ -389,7 +415,7 @@ function getRawTopLevelPipelineSubsteps(
389
415
  }
390
416
  const topLevel = [...(pipeline.stages ?? [])];
391
417
  const tableNamespace = pipeline.tableNamespace?.trim();
392
- if (pipeline.csvArg && !topLevel.some((substep) => substep.type === 'csv')) {
418
+ if (pipeline.csvArg && !substepsContainType(topLevel, 'csv')) {
393
419
  topLevel.unshift({
394
420
  type: 'csv',
395
421
  field: 'csv',
@@ -397,10 +423,7 @@ function getRawTopLevelPipelineSubsteps(
397
423
  description: pipeline.csvDescription,
398
424
  });
399
425
  }
400
- if (
401
- tableNamespace &&
402
- !topLevel.some((substep) => substep.type === 'dataset')
403
- ) {
426
+ if (tableNamespace && !substepsContainType(topLevel, 'dataset')) {
404
427
  topLevel.push({
405
428
  type: 'dataset',
406
429
  field: tableNamespace,
@@ -415,6 +438,34 @@ function getRawTopLevelPipelineSubsteps(
415
438
  return [...pipeline.substeps];
416
439
  }
417
440
 
441
+ function substepsContainType(
442
+ substeps: readonly PlayStaticSubstep[],
443
+ type: PlayStaticSubstep['type'],
444
+ ): boolean {
445
+ for (const substep of substeps) {
446
+ if (substep.type === type) {
447
+ return true;
448
+ }
449
+ if (
450
+ (substep.type === 'dataset' ||
451
+ substep.type === 'step_suite' ||
452
+ substep.type === 'control_flow') &&
453
+ substep.steps?.length &&
454
+ substepsContainType(substep.steps, type)
455
+ ) {
456
+ return true;
457
+ }
458
+ if (
459
+ substep.type === 'play_call' &&
460
+ substep.pipeline &&
461
+ substepsContainType(getCompiledPipelineSubsteps(substep.pipeline), type)
462
+ ) {
463
+ return true;
464
+ }
465
+ }
466
+ return false;
467
+ }
468
+
418
469
  export function flattenStaticSubsteps(
419
470
  substeps: PlayStaticSubstep[],
420
471
  ): PlayStaticSubstep[] {
@@ -430,6 +481,10 @@ export function flattenStaticSubsteps(
430
481
  flattened.push(...flattenStaticSubsteps(substep.steps));
431
482
  continue;
432
483
  }
484
+ if (substep.type === 'control_flow') {
485
+ flattened.push(...flattenStaticSubsteps(substep.steps));
486
+ continue;
487
+ }
433
488
  if (substep.type === 'play_call' && substep.pipeline) {
434
489
  const nestedSubsteps = getCompiledPipelineSubsteps(substep.pipeline);
435
490
  if (nestedSubsteps.length > 0) {
@@ -452,11 +507,19 @@ export function compileStaticGraph(
452
507
  ): PlayCompiledStaticGraph {
453
508
  const rawTopLevel = getRawTopLevelPipelineSubsteps(pipeline);
454
509
  const datasets: PlayCompiledStaticGraph['datasets'] = [];
455
- const topLevel = rawTopLevel.map((substep) => {
456
- if (substep.type !== 'dataset') {
457
- return substep;
458
- }
459
- const columns = compileDatasetColumns(substep, pipeline?.substeps ?? []);
510
+ const topLevel = rawTopLevel.map((substep) =>
511
+ compileStaticGraphSubstep(substep, pipeline?.substeps ?? [], datasets),
512
+ );
513
+ return { topLevel, datasets };
514
+ }
515
+
516
+ function compileStaticGraphSubstep(
517
+ substep: PlayStaticSubstep,
518
+ pipelineSubsteps: PlayStaticSubstep[],
519
+ datasets: PlayCompiledStaticGraph['datasets'],
520
+ ): PlayStaticSubstep {
521
+ if (substep.type === 'dataset') {
522
+ const columns = compileDatasetColumns(substep, pipelineSubsteps);
460
523
  const tableNamespace = (substep.tableNamespace ?? substep.field).trim();
461
524
  if (tableNamespace) {
462
525
  datasets.push({ tableNamespace, columns });
@@ -464,9 +527,22 @@ export function compileStaticGraph(
464
527
  return {
465
528
  ...substep,
466
529
  columns,
530
+ steps: substep.steps?.map((nested) =>
531
+ compileStaticGraphSubstep(nested, pipelineSubsteps, datasets),
532
+ ),
467
533
  } satisfies PlayStaticSubstep;
468
- });
469
- return { topLevel, datasets };
534
+ }
535
+
536
+ if (substep.type === 'step_suite' || substep.type === 'control_flow') {
537
+ return {
538
+ ...substep,
539
+ steps: substep.steps.map((nested) =>
540
+ compileStaticGraphSubstep(nested, pipelineSubsteps, datasets),
541
+ ),
542
+ } satisfies PlayStaticSubstep;
543
+ }
544
+
545
+ return substep;
470
546
  }
471
547
 
472
548
  function compileDatasetColumns(
@@ -562,7 +638,7 @@ function columnProducerFromSubstep(
562
638
  field: string,
563
639
  ): PlayStaticColumnProducer {
564
640
  const steps =
565
- substep.type === 'step_suite'
641
+ substep.type === 'step_suite' || substep.type === 'control_flow'
566
642
  ? substep.steps
567
643
  .map((step) => {
568
644
  const stepField = fieldForColumnProducer(step) ?? field;
@@ -577,15 +653,20 @@ function columnProducerFromSubstep(
577
653
  ? 'waterfall'
578
654
  : substep.type === 'step_suite'
579
655
  ? 'stepProgram'
580
- : substep.type === 'play_call'
581
- ? 'playCall'
582
- : 'transform';
656
+ : substep.type === 'control_flow'
657
+ ? 'controlFlow'
658
+ : substep.type === 'play_call'
659
+ ? 'playCall'
660
+ : 'transform';
583
661
  return {
584
662
  id: producerId(substep, field),
585
663
  kind,
586
664
  field,
587
665
  ...(substep.type === 'tool' ? { toolId: substep.toolId } : {}),
588
666
  ...(substep.type === 'play_call' ? { playId: substep.playId } : {}),
667
+ ...(substep.dependsOnFields?.length
668
+ ? { dependsOnFields: [...substep.dependsOnFields] }
669
+ : {}),
589
670
  ...(substep.staleAfterSeconds !== undefined
590
671
  ? { staleAfterSeconds: substep.staleAfterSeconds }
591
672
  : {}),
@@ -603,11 +684,21 @@ function producerId(substep: PlayStaticSubstep, field: string): string {
603
684
  }
604
685
  if (substep.type === 'play_call') return `${field}:play:${substep.playId}`;
605
686
  if (substep.type === 'step_suite') return `${field}:steps`;
687
+ if (substep.type === 'control_flow')
688
+ return `${field}:control:${substep.kind}:${sourceRangeKey(substep.sourceRange)}`;
606
689
  if (substep.type === 'run_javascript')
607
690
  return `${field}:transform:${substep.alias}`;
608
691
  return `${field}:transform`;
609
692
  }
610
693
 
694
+ function sourceRangeKey(
695
+ sourceRange: PlayStaticSourceRange | undefined,
696
+ ): string {
697
+ return sourceRange
698
+ ? `${sourceRange.startLine}:${sourceRange.startColumn}:${sourceRange.endLine}:${sourceRange.endColumn}`
699
+ : 'unknown';
700
+ }
701
+
611
702
  export function resolveSheetContractForTableNamespace(
612
703
  pipeline: PlayStaticPipeline | null | undefined,
613
704
  tableNamespace: string | null | undefined,
@@ -685,8 +776,7 @@ export function resolveStaticDatasetColumnsForTableNamespace(
685
776
  const compiled = compileStaticGraph(currentPipeline);
686
777
  const matchingDataset = compiled.datasets.find(
687
778
  (dataset) =>
688
- normalizeTableNamespace(dataset.tableNamespace) ===
689
- normalizedNamespace,
779
+ normalizeTableNamespace(dataset.tableNamespace) === normalizedNamespace,
690
780
  );
691
781
  if (matchingDataset) {
692
782
  return matchingDataset.columns;
@@ -762,23 +852,23 @@ export function compileSheetContract(pipeline: PlayStaticPipeline): {
762
852
  addColumn({ id: field, source: 'datasetColumn', field });
763
853
  }
764
854
 
765
- for (const substep of pipeline.substeps) {
855
+ const processSubstep = (substep: PlayStaticSubstep) => {
766
856
  if (substep.type === 'waterfall') {
767
857
  if (!substep.id) {
768
858
  if (substep.tool) {
769
- continue;
859
+ return;
770
860
  }
771
861
  errors.push(
772
862
  `Sheet contract cannot compile waterfall field "${substep.field}" without a literal waterfall id.`,
773
863
  );
774
- continue;
864
+ return;
775
865
  }
776
866
  if (!substep.steps?.length) {
777
867
  errors.push(
778
868
  `Sheet contract cannot compile waterfall "${substep.id}" because its steps are not statically known. ` +
779
869
  'Use an inline array, a local const array, or a local no-arg function that returns an array of step("id", "tool", ...) calls.',
780
870
  );
781
- continue;
871
+ return;
782
872
  }
783
873
  for (const step of substep.steps) {
784
874
  addColumn({
@@ -794,7 +884,7 @@ export function compileSheetContract(pipeline: PlayStaticPipeline): {
794
884
  toolId: step.toolId,
795
885
  });
796
886
  }
797
- continue;
887
+ return;
798
888
  }
799
889
 
800
890
  if (substep.type === 'step_suite') {
@@ -825,10 +915,25 @@ export function compileSheetContract(pipeline: PlayStaticPipeline): {
825
915
  if (step.type === 'step_suite') {
826
916
  addStepSuiteColumns(step, rootField);
827
917
  }
918
+ if (step.type === 'control_flow') {
919
+ for (const nested of step.steps) {
920
+ if (nested.type === 'step_suite') {
921
+ addStepSuiteColumns(nested, rootField);
922
+ } else {
923
+ processSubstep(nested);
924
+ }
925
+ }
926
+ }
828
927
  }
829
928
  };
830
929
  addStepSuiteColumns(substep, substep.field);
831
- continue;
930
+ return;
931
+ }
932
+ if (substep.type === 'control_flow') {
933
+ for (const nested of substep.steps) {
934
+ processSubstep(nested);
935
+ }
936
+ return;
832
937
  }
833
938
  if (substep.type === 'play_call') {
834
939
  if (substep.cycleDetected || substep.resolutionError) {
@@ -864,6 +969,10 @@ export function compileSheetContract(pipeline: PlayStaticPipeline): {
864
969
  }
865
970
  }
866
971
  }
972
+ };
973
+
974
+ for (const substep of pipeline.substeps) {
975
+ processSubstep(substep);
867
976
  }
868
977
 
869
978
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.82",
3
+ "version": "0.1.85",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {