deepline 0.1.47 → 0.1.48

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.
@@ -48,6 +48,7 @@ export type WorkerRuntimeReceiptAction =
48
48
  type RuntimeReceiptContext = {
49
49
  baseUrl: string;
50
50
  executorToken: string;
51
+ orgId?: string | null;
51
52
  playName: string;
52
53
  runId: string;
53
54
  key: string;
@@ -59,11 +60,11 @@ type RuntimeReceiptContext = {
59
60
  };
60
61
 
61
62
  function scopedReceiptKey(input: {
63
+ orgId?: string | null;
62
64
  playName: string;
63
- runId: string;
64
65
  key: string;
65
66
  }): string {
66
- return `run:${input.runId}:play:${input.playName}:${input.key}`;
67
+ return `ctx:${input.orgId?.trim() || 'org'}:${input.playName.trim() || 'play'}:${input.key}`;
67
68
  }
68
69
 
69
70
  function receiptOutput<T>(receipt: WorkerRuntimeReceipt): T {
@@ -74,13 +75,21 @@ function errorMessage(error: unknown): string {
74
75
  return error instanceof Error ? error.message : String(error);
75
76
  }
76
77
 
78
+ function runningReceiptError(key: string, receipt: WorkerRuntimeReceipt): Error {
79
+ return new Error(
80
+ `Runtime receipt ${key} is already running for run ${receipt.runId ?? 'unknown'}.`,
81
+ );
82
+ }
83
+
77
84
  export async function runWorkerRuntimeReceiptBoundary<T>(
78
85
  input: RuntimeReceiptContext & {
79
86
  execute: () => Promise<T> | T;
80
87
  },
81
88
  ): Promise<T> {
82
89
  const key = scopedReceiptKey(input);
83
- const existing = await input.postRuntimeApi(input.baseUrl, input.executorToken, {
90
+ const postRuntimeReceiptAction = (body: WorkerRuntimeReceiptAction) =>
91
+ input.postRuntimeApi(input.baseUrl, input.executorToken, body);
92
+ const existing = await postRuntimeReceiptAction({
84
93
  action: 'get_runtime_step_receipt',
85
94
  playName: input.playName,
86
95
  runId: input.runId,
@@ -93,46 +102,67 @@ export async function runWorkerRuntimeReceiptBoundary<T>(
93
102
  return receiptOutput<T>(existing.receipt);
94
103
  }
95
104
 
96
- const claimed = await input.postRuntimeApi(input.baseUrl, input.executorToken, {
105
+ const claimed = await postRuntimeReceiptAction({
97
106
  action: 'claim_runtime_step_receipt',
98
107
  playName: input.playName,
99
108
  runId: input.runId,
100
109
  key,
101
110
  });
102
111
  if (!claimed.receipt) {
112
+ const latest = await postRuntimeReceiptAction({
113
+ action: 'get_runtime_step_receipt',
114
+ playName: input.playName,
115
+ runId: input.runId,
116
+ key,
117
+ });
103
118
  if (
104
- existing.receipt?.status === 'running' &&
105
- existing.receipt.runId !== input.runId
119
+ latest.receipt?.status === 'completed' ||
120
+ latest.receipt?.status === 'skipped'
106
121
  ) {
107
- throw new Error(
108
- `Runtime receipt ${key} is already running for run ${existing.receipt.runId ?? 'unknown'}.`,
109
- );
122
+ return receiptOutput<T>(latest.receipt);
110
123
  }
111
- if (existing.receipt?.status === 'failed') {
124
+ if (latest.receipt?.status === 'running') {
125
+ throw runningReceiptError(key, latest.receipt);
126
+ }
127
+ if (latest.receipt?.status === 'failed') {
112
128
  throw new Error(
113
- `Runtime receipt ${key} is failed and could not be claimed: ${existing.receipt.error ?? 'unknown error'}`,
129
+ `Runtime receipt ${key} is failed and could not be claimed: ${latest.receipt.error ?? 'unknown error'}`,
114
130
  );
115
131
  }
132
+ throw new Error(
133
+ `Runtime receipt ${key} claim did not return execution ownership.`,
134
+ );
116
135
  }
117
136
 
137
+ let output: T;
118
138
  try {
119
- const output = await input.execute();
120
- await input.postRuntimeApi(input.baseUrl, input.executorToken, {
121
- action: 'complete_runtime_step_receipt',
122
- playName: input.playName,
123
- runId: input.runId,
124
- key,
125
- output,
126
- });
127
- return output;
139
+ output = await input.execute();
128
140
  } catch (error) {
129
- await input.postRuntimeApi(input.baseUrl, input.executorToken, {
141
+ const failed = await postRuntimeReceiptAction({
130
142
  action: 'fail_runtime_step_receipt',
131
143
  playName: input.playName,
132
144
  runId: input.runId,
133
145
  key,
134
146
  error: errorMessage(error),
135
147
  });
148
+ if (!failed.receipt) {
149
+ throw new Error(
150
+ `Runtime receipt ${key} execution failed and failed receipt could not be persisted: ${errorMessage(error)}`,
151
+ );
152
+ }
136
153
  throw error;
137
154
  }
155
+ const completed = await postRuntimeReceiptAction({
156
+ action: 'complete_runtime_step_receipt',
157
+ playName: input.playName,
158
+ runId: input.runId,
159
+ key,
160
+ output,
161
+ });
162
+ if (!completed.receipt) {
163
+ throw new Error(
164
+ `Runtime receipt ${key} execution completed but completed receipt could not be persisted.`,
165
+ );
166
+ }
167
+ return output;
138
168
  }
@@ -166,6 +166,7 @@ export type ToolExecutionRequest = {
166
166
  tool: string;
167
167
  input: Record<string, unknown>;
168
168
  description?: string;
169
+ staleAfterSeconds?: number;
169
170
  };
170
171
 
171
172
  export type StepResolver<Row, Value> = (
@@ -297,7 +298,10 @@ export type CsvOptions = {
297
298
  * const enriched = await ctx
298
299
  * .map('companies', [{ domain: 'a.com' }, { domain: 'b.com' }])
299
300
  * .step('company', (row, rowCtx) =>
300
- * rowCtx.tool('company_search', 'test_company_search', { domain: row.domain }, {
301
+ * rowCtx.tools.execute({
302
+ * id: 'company_search',
303
+ * tool: 'test_company_search',
304
+ * input: { domain: row.domain },
301
305
  * description: 'Look up company details by domain.',
302
306
  * }))
303
307
  * .run({ description: 'Look up company details.' });
@@ -362,30 +366,30 @@ export interface DeeplinePlayRuntimeContext {
362
366
  *
363
367
  * @example Single tool per row
364
368
  * ```typescript
365
- * const results = await ctx
366
- * .map('companies', leads)
367
- * .step('company', (row, ctx) =>
368
- * ctx.tools.execute({
369
- * id: 'company_search',
370
- * tool: 'test_company_search',
371
- * input: { domain: row.domain },
372
- * description: 'Look up company details by domain.',
373
- * }))
369
+ * const results = await ctx
370
+ * .map('companies', leads)
371
+ * .step('company', (row, ctx) =>
372
+ * ctx.tools.execute({
373
+ * id: 'company_search',
374
+ * tool: 'test_company_search',
375
+ * input: { domain: row.domain },
376
+ * description: 'Look up company details by domain.',
377
+ * }))
374
378
  * .run({ description: 'Look up companies.' });
375
379
  * // [{ domain: 'stripe.com', company: { name: 'Stripe', ... } }, ...]
376
380
  * ```
377
381
  *
378
382
  * @example Multiple columns with pre/post logic
379
383
  * ```typescript
380
- * const results = await ctx
381
- * .map('leads', leads)
382
- * .step('company', (row, ctx) =>
383
- * ctx.tools.execute({
384
- * id: 'company_search',
385
- * tool: 'test_company_search',
386
- * input: { domain: row.domain },
387
- * description: 'Look up company details by domain.',
388
- * }))
384
+ * const results = await ctx
385
+ * .map('leads', leads)
386
+ * .step('company', (row, ctx) =>
387
+ * ctx.tools.execute({
388
+ * id: 'company_search',
389
+ * tool: 'test_company_search',
390
+ * input: { domain: row.domain },
391
+ * description: 'Look up company details by domain.',
392
+ * }))
389
393
  * .step('score', (row) =>
390
394
  * row.company?.employeeCount > 100 ? 'enterprise' : 'smb')
391
395
  * .run({ description: 'Enrich leads.' });
@@ -407,31 +411,24 @@ export interface DeeplinePlayRuntimeContext {
407
411
  * @returns The tool's output
408
412
  */
409
413
  execute<TOutput = LoosePlayObject>(
410
- request: ToolExecutionRequest,
414
+ request: ToolExecutionRequest & { staleAfterSeconds?: number },
411
415
  ): Promise<ToolExecuteResult<TOutput>>;
412
416
  };
413
- /**
414
- * Execute a single tool by stable step key and tool ID.
415
- *
416
- * Shorthand for `ctx.tools.execute(...)`; this is the preferred spelling in
417
- * row-level step programs.
418
- */
419
- tool<TOutput = LoosePlayObject>(
420
- key: string,
421
- toolId: string,
422
- input: Record<string, unknown>,
423
- options?: { description?: string },
424
- ): Promise<ToolExecuteResult<TOutput>>;
425
417
  runSteps<TInput extends Record<string, unknown>, TOutput>(
426
418
  program: StepProgram<TInput, any, TOutput>,
427
419
  input: TInput,
428
420
  options?: { description?: string },
429
421
  ): Promise<TOutput>;
430
- step<T>(id: string, run: () => T | Promise<T>): Promise<T>;
422
+ step<T>(
423
+ id: string,
424
+ run: () => T | Promise<T>,
425
+ options?: { staleAfterSeconds?: number },
426
+ ): Promise<T>;
431
427
  fetch(
432
428
  key: string,
433
429
  url: string | URL,
434
430
  init?: RequestInit,
431
+ options?: { staleAfterSeconds?: number },
435
432
  ): Promise<{
436
433
  ok: boolean;
437
434
  status: number;
@@ -445,7 +442,7 @@ export interface DeeplinePlayRuntimeContext {
445
442
  key: string,
446
443
  playRef: string | PlayReferenceLike,
447
444
  input: Record<string, unknown>,
448
- options: { description?: string },
445
+ options: { description?: string; staleAfterSeconds?: number },
449
446
  ): Promise<Record<string, unknown>>;
450
447
 
451
448
  /**
@@ -944,11 +941,9 @@ export class DeeplineContext {
944
941
  toolId: string,
945
942
  input: Record<string, unknown>,
946
943
  ): Promise<ToolExecuteResult> => {
947
- const response = await this.client.executeTool(
948
- toolId,
949
- input,
950
- { includeToolMetadata: true },
951
- );
944
+ const response = await this.client.executeTool(toolId, input, {
945
+ includeToolMetadata: true,
946
+ });
952
947
  return toolExecutionEnvelopeToResult(toolId, response);
953
948
  },
954
949
  };
@@ -1150,9 +1145,7 @@ function toolExecutionEnvelopeToResult(
1150
1145
  ? toolMetadata.toolId
1151
1146
  : fallbackToolId,
1152
1147
  extractors: extractorDescriptorRecord(toolMetadata.extractors),
1153
- targetGetters: stringArrayRecord(
1154
- toolMetadata.targetGetters,
1155
- ),
1148
+ targetGetters: stringArrayRecord(toolMetadata.targetGetters),
1156
1149
  listExtractorPaths: stringArray(toolMetadata.listExtractorPaths),
1157
1150
  listIdentityGetters: stringArrayRecord(toolMetadata.listIdentityGetters),
1158
1151
  },
@@ -53,7 +53,7 @@ export type HarnessBinding = PlayHarnessRpc;
53
53
  *
54
54
  * Why a module-level holder instead of threading the binding through
55
55
  * every call site: most leaf-call sites are deep inside SDK utilities
56
- * (`ctx.tool`, `ctx.csv`, etc.) and threading a binding everywhere would
56
+ * (`ctx.tools.execute`, `ctx.csv`, etc.) and threading a binding everywhere would
57
57
  * be a structural mess. A single set-once holder, set very early in the
58
58
  * worker's lifecycle, gives us clean call sites without sacrificing
59
59
  * isolation — each per-play Worker is its own isolate, so the holder is
@@ -1,2 +1,2 @@
1
- export const SDK_VERSION = "0.1.47";
1
+ export const SDK_VERSION = "0.1.48";
2
2
  export const SDK_API_CONTRACT = "2026-05-stripe-promo-checkout";
@@ -11,7 +11,6 @@
11
11
  import type {
12
12
  ConditionalStepResolver,
13
13
  DefinePlayConfig,
14
- DeeplineNamedPlay,
15
14
  DeeplinePlayRuntimeContext,
16
15
  DefinedPlay,
17
16
  PlayBindings,
@@ -57,9 +56,11 @@ type PlayMetadata = {
57
56
  billing?: PlayBindings['billing'];
58
57
  };
59
58
 
60
- class WorkerConditionalStepResolver<Row, Value, ElseValue>
61
- implements ConditionalStepResolver<Row, Value, ElseValue>
62
- {
59
+ class WorkerConditionalStepResolver<
60
+ Row,
61
+ Value,
62
+ ElseValue,
63
+ > implements ConditionalStepResolver<Row, Value, ElseValue> {
63
64
  readonly kind = 'conditional' as const;
64
65
 
65
66
  constructor(
@@ -75,9 +76,11 @@ class WorkerConditionalStepResolver<Row, Value, ElseValue>
75
76
  }
76
77
  }
77
78
 
78
- class WorkerStepProgram<Input, Output, ReturnValue>
79
- implements StepProgram<Input, Output, ReturnValue>
80
- {
79
+ class WorkerStepProgram<Input, Output, ReturnValue> implements StepProgram<
80
+ Input,
81
+ Output,
82
+ ReturnValue
83
+ > {
81
84
  readonly kind = 'steps' as const;
82
85
  declare readonly __inputType?: (input: Input) => void;
83
86
 
@@ -94,7 +97,7 @@ class WorkerStepProgram<Input, Output, ReturnValue>
94
97
  | StepProgramResolver<Output, Value>,
95
98
  ): StepProgram<Input, Output & Record<Name, Value>, ReturnValue> {
96
99
  if (!name.trim()) {
97
- throw new Error('steps().step(name, ...) requires a non-empty step name.');
100
+ throw new Error('Step name required.');
98
101
  }
99
102
  return new WorkerStepProgram(
100
103
  [
@@ -118,26 +121,6 @@ class WorkerStepProgram<Input, Output, ReturnValue>
118
121
  }
119
122
  }
120
123
 
121
- function createUnavailableNamedPlayHandle<TInput, TOutput extends PlayReturnObject>(
122
- name: string,
123
- ): DeeplineNamedPlay<TInput, TOutput> {
124
- const unavailable = () => {
125
- throw new Error(
126
- `definePlay("${name}") remote lifecycle methods are unavailable inside workers_edge play execution. Use ctx.runPlay(...) for runtime composition.`,
127
- );
128
- };
129
- return {
130
- name,
131
- get: unavailable,
132
- runs: unavailable,
133
- versions: unavailable,
134
- publish: unavailable,
135
- clearHistory: unavailable,
136
- run: unavailable,
137
- runSync: unavailable,
138
- };
139
- }
140
-
141
124
  export function steps<TInput>(): StepProgram<TInput, TInput, TInput> {
142
125
  return new WorkerStepProgram<TInput, TInput, TInput>([]);
143
126
  }
@@ -153,9 +136,7 @@ export function defineInput<TInput>(
153
136
  schema: Record<string, unknown>,
154
137
  ): PlayInputContract<TInput> {
155
138
  if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
156
- throw new Error(
157
- 'defineInput<T>(schema) requires a JSON-schema-like object.',
158
- );
139
+ throw new Error('defineInput schema must be an object');
159
140
  }
160
141
  return { schema };
161
142
  }
@@ -196,12 +177,10 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
196
177
  const name = config.name;
197
178
  const fn = config.fn;
198
179
  if (typeof fn !== 'function') {
199
- throw new Error('definePlay(...) requires an async run function.');
180
+ throw new Error('definePlay run must be async');
200
181
  }
201
182
  if (name.includes('/')) {
202
- throw new Error(
203
- 'definePlay(name, ...) play names cannot contain "/". Slash is reserved for qualified references like "prebuilt/example" or "self/example".',
204
- );
183
+ throw new Error('Play name cannot contain /');
205
184
  }
206
185
  const normalizedName = name
207
186
  .trim()
@@ -210,14 +189,10 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
210
189
  .replace(/^_+|_+$/g, '')
211
190
  .toLowerCase();
212
191
  if (!normalizedName) {
213
- throw new Error(
214
- 'definePlay(name, ...) requires a play name with at least one letter or number. Use only letters, numbers, underscores, or hyphens.',
215
- );
192
+ throw new Error('Play name required');
216
193
  }
217
194
  if (normalizedName.length > 63) {
218
- throw new Error(
219
- `definePlay("${name}", ...) is too long after normalization (${normalizedName.length}/63). Shorten the play name to 63 characters or fewer. Normalized value: "${normalizedName}".`,
220
- );
195
+ throw new Error('Play name >63');
221
196
  }
222
197
 
223
198
  const metadata: PlayMetadata = {
@@ -230,42 +205,17 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
230
205
 
231
206
  Object.defineProperty(play, PLAY_METADATA_SYMBOL, {
232
207
  value: metadata,
233
- enumerable: false,
234
- configurable: false,
235
- writable: false,
236
208
  });
237
209
  Object.defineProperty(play, 'playName', {
238
210
  value: name,
239
211
  enumerable: true,
240
- configurable: false,
241
- writable: false,
242
212
  });
243
213
  Object.defineProperty(play, 'bindings', {
244
214
  value: config.bindings,
245
215
  enumerable: true,
246
- configurable: false,
247
- writable: false,
248
216
  });
249
217
 
250
- const handle = createUnavailableNamedPlayHandle<TInput, TOutput>(name);
251
- for (const key of [
252
- 'name',
253
- 'get',
254
- 'runs',
255
- 'versions',
256
- 'publish',
257
- 'clearHistory',
258
- 'run',
259
- 'runSync',
260
- ] as const) {
261
- Object.defineProperty(play, key, {
262
- value: handle[key],
263
- enumerable: false,
264
- configurable: false,
265
- writable: false,
266
- });
267
- }
268
-
218
+ Object.defineProperty(play, 'name', { value: name });
269
219
  return play;
270
220
  }
271
221
 
@@ -38,7 +38,7 @@ function stableValue(value: unknown): unknown {
38
38
  return value;
39
39
  }
40
40
 
41
- function stableStringify(value: unknown): string {
41
+ export function stableStringify(value: unknown): string {
42
42
  return JSON.stringify(stableValue(value));
43
43
  }
44
44
 
@@ -46,7 +46,7 @@ function rightRotate32(value: number, bits: number): number {
46
46
  return (value >>> bits) | (value << (32 - bits));
47
47
  }
48
48
 
49
- function sha256Hex(input: string): string {
49
+ export function sha256Hex(input: string): string {
50
50
  const bytes = Array.from(new TextEncoder().encode(input));
51
51
  const bitLength = bytes.length * 8;
52
52
  bytes.push(0x80);
@@ -232,20 +232,6 @@ export function resolveStaleMapTableNamespace(
232
232
  return `${prefix}_${digest}`;
233
233
  }
234
234
 
235
- export function maxArtifactTableNamespaceLength(
236
- playName?: string | null,
237
- ): number {
238
- if (!playName?.trim()) {
239
- return POSTGRES_IDENTIFIER_MAX_LENGTH;
240
- }
241
-
242
- const normalizedPlayName = normalizePlayNameForSheet(playName);
243
- return Math.max(
244
- 1,
245
- POSTGRES_IDENTIFIER_MAX_LENGTH - normalizedPlayName.length - 1,
246
- );
247
- }
248
-
249
235
  export function validatePlaySheetTableName(
250
236
  playName: string,
251
237
  tableNamespace: string,
@@ -263,71 +249,37 @@ export function validatePlaySheetTableName(
263
249
  return resolved;
264
250
  }
265
251
 
266
- export function resolvePlayRunTableNamespace(
267
- tableNamespace: string,
268
- _input?: Record<string, unknown> | null,
269
- _options?: {
270
- playName?: string | null;
271
- },
272
- ): string {
273
- return normalizeTableNamespace(tableNamespace);
274
- }
275
-
276
252
  export function derivePlayRowIdentity(
277
253
  row: Record<string, unknown>,
278
254
  tableNamespace: string,
279
- logicFingerprint?: string | null,
280
255
  ): string {
281
256
  return deriveDerivedOutputIdentity({
282
257
  inputItem: row,
283
258
  operationNamespace: tableNamespace,
284
- logicFingerprint,
285
259
  });
286
260
  }
287
261
 
288
262
  export function deriveDerivedOutputIdentity(input: {
289
263
  inputItem: Record<string, unknown>;
290
264
  operationNamespace: string;
291
- logicFingerprint?: string | null;
292
265
  }): string {
293
- const normalizedNamespace = normalizeTableNamespace(
294
- input.operationNamespace,
295
- );
266
+ const normalizedNamespace = normalizeTableNamespace(input.operationNamespace);
296
267
  const canonicalRow = stableStringify(input.inputItem);
297
- const fingerprint = input.logicFingerprint?.trim()
298
- ? `\nlogic:${input.logicFingerprint.trim()}`
299
- : '';
300
- const digest = sha256Hex(
301
- `${normalizedNamespace}${fingerprint}\n${canonicalRow}`,
302
- );
268
+ const digest = sha256Hex(`${normalizedNamespace}\n${canonicalRow}`);
303
269
  return `${normalizedNamespace}:${digest}`;
304
270
  }
305
271
 
306
272
  export function deriveToolRequestIdentity(input: {
307
273
  toolId: string;
308
274
  requestInput: Record<string, unknown>;
309
- effectiveAccountContext?: string | null;
310
- toolContractRevision?: string | number | null;
311
- reuseSafetyPolicy?: string | null;
312
275
  }): string {
313
276
  const toolId = input.toolId.trim();
314
277
  if (!toolId) {
315
278
  throw new Error('Tool request identity requires a non-empty tool id.');
316
279
  }
317
- const accountContext =
318
- input.effectiveAccountContext?.trim() || 'default_account_context';
319
- const contractRevision =
320
- input.toolContractRevision == null
321
- ? 'default_tool_contract'
322
- : String(input.toolContractRevision).trim() || 'default_tool_contract';
323
- const reuseSafetyPolicy =
324
- input.reuseSafetyPolicy?.trim() || 'default_reuse_policy';
325
280
  const digest = sha256Hex(
326
281
  stableStringify({
327
- accountContext,
328
282
  requestInput: input.requestInput,
329
- reuseSafetyPolicy,
330
- toolContractRevision: contractRevision,
331
283
  toolId,
332
284
  }),
333
285
  );
@@ -344,14 +296,8 @@ export function deriveToolRequestIdentity(input: {
344
296
  export function derivePlayRowIdentityFromKey(
345
297
  key: string,
346
298
  tableNamespace: string,
347
- logicFingerprint?: string | null,
348
299
  ): string {
349
300
  const normalizedNamespace = normalizeTableNamespace(tableNamespace);
350
- const fingerprint = logicFingerprint?.trim()
351
- ? `\nlogic:${logicFingerprint.trim()}`
352
- : '';
353
- const digest = sha256Hex(`${normalizedNamespace}${fingerprint}\nkey:${key}`);
301
+ const digest = sha256Hex(`${normalizedNamespace}\nkey:${key}`);
354
302
  return `${normalizedNamespace}:${digest}`;
355
303
  }
356
-
357
- export const normalizeMapKeyNamespace = normalizeTableNamespace;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {