deepline 0.1.186 → 0.1.188

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.
@@ -168,6 +168,10 @@ import {
168
168
  WorkerRunWorkDispatcher,
169
169
  type WorkerRunMapBatchesResult,
170
170
  } from './runtime/run-work-dispatcher';
171
+ import {
172
+ boundedWorkflowPreviewRows,
173
+ WORKFLOW_MAP_CHUNK_PREVIEW_MAX_BYTES,
174
+ } from './runtime/workflow-preview';
171
175
  import { WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL } from './runtime/worker-platform-budget';
172
176
  import {
173
177
  PLAY_RUNTIME_CONTRACT,
@@ -252,6 +256,7 @@ import { normalizePlayRunFailure } from '../../../shared_libs/play-runtime/run-f
252
256
  import {
253
257
  createRuntimePersistenceLatch,
254
258
  isRuntimePersistenceCircuitOpenError,
259
+ RuntimePersistenceCircuitOpenError,
255
260
  tripRuntimePersistenceLatch,
256
261
  } from '../../../shared_libs/play-runtime/persistence-latch';
257
262
  import {
@@ -2420,7 +2425,8 @@ type WorkerMapBlockedChunkSummary = {
2420
2425
  };
2421
2426
 
2422
2427
  type WorkerMapChunkSummary<T extends Record<string, unknown>> =
2423
- WorkerMapCompletedChunkSummary<T> | WorkerMapBlockedChunkSummary;
2428
+ | WorkerMapCompletedChunkSummary<T>
2429
+ | WorkerMapBlockedChunkSummary;
2424
2430
 
2425
2431
  function serializeDurableStepValue<T>(value: T, depth = 0): T {
2426
2432
  if (depth > 20 || value == null) return value;
@@ -2479,7 +2485,9 @@ type WorkerConditionalStepResolver = {
2479
2485
  type WorkerStepProgramStep = {
2480
2486
  name: string;
2481
2487
  resolver:
2482
- WorkerStepResolver | WorkerConditionalStepResolver | WorkerStepProgram;
2488
+ | WorkerStepResolver
2489
+ | WorkerConditionalStepResolver
2490
+ | WorkerStepProgram;
2483
2491
  };
2484
2492
 
2485
2493
  type WorkerStepProgram = {
@@ -3416,7 +3424,9 @@ function resolveSheetContractFromReq(
3416
3424
  // `staticPipeline` field is what `resolveSheetContractForTableNamespace`
3417
3425
  // expects. Reach in safely.
3418
3426
  const snapshot = req.contractSnapshot as
3419
- { staticPipeline?: unknown } | undefined | null;
3427
+ | { staticPipeline?: unknown }
3428
+ | undefined
3429
+ | null;
3420
3430
  const pipeline = snapshot?.staticPipeline;
3421
3431
  if (!pipeline || typeof pipeline !== 'object' || Array.isArray(pipeline)) {
3422
3432
  return null;
@@ -3508,7 +3518,9 @@ async function releaseRuntimeLeasesOnTeardown(input: {
3508
3518
 
3509
3519
  function staticPipelineFromReq(req: RunRequest): PlayStaticPipeline | null {
3510
3520
  const snapshot = req.contractSnapshot as
3511
- { staticPipeline?: unknown } | undefined | null;
3521
+ | { staticPipeline?: unknown }
3522
+ | undefined
3523
+ | null;
3512
3524
  const pipeline = snapshot?.staticPipeline;
3513
3525
  if (!pipeline || typeof pipeline !== 'object' || Array.isArray(pipeline)) {
3514
3526
  return null;
@@ -5100,6 +5112,15 @@ function createMinimalWorkerCtx(
5100
5112
  }
5101
5113
  };
5102
5114
 
5115
+ const runtimePersistenceCircuitError = (): Error | null => {
5116
+ if (persistFailure) {
5117
+ tripRuntimePersistenceLatch(persistenceLatch, persistFailure);
5118
+ }
5119
+ return persistenceLatch.tripped
5120
+ ? new RuntimePersistenceCircuitOpenError(persistenceLatch)
5121
+ : null;
5122
+ };
5123
+
5103
5124
  const persistExecutedRows = async () => {
5104
5125
  const rowsToPersist = executedRows
5105
5126
  .map((row, executedIndex) =>
@@ -5207,11 +5228,13 @@ function createMinimalWorkerCtx(
5207
5228
  pendingPersistRows = 0;
5208
5229
  pendingPersistBytes = 0;
5209
5230
  const task = persistFlushChain.then(async () => {
5210
- if (persistFailure) throw persistFailure;
5231
+ const circuitError = runtimePersistenceCircuitError();
5232
+ if (circuitError) throw circuitError;
5211
5233
  await persistExecutedRows();
5212
5234
  });
5213
5235
  persistFlushChain = task.catch((error) => {
5214
5236
  persistFailure ??= error;
5237
+ tripRuntimePersistenceLatch(persistenceLatch, error);
5215
5238
  });
5216
5239
  return task;
5217
5240
  };
@@ -5296,26 +5319,31 @@ function createMinimalWorkerCtx(
5296
5319
  return Promise.resolve();
5297
5320
  };
5298
5321
 
5299
- const schedulePersistExecutedRows = () => {
5300
- if (persistFailure) return;
5322
+ const schedulePersistExecutedRows = (): Promise<void> | null => {
5323
+ const circuitError = runtimePersistenceCircuitError();
5324
+ if (circuitError) return Promise.reject(circuitError);
5301
5325
  if (
5302
5326
  pendingPersistRows >= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS ||
5303
5327
  pendingPersistBytes >= MAP_INCREMENTAL_PERSIST_CHUNK_BYTES
5304
5328
  ) {
5305
- void enqueuePersistExecutedRows().catch(() => undefined);
5306
- return;
5329
+ return enqueuePersistExecutedRows();
5307
5330
  }
5308
- if (scheduledPersistTimer) return;
5331
+ if (scheduledPersistTimer) return null;
5309
5332
  scheduledPersistTimer = setTimeout(() => {
5310
5333
  scheduledPersistTimer = null;
5311
5334
  void enqueuePersistExecutedRows().catch(() => undefined);
5312
5335
  }, MAP_INCREMENTAL_PERSIST_INTERVAL_MS);
5336
+ return null;
5313
5337
  };
5314
5338
 
5315
- const notePersistableRow = (row: Record<string, unknown>) => {
5339
+ const notePersistableRow = async (
5340
+ row: Record<string, unknown>,
5341
+ ): Promise<void> => {
5316
5342
  pendingPersistRows += 1;
5317
5343
  pendingPersistBytes += JSON.stringify(row).length;
5318
- schedulePersistExecutedRows();
5344
+ await (schedulePersistExecutedRows() ?? Promise.resolve());
5345
+ const circuitError = runtimePersistenceCircuitError();
5346
+ if (circuitError) throw circuitError;
5319
5347
  };
5320
5348
 
5321
5349
  let idx = 0;
@@ -5325,6 +5353,8 @@ function createMinimalWorkerCtx(
5325
5353
  (async () => {
5326
5354
  while (true) {
5327
5355
  if (abortSignal?.aborted) return;
5356
+ const circuitError = runtimePersistenceCircuitError();
5357
+ if (circuitError) throw circuitError;
5328
5358
  const myIndex = idx++;
5329
5359
  if (myIndex >= rowsToExecute.length) return;
5330
5360
  const rowSlot = await governor.acquireRowSlot({
@@ -5472,7 +5502,7 @@ function createMinimalWorkerCtx(
5472
5502
  executedRows[myIndex] = enriched as T &
5473
5503
  Record<string, unknown>;
5474
5504
  completedExecutedRows += 1;
5475
- notePersistableRow(enriched);
5505
+ await notePersistableRow(enriched);
5476
5506
  reportChunkProgress(false);
5477
5507
  } catch (rowError) {
5478
5508
  // Receipt persistence errors stay run-fatal, but keep the
@@ -5522,7 +5552,7 @@ function createMinimalWorkerCtx(
5522
5552
  };
5523
5553
  failedExecutedRows += 1;
5524
5554
  if (!failFastRowErrors) {
5525
- notePersistableRow(enriched);
5555
+ await notePersistableRow(enriched);
5526
5556
  }
5527
5557
  // Bounded per-chunk samples: every failure is persisted on
5528
5558
  // its row, but only the first few get a log line so a wide
@@ -5636,7 +5666,8 @@ function createMinimalWorkerCtx(
5636
5666
  rangeStart: baseOffset + chunkStart,
5637
5667
  rangeEnd: baseOffset + chunkStart,
5638
5668
  rowsRead: chunkRows.length,
5639
- rowsWritten: 0,
5669
+ rowsWritten:
5670
+ persistedExecutedIndexes.size + persistedFailedIndexes.size,
5640
5671
  rowsExecuted: rowsToExecute.length,
5641
5672
  rowsCached: 0,
5642
5673
  rowsDuplicateReused: duplicateInputReuseCount,
@@ -5867,7 +5898,11 @@ function createMinimalWorkerCtx(
5867
5898
  // storage may keep only the bounded preview sample; same-run play code
5868
5899
  // that needs more rows uses volatileWorkflowChunkRows, which is not
5869
5900
  // part of the persisted step result.
5870
- preview: toWorkflowSerializableValue(publicOut.slice(0, 5)),
5901
+ preview: boundedWorkflowPreviewRows(publicOut, {
5902
+ maxRows: WORKER_DATASET_PREVIEW_ROWS,
5903
+ maxBytes: WORKFLOW_MAP_CHUNK_PREVIEW_MAX_BYTES,
5904
+ serialize: toWorkflowSerializableValue,
5905
+ }),
5871
5906
  cachedRows:
5872
5907
  includeCachedRowsInChunkResult &&
5873
5908
  out.length <= WORKER_DATASET_IN_MEMORY_ROWS
@@ -6993,7 +7028,8 @@ function createMinimalWorkerCtx(
6993
7028
  // the small structural shape ChildPlayAwait needs; bridge it the
6994
7029
  // same way the inline implementation did.
6995
7030
  workflowStep: workflowStep as unknown as
6996
- WorkflowStepLike | undefined,
7031
+ | WorkflowStepLike
7032
+ | undefined,
6997
7033
  workflowId,
6998
7034
  playName: resolvedName,
6999
7035
  key: normalizedKey,
@@ -0,0 +1,26 @@
1
+ export const WORKFLOW_MAP_CHUNK_PREVIEW_MAX_BYTES = 64 * 1024;
2
+
3
+ const jsonByteLength = (value: unknown): number =>
4
+ new TextEncoder().encode(JSON.stringify(value)).byteLength;
5
+
6
+ export function boundedWorkflowPreviewRows<T extends Record<string, unknown>>(
7
+ rows: readonly T[],
8
+ input: {
9
+ maxRows: number;
10
+ maxBytes: number;
11
+ serialize?: (row: T) => T;
12
+ },
13
+ ): T[] {
14
+ const preview: T[] = [];
15
+ let bytes = 2; // Opening and closing array brackets.
16
+ for (const row of rows) {
17
+ if (preview.length >= input.maxRows) break;
18
+ const serialized = input.serialize ? input.serialize(row) : row;
19
+ const rowBytes = jsonByteLength(serialized) + (preview.length > 0 ? 1 : 0);
20
+ if (rowBytes > input.maxBytes) continue;
21
+ if (bytes + rowBytes > input.maxBytes) break;
22
+ preview.push(serialized);
23
+ bytes += rowBytes;
24
+ }
25
+ return preview;
26
+ }
@@ -117,9 +117,16 @@ import type { ToolExecution } from './client.js';
117
117
  /**
118
118
  * Optional trigger bindings for a play.
119
119
  *
120
- * Plays can be triggered by webhooks (with HMAC signature verification)
121
- * or cron schedules. Bindings are declared as the third argument to
122
- * {@link definePlay}.
120
+ * A play can be triggered three ways, declared as the third argument to
121
+ * {@link definePlay}:
122
+ * - `webhook` — an inbound HTTP call (with optional HMAC signature verification);
123
+ * - `cron` — a schedule; or
124
+ * - `sqlListeners` — a **monitor**: the play runs whenever a monitor writes a new
125
+ * row to its output stream. This is how you build a play "on top of" a monitor
126
+ * (e.g. run enrichment every time a watched company posts a new job). Each
127
+ * listener binds to a monitor tool id + one of its output stream keys (see
128
+ * `deepline monitors available <id>` for a tool's streams and row columns).
129
+ * The changed row is delivered to the handler as the listener event's `after`.
123
130
  *
124
131
  * @example Webhook with HMAC verification
125
132
  * ```typescript
@@ -141,6 +148,20 @@ import type { ToolExecution } from './client.js';
141
148
  * });
142
149
  * ```
143
150
  *
151
+ * @example Monitor-triggered (run a play on a monitor's output)
152
+ * ```typescript
153
+ * definePlay('on-new-job-opening', handler, {
154
+ * sqlListeners: [
155
+ * {
156
+ * id: 'jobs',
157
+ * tool: 'tamradar.company_radar',
158
+ * stream: 'company_job_openings',
159
+ * operations: ['INSERT'],
160
+ * },
161
+ * ],
162
+ * });
163
+ * ```
164
+ *
144
165
  * @sdkReference runtime 030
145
166
  */
146
167
  export type PlayBindings = {
@@ -105,10 +105,10 @@ export const SDK_RELEASE = {
105
105
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
106
106
  // fields shipped in 0.1.153.
107
107
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
108
- version: '0.1.186',
108
+ version: '0.1.188',
109
109
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
110
110
  supportPolicy: {
111
- latest: '0.1.186',
111
+ latest: '0.1.188',
112
112
  minimumSupported: '0.1.53',
113
113
  deprecatedBelow: '0.1.53',
114
114
  commandMinimumSupported: [
@@ -922,10 +922,98 @@ export interface PlayCheckResult {
922
922
  warnings?: string[];
923
923
  staticPipeline?: Record<string, unknown> | null;
924
924
  toolGetterHints?: PlayCheckToolGetterHint[];
925
+ /**
926
+ * Recognized trigger bindings parsed from the play source (`sqlListeners` /
927
+ * cron / webhook). Present only when the play declares at least one trigger,
928
+ * so an author can confirm the binding was recognized rather than silently
929
+ * dropped. Field names mirror the `definePlay` binding contract.
930
+ */
931
+ triggers?: PlayCheckTriggersSummary | null;
932
+ /**
933
+ * Structured, machine-actionable issues surfaced by the check. An ADDITIVE
934
+ * channel alongside `errors[]`: every `error`-severity issue is also present
935
+ * in `errors[]` (so string-level tooling keeps working), while an agent can
936
+ * read `code` / `validOptions` / `docsHint` to self-correct. `warning`
937
+ * severity issues do NOT make the check invalid.
938
+ */
939
+ issues?: PlayCheckIssue[];
940
+ /**
941
+ * Affirmative echo of what Deepline recognized — triggers, tools,
942
+ * datasets/columns, inputs, outputs — so a valid check reflects the parsed
943
+ * shape rather than a bare "ok".
944
+ */
945
+ recognized?: PlayCheckRecognizedSummary;
946
+ /**
947
+ * Human one-liner over {@link PlayCheckResult.recognized}, e.g.
948
+ * `1 trigger · 2 tools · 1 dataset · 14 columns`.
949
+ */
950
+ summary?: string;
925
951
  artifactHash?: string | null;
926
952
  graphHash?: string | null;
927
953
  }
928
954
 
955
+ /** Severity of a {@link PlayCheckIssue}. `error` fails the check; `warning` does not. */
956
+ export type PlayCheckIssueSeverity = 'error' | 'warning';
957
+
958
+ /**
959
+ * One structured, machine-actionable issue surfaced by `deepline plays check`.
960
+ * Mirrors the server-side `PlayCheckIssue` contract. `code` is a stable machine
961
+ * code from a closed server-side set; `validOptions` enumerates the valid values
962
+ * (tool ids / stream keys / columns / operators) so an agent self-corrects by
963
+ * reading the structure instead of brute-forcing the compiler.
964
+ */
965
+ export interface PlayCheckIssue {
966
+ code: string;
967
+ severity: PlayCheckIssueSeverity;
968
+ message: string;
969
+ path?: string;
970
+ hint?: string;
971
+ validOptions?: string[];
972
+ docsHint?: string;
973
+ }
974
+
975
+ /**
976
+ * Affirmative "here's what Deepline recognized" echo returned alongside a
977
+ * check's issues. Field names reuse the public play binding contract.
978
+ */
979
+ export interface PlayCheckRecognizedSummary {
980
+ triggers?: PlayCheckTriggersSummary;
981
+ tools?: string[];
982
+ datasets?: { name: string; columns?: string[] }[];
983
+ inputs?: string[];
984
+ outputs?: string[];
985
+ }
986
+
987
+ /**
988
+ * One recognized SQL-listener trigger echoed back by {@link DeeplineClient.checkPlayArtifact}.
989
+ */
990
+ export interface PlayCheckSqlListenerTrigger {
991
+ id: string;
992
+ tool?: string;
993
+ stream?: string;
994
+ operations: string[];
995
+ /**
996
+ * The recognized `sqlListeners.where` row filter, echoed back so an author can
997
+ * confirm Deepline bound it. Mirrors the server `StoredSqlListenerWhere` shape
998
+ * (`before` / `after` maps of column → operator filter). Absent when the
999
+ * listener declares no `where`.
1000
+ */
1001
+ where?: {
1002
+ before?: Record<string, Record<string, unknown>>;
1003
+ after?: Record<string, Record<string, unknown>>;
1004
+ };
1005
+ }
1006
+
1007
+ /**
1008
+ * Concise summary of the trigger bindings the server recognized for a play.
1009
+ * Only present triggers are populated.
1010
+ */
1011
+ export interface PlayCheckTriggersSummary {
1012
+ sqlListeners?: PlayCheckSqlListenerTrigger[];
1013
+ cron?: { schedule: string; timezone?: string };
1014
+ webhook?: true;
1015
+ }
1016
+
929
1017
  export interface PlayCheckToolGetterHint {
930
1018
  toolId: string;
931
1019
  lists: Array<{