deepline 0.1.70 → 0.1.71

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.
package/README.md CHANGED
@@ -212,8 +212,8 @@ export default definePlay('rate-limit-repro', async (ctx) => {
212
212
  row_number: i + 1,
213
213
  }));
214
214
 
215
- const results = await ctx.map('rate_limit_probes', items)
216
- .step("result", (row, ctx) =>
215
+ const results = await ctx.dataset('rate_limit_probes', items)
216
+ .withColumn("result", (row, ctx) =>
217
217
  ctx.tools.execute({
218
218
  id: 'rate_limit_probe',
219
219
  tool: 'test_rate_limit',
@@ -242,12 +242,12 @@ deepline play run --file rate-limit-repro.play.ts --watch
242
242
 
243
243
  ### 4. Fallback variant (multi-step with rate limits)
244
244
 
245
- Use `steps()` and `when()` to express row-level fallback sequences. Skipped
245
+ Use `steps()` and `runIf()` to express row-level fallback sequences. Skipped
246
246
  conditional steps yield `null`.
247
247
 
248
248
  ```bash
249
249
  cat > rate-limit-waterfall.play.ts << 'TS'
250
- import { definePlay, steps, when } from 'deepline';
250
+ import { definePlay, runIf, steps } from 'deepline';
251
251
 
252
252
  export default definePlay('rate-limit-waterfall', async (ctx) => {
253
253
  const leads = Array.from({ length: 24 }, (_, i) => ({
@@ -267,7 +267,7 @@ export default definePlay('rate-limit-waterfall', async (ctx) => {
267
267
  },
268
268
  description: 'Exercise primary rate-limit behavior.',
269
269
  }))
270
- .step("backup_probe", when(
270
+ .step("backup_probe", runIf(
271
271
  (row) => !row.primary_probe,
272
272
  (row, ctx) =>
273
273
  ctx.tools.execute({
@@ -284,8 +284,8 @@ export default definePlay('rate-limit-waterfall', async (ctx) => {
284
284
  .return((row) => row.primary_probe ?? row.backup_probe ?? null);
285
285
 
286
286
  // Fan out over leads — each gets a row-level fallback sequence
287
- const results = await ctx.map('leads', leads)
288
- .step("probe", probeFallback)
287
+ const results = await ctx.dataset('leads', leads)
288
+ .withColumn("probe", probeFallback)
289
289
  .run();
290
290
 
291
291
  return results;
package/dist/cli/index.js CHANGED
@@ -229,10 +229,10 @@ var import_node_path2 = require("path");
229
229
 
230
230
  // src/release.ts
231
231
  var SDK_RELEASE = {
232
- version: "0.1.70",
233
- apiContract: "2026-05-play-bootstrap-dataset-summary",
232
+ version: "0.1.71",
233
+ apiContract: "2026-06-dataset-column-syntax-cutover",
234
234
  supportPolicy: {
235
- latest: "0.1.70",
235
+ latest: "0.1.71",
236
236
  minimumSupported: "0.1.53",
237
237
  deprecatedBelow: "0.1.53"
238
238
  }
@@ -6885,7 +6885,7 @@ function playInspectionComments(playRef, indent) {
6885
6885
  function accessorExpression(base, field) {
6886
6886
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field) ? `${base}.${field}` : `${base}[${jsString(field)}]`;
6887
6887
  }
6888
- function needsWhenImport(options) {
6888
+ function needsRunIfImport(options) {
6889
6889
  return stageProviders(options.email).length > 1 || stageProviders(options.phone).length > 1;
6890
6890
  }
6891
6891
  function sourceCollectionTypeName(entity) {
@@ -7187,7 +7187,7 @@ function generateFinderPlayStep(input2) {
7187
7187
  " ",
7188
7188
  input2.stage.value
7189
7189
  );
7190
- return `.step(${jsString(outputField)}, async (row, rowCtx) => {
7190
+ return `.withColumn(${jsString(outputField)}, async (row, rowCtx) => {
7191
7191
  const ${input2.aggregateStepName}Input = ${payload};
7192
7192
  throw new Error(${jsString(`TODO: map ${input2.aggregateStepName}Input for ${input2.stage.value}, then delete this throw.`)});
7193
7193
  const ${input2.aggregateStepName}Result = await rowCtx.runPlay<${resultTypeName}>(
@@ -7233,12 +7233,12 @@ function generateFinderProviderStep(input2) {
7233
7233
  const stepName = input2.stepNames[input2.index];
7234
7234
  switch (input2.index) {
7235
7235
  case 0:
7236
- return `.step(${jsString(stepName)}, ${input2.resolver})`;
7236
+ return `.withColumn(${jsString(stepName)}, ${input2.resolver})`;
7237
7237
  default: {
7238
7238
  const priorCandidates = input2.stepNames.slice(0, input2.index).map((name) => `row.${name}`).join(", ");
7239
- return `.step(
7239
+ return `.withColumn(
7240
7240
  ${jsString(stepName)},
7241
- when(
7241
+ runIf(
7242
7242
  (row) => ![${priorCandidates}].some((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)}),
7243
7243
  ${input2.resolver},
7244
7244
  ),
@@ -7272,9 +7272,9 @@ function generateFinderProviderWaterfall(input2) {
7272
7272
  );
7273
7273
  const candidateNames = stepNames.map((name) => `row.${name}`).join(", ");
7274
7274
  return `// ${input2.aggregateStepName} provider waterfall. Each provider leg is active once its TODO throw is removed;
7275
- // delete or comment out legs you do not want before running. Later legs are gated with when(...).
7275
+ // delete or comment out legs you do not want before running. Later legs are gated with runIf(...).
7276
7276
  ${providerSteps.join("\n ")}
7277
- .step(${jsString(input2.aggregateStepName)}, (row) => {
7277
+ .withColumn(${jsString(input2.aggregateStepName)}, (row) => {
7278
7278
  const candidates = [${candidateNames}];
7279
7279
  const match = candidates.find((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)});
7280
7280
  return ${optionalFinderValueExpression("match", input2.outputField)} ?? null;
@@ -7329,7 +7329,7 @@ function generateBootstrapPlaySource(input2) {
7329
7329
  rowTypeDefinitions,
7330
7330
  finderPlayResultTypes
7331
7331
  ].filter((definition) => definition.trim().length > 0).join("\n\n");
7332
- const importNames = needsWhenImport(input2.options) ? "definePlay, when" : "definePlay";
7332
+ const importNames = needsRunIfImport(input2.options) ? "definePlay, runIf" : "definePlay";
7333
7333
  return `import { ${importNames} } from 'deepline';
7334
7334
 
7335
7335
  ${typeDefinitions}
@@ -7348,7 +7348,7 @@ export default definePlay(${jsString(input2.options.name)}, async (ctx, input: I
7348
7348
  }
7349
7349
 
7350
7350
  const rows = await ctx
7351
- .map('bootstrap_rows', rowsToProcess)${mapSteps}
7351
+ .dataset('bootstrap_rows', rowsToProcess)${mapSteps}
7352
7352
  .run({
7353
7353
  key: (_row, index) => index,
7354
7354
  description: ${jsString(`Bootstrap ${input2.options.template}: seed source rows, run requested stages, then return the mapped rows.`)},
@@ -7858,7 +7858,7 @@ function normalizePlayNameForSheet(value) {
7858
7858
  function normalizeTableNamespace(value) {
7859
7859
  return validateIdentifierPart(
7860
7860
  value,
7861
- "ctx.map() key",
7861
+ "ctx.dataset() key",
7862
7862
  MAP_KEY_NAMESPACE_MAX_LENGTH
7863
7863
  );
7864
7864
  }
@@ -7868,7 +7868,7 @@ function validatePlaySheetTableName(playName, tableNamespace) {
7868
7868
  const resolved = `${playSegment}_${keySegment}`;
7869
7869
  if (resolved.length > POSTGRES_IDENTIFIER_MAX_LENGTH) {
7870
7870
  throw new Error(
7871
- `Play sheet table name is too long after normalization (${resolved.length}/63). Shorten the play name or ctx.map() key. Resolved table name: "${resolved}".`
7871
+ `Play sheet table name is too long after normalization (${resolved.length}/63). Shorten the play name or ctx.dataset() key. Resolved table name: "${resolved}".`
7872
7872
  );
7873
7873
  }
7874
7874
  return resolved;
@@ -10111,10 +10111,66 @@ function formatActiveRunConflictError(input2) {
10111
10111
  lines.push(` rerun: add --force to the same deepline plays run command`);
10112
10112
  return lines.join("\n");
10113
10113
  }
10114
+ var PLAY_SYNTAX_MIGRATION_ERROR_MARKERS = [
10115
+ "ctx.map(...) has been replaced by ctx.dataset(...)",
10116
+ "Dataset .step(...) has been replaced by .withColumn(...)"
10117
+ ];
10118
+ function stringArrayField(record, key) {
10119
+ const value = record[key];
10120
+ if (!Array.isArray(value)) return [];
10121
+ return value.filter((entry) => typeof entry === "string");
10122
+ }
10123
+ function extractPlayValidationErrors(value) {
10124
+ if (value instanceof DeeplineError) {
10125
+ return extractPlayValidationErrors(value.details);
10126
+ }
10127
+ if (!isRecord4(value)) {
10128
+ return [];
10129
+ }
10130
+ const directErrors = stringArrayField(value, "errors");
10131
+ if (directErrors.length > 0) {
10132
+ return directErrors;
10133
+ }
10134
+ for (const key of ["response", "error", "details"]) {
10135
+ const nestedErrors = extractPlayValidationErrors(value[key]);
10136
+ if (nestedErrors.length > 0) {
10137
+ return nestedErrors;
10138
+ }
10139
+ }
10140
+ return [];
10141
+ }
10142
+ function orderPlayValidationErrors(errors) {
10143
+ const uniqueErrors = [...new Set(errors)];
10144
+ const migrationErrors = uniqueErrors.filter(
10145
+ (error) => PLAY_SYNTAX_MIGRATION_ERROR_MARKERS.some(
10146
+ (marker) => error.includes(marker)
10147
+ )
10148
+ );
10149
+ const remainingErrors = uniqueErrors.filter(
10150
+ (error) => !migrationErrors.includes(error)
10151
+ );
10152
+ return [...migrationErrors, ...remainingErrors];
10153
+ }
10154
+ function normalizePlayValidationError(error) {
10155
+ const validationErrors = extractPlayValidationErrors(error);
10156
+ if (validationErrors.length === 0) {
10157
+ return error;
10158
+ }
10159
+ const message = orderPlayValidationErrors(validationErrors).join("\n");
10160
+ if (!message.trim()) {
10161
+ return error;
10162
+ }
10163
+ return new DeeplineError(
10164
+ message,
10165
+ error instanceof DeeplineError ? error.statusCode : void 0,
10166
+ error instanceof DeeplineError ? error.code : "PLAY_VALIDATION_ERROR",
10167
+ error instanceof DeeplineError ? error.details : void 0
10168
+ );
10169
+ }
10114
10170
  function normalizePlayStartError(error, playName) {
10115
10171
  const activeRuns = extractActiveRunsFromError(error);
10116
10172
  if (activeRuns.length === 0) {
10117
- return error;
10173
+ return normalizePlayValidationError(error);
10118
10174
  }
10119
10175
  return new DeeplineError(
10120
10176
  formatActiveRunConflictError({ playName, activeRuns }),
@@ -10277,7 +10333,7 @@ function writeStartedPlayRun(input2) {
10277
10333
  );
10278
10334
  }
10279
10335
  function parsePlayRunOptions(args) {
10280
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
10336
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.dataset guidance.";
10281
10337
  let filePath = null;
10282
10338
  let playName = null;
10283
10339
  let input2 = null;
@@ -10640,7 +10696,10 @@ async function handleFileBackedRun(options) {
10640
10696
  progress.phase("compiled play");
10641
10697
  } catch (error) {
10642
10698
  progress.fail();
10643
- console.error(error instanceof Error ? error.message : String(error));
10699
+ const normalizedError = normalizePlayValidationError(error);
10700
+ console.error(
10701
+ normalizedError instanceof Error ? normalizedError.message : String(normalizedError)
10702
+ );
10644
10703
  return 1;
10645
10704
  }
10646
10705
  const bundleResult = graph.root;
@@ -11783,7 +11842,7 @@ function registerPlayCommands(program) {
11783
11842
  Concepts:
11784
11843
  Plays are durable cloud workflows.
11785
11844
  Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
11786
- ctx.map adds row keys and row progress.
11845
+ ctx.dataset adds row keys and row progress.
11787
11846
  Named play runs use the live revision unless --latest or --revision-id is set.
11788
11847
  Running a local file does not make it live; use set-live/publish explicitly.
11789
11848
 
@@ -11836,19 +11895,19 @@ Idempotent execution:
11836
11895
 
11837
11896
  await ctx.tools.execute({ id: 'company_lookup', tool, input });
11838
11897
 
11839
- For rows, use ctx.map plus a stable row key:
11898
+ For rows, use ctx.dataset plus a stable row key:
11840
11899
 
11841
11900
  const rows = await ctx
11842
- .map('companies_v1', companies)
11843
- .step('cto', (row, ctx) => ctx.tools.execute({
11901
+ .dataset('companies_v1', companies)
11902
+ .withColumn('cto', (row, ctx) => ctx.tools.execute({
11844
11903
  id: 'find_cto',
11845
11904
  tool: 'apollo_search_people_with_match',
11846
11905
  input: { q_organization_domains_list: [row.domain], per_page: 1 },
11847
11906
  }))
11848
11907
  .run({ key: 'domain' });
11849
11908
 
11850
- Reuse needs the same play, tool id, map name, row key, and compatible logic.
11851
- To refresh, change the id/map key or set staleAfterSeconds:
11909
+ Reuse needs the same play, tool id, dataset name, row key, and compatible logic.
11910
+ To refresh, change the id/dataset key or set staleAfterSeconds:
11852
11911
 
11853
11912
  .run({ key: 'domain', staleAfterSeconds: 86400 })
11854
11913
 
@@ -14003,11 +14062,11 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
14003
14062
  const rows = (list?.get() ?? []).slice(0, 100);
14004
14063
  // ${sampleRows}
14005
14064
  // columns: ${columns}
14006
- // .step('email_waterfall', (row, rowCtx) => rowCtx.runPlay('name_domain_email', 'name-and-domain-to-email', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), domain: String(row.domain ?? '') }, { description: 'Resolve email.' }))
14007
- // .step('phone_waterfall', (row, rowCtx) => rowCtx.runPlay('contact_phone', 'contact-to-phone', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), email: String(row.email ?? '') }, { description: 'Resolve phone.' }))
14008
- // ctx.map is idempotent by map key + row key; reruns reuse completed rows.
14065
+ // .withColumn('email_waterfall', (row, rowCtx) => rowCtx.runPlay('name_domain_email', 'name-and-domain-to-email', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), domain: String(row.domain ?? '') }, { description: 'Resolve email.' }))
14066
+ // .withColumn('phone_waterfall', (row, rowCtx) => rowCtx.runPlay('contact_phone', 'contact-to-phone', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), email: String(row.email ?? '') }, { description: 'Resolve phone.' }))
14067
+ // ctx.dataset is idempotent by dataset key + row key; reruns reuse completed rows.
14009
14068
  const enrichedData = await ctx
14010
- .map('enriched_data', rows)
14069
+ .dataset('enriched_data', rows)
14011
14070
  .run({
14012
14071
  key: ${rowKey},
14013
14072
  description: 'Enrich seeded rows.',
@@ -14821,8 +14880,8 @@ async function runPlayRunnerHealthCheck() {
14821
14880
  "",
14822
14881
  "export default definePlay('health-check', async (ctx) => {",
14823
14882
  " const rows = await ctx",
14824
- " .map('health_rows', [{ id: 'a' }, { id: 'b' }])",
14825
- " .step('echo', (row) => ({ ok: true, id: row.id }))",
14883
+ " .dataset('health_rows', [{ id: 'a' }, { id: 'b' }])",
14884
+ " .withColumn('echo', (row) => ({ ok: true, id: row.id }))",
14826
14885
  " .run({ key: 'id' });",
14827
14886
  " return { ok: true, rows, source: 'deepline health --play-runner' };",
14828
14887
  "});",
@@ -206,10 +206,10 @@ import { join as join2 } from "path";
206
206
 
207
207
  // src/release.ts
208
208
  var SDK_RELEASE = {
209
- version: "0.1.70",
210
- apiContract: "2026-05-play-bootstrap-dataset-summary",
209
+ version: "0.1.71",
210
+ apiContract: "2026-06-dataset-column-syntax-cutover",
211
211
  supportPolicy: {
212
- latest: "0.1.70",
212
+ latest: "0.1.71",
213
213
  minimumSupported: "0.1.53",
214
214
  deprecatedBelow: "0.1.53"
215
215
  }
@@ -6888,7 +6888,7 @@ function playInspectionComments(playRef, indent) {
6888
6888
  function accessorExpression(base, field) {
6889
6889
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field) ? `${base}.${field}` : `${base}[${jsString(field)}]`;
6890
6890
  }
6891
- function needsWhenImport(options) {
6891
+ function needsRunIfImport(options) {
6892
6892
  return stageProviders(options.email).length > 1 || stageProviders(options.phone).length > 1;
6893
6893
  }
6894
6894
  function sourceCollectionTypeName(entity) {
@@ -7190,7 +7190,7 @@ function generateFinderPlayStep(input2) {
7190
7190
  " ",
7191
7191
  input2.stage.value
7192
7192
  );
7193
- return `.step(${jsString(outputField)}, async (row, rowCtx) => {
7193
+ return `.withColumn(${jsString(outputField)}, async (row, rowCtx) => {
7194
7194
  const ${input2.aggregateStepName}Input = ${payload};
7195
7195
  throw new Error(${jsString(`TODO: map ${input2.aggregateStepName}Input for ${input2.stage.value}, then delete this throw.`)});
7196
7196
  const ${input2.aggregateStepName}Result = await rowCtx.runPlay<${resultTypeName}>(
@@ -7236,12 +7236,12 @@ function generateFinderProviderStep(input2) {
7236
7236
  const stepName = input2.stepNames[input2.index];
7237
7237
  switch (input2.index) {
7238
7238
  case 0:
7239
- return `.step(${jsString(stepName)}, ${input2.resolver})`;
7239
+ return `.withColumn(${jsString(stepName)}, ${input2.resolver})`;
7240
7240
  default: {
7241
7241
  const priorCandidates = input2.stepNames.slice(0, input2.index).map((name) => `row.${name}`).join(", ");
7242
- return `.step(
7242
+ return `.withColumn(
7243
7243
  ${jsString(stepName)},
7244
- when(
7244
+ runIf(
7245
7245
  (row) => ![${priorCandidates}].some((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)}),
7246
7246
  ${input2.resolver},
7247
7247
  ),
@@ -7275,9 +7275,9 @@ function generateFinderProviderWaterfall(input2) {
7275
7275
  );
7276
7276
  const candidateNames = stepNames.map((name) => `row.${name}`).join(", ");
7277
7277
  return `// ${input2.aggregateStepName} provider waterfall. Each provider leg is active once its TODO throw is removed;
7278
- // delete or comment out legs you do not want before running. Later legs are gated with when(...).
7278
+ // delete or comment out legs you do not want before running. Later legs are gated with runIf(...).
7279
7279
  ${providerSteps.join("\n ")}
7280
- .step(${jsString(input2.aggregateStepName)}, (row) => {
7280
+ .withColumn(${jsString(input2.aggregateStepName)}, (row) => {
7281
7281
  const candidates = [${candidateNames}];
7282
7282
  const match = candidates.find((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)});
7283
7283
  return ${optionalFinderValueExpression("match", input2.outputField)} ?? null;
@@ -7332,7 +7332,7 @@ function generateBootstrapPlaySource(input2) {
7332
7332
  rowTypeDefinitions,
7333
7333
  finderPlayResultTypes
7334
7334
  ].filter((definition) => definition.trim().length > 0).join("\n\n");
7335
- const importNames = needsWhenImport(input2.options) ? "definePlay, when" : "definePlay";
7335
+ const importNames = needsRunIfImport(input2.options) ? "definePlay, runIf" : "definePlay";
7336
7336
  return `import { ${importNames} } from 'deepline';
7337
7337
 
7338
7338
  ${typeDefinitions}
@@ -7351,7 +7351,7 @@ export default definePlay(${jsString(input2.options.name)}, async (ctx, input: I
7351
7351
  }
7352
7352
 
7353
7353
  const rows = await ctx
7354
- .map('bootstrap_rows', rowsToProcess)${mapSteps}
7354
+ .dataset('bootstrap_rows', rowsToProcess)${mapSteps}
7355
7355
  .run({
7356
7356
  key: (_row, index) => index,
7357
7357
  description: ${jsString(`Bootstrap ${input2.options.template}: seed source rows, run requested stages, then return the mapped rows.`)},
@@ -7861,7 +7861,7 @@ function normalizePlayNameForSheet(value) {
7861
7861
  function normalizeTableNamespace(value) {
7862
7862
  return validateIdentifierPart(
7863
7863
  value,
7864
- "ctx.map() key",
7864
+ "ctx.dataset() key",
7865
7865
  MAP_KEY_NAMESPACE_MAX_LENGTH
7866
7866
  );
7867
7867
  }
@@ -7871,7 +7871,7 @@ function validatePlaySheetTableName(playName, tableNamespace) {
7871
7871
  const resolved = `${playSegment}_${keySegment}`;
7872
7872
  if (resolved.length > POSTGRES_IDENTIFIER_MAX_LENGTH) {
7873
7873
  throw new Error(
7874
- `Play sheet table name is too long after normalization (${resolved.length}/63). Shorten the play name or ctx.map() key. Resolved table name: "${resolved}".`
7874
+ `Play sheet table name is too long after normalization (${resolved.length}/63). Shorten the play name or ctx.dataset() key. Resolved table name: "${resolved}".`
7875
7875
  );
7876
7876
  }
7877
7877
  return resolved;
@@ -10114,10 +10114,66 @@ function formatActiveRunConflictError(input2) {
10114
10114
  lines.push(` rerun: add --force to the same deepline plays run command`);
10115
10115
  return lines.join("\n");
10116
10116
  }
10117
+ var PLAY_SYNTAX_MIGRATION_ERROR_MARKERS = [
10118
+ "ctx.map(...) has been replaced by ctx.dataset(...)",
10119
+ "Dataset .step(...) has been replaced by .withColumn(...)"
10120
+ ];
10121
+ function stringArrayField(record, key) {
10122
+ const value = record[key];
10123
+ if (!Array.isArray(value)) return [];
10124
+ return value.filter((entry) => typeof entry === "string");
10125
+ }
10126
+ function extractPlayValidationErrors(value) {
10127
+ if (value instanceof DeeplineError) {
10128
+ return extractPlayValidationErrors(value.details);
10129
+ }
10130
+ if (!isRecord4(value)) {
10131
+ return [];
10132
+ }
10133
+ const directErrors = stringArrayField(value, "errors");
10134
+ if (directErrors.length > 0) {
10135
+ return directErrors;
10136
+ }
10137
+ for (const key of ["response", "error", "details"]) {
10138
+ const nestedErrors = extractPlayValidationErrors(value[key]);
10139
+ if (nestedErrors.length > 0) {
10140
+ return nestedErrors;
10141
+ }
10142
+ }
10143
+ return [];
10144
+ }
10145
+ function orderPlayValidationErrors(errors) {
10146
+ const uniqueErrors = [...new Set(errors)];
10147
+ const migrationErrors = uniqueErrors.filter(
10148
+ (error) => PLAY_SYNTAX_MIGRATION_ERROR_MARKERS.some(
10149
+ (marker) => error.includes(marker)
10150
+ )
10151
+ );
10152
+ const remainingErrors = uniqueErrors.filter(
10153
+ (error) => !migrationErrors.includes(error)
10154
+ );
10155
+ return [...migrationErrors, ...remainingErrors];
10156
+ }
10157
+ function normalizePlayValidationError(error) {
10158
+ const validationErrors = extractPlayValidationErrors(error);
10159
+ if (validationErrors.length === 0) {
10160
+ return error;
10161
+ }
10162
+ const message = orderPlayValidationErrors(validationErrors).join("\n");
10163
+ if (!message.trim()) {
10164
+ return error;
10165
+ }
10166
+ return new DeeplineError(
10167
+ message,
10168
+ error instanceof DeeplineError ? error.statusCode : void 0,
10169
+ error instanceof DeeplineError ? error.code : "PLAY_VALIDATION_ERROR",
10170
+ error instanceof DeeplineError ? error.details : void 0
10171
+ );
10172
+ }
10117
10173
  function normalizePlayStartError(error, playName) {
10118
10174
  const activeRuns = extractActiveRunsFromError(error);
10119
10175
  if (activeRuns.length === 0) {
10120
- return error;
10176
+ return normalizePlayValidationError(error);
10121
10177
  }
10122
10178
  return new DeeplineError(
10123
10179
  formatActiveRunConflictError({ playName, activeRuns }),
@@ -10280,7 +10336,7 @@ function writeStartedPlayRun(input2) {
10280
10336
  );
10281
10337
  }
10282
10338
  function parsePlayRunOptions(args) {
10283
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
10339
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.dataset guidance.";
10284
10340
  let filePath = null;
10285
10341
  let playName = null;
10286
10342
  let input2 = null;
@@ -10643,7 +10699,10 @@ async function handleFileBackedRun(options) {
10643
10699
  progress.phase("compiled play");
10644
10700
  } catch (error) {
10645
10701
  progress.fail();
10646
- console.error(error instanceof Error ? error.message : String(error));
10702
+ const normalizedError = normalizePlayValidationError(error);
10703
+ console.error(
10704
+ normalizedError instanceof Error ? normalizedError.message : String(normalizedError)
10705
+ );
10647
10706
  return 1;
10648
10707
  }
10649
10708
  const bundleResult = graph.root;
@@ -11786,7 +11845,7 @@ function registerPlayCommands(program) {
11786
11845
  Concepts:
11787
11846
  Plays are durable cloud workflows.
11788
11847
  Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
11789
- ctx.map adds row keys and row progress.
11848
+ ctx.dataset adds row keys and row progress.
11790
11849
  Named play runs use the live revision unless --latest or --revision-id is set.
11791
11850
  Running a local file does not make it live; use set-live/publish explicitly.
11792
11851
 
@@ -11839,19 +11898,19 @@ Idempotent execution:
11839
11898
 
11840
11899
  await ctx.tools.execute({ id: 'company_lookup', tool, input });
11841
11900
 
11842
- For rows, use ctx.map plus a stable row key:
11901
+ For rows, use ctx.dataset plus a stable row key:
11843
11902
 
11844
11903
  const rows = await ctx
11845
- .map('companies_v1', companies)
11846
- .step('cto', (row, ctx) => ctx.tools.execute({
11904
+ .dataset('companies_v1', companies)
11905
+ .withColumn('cto', (row, ctx) => ctx.tools.execute({
11847
11906
  id: 'find_cto',
11848
11907
  tool: 'apollo_search_people_with_match',
11849
11908
  input: { q_organization_domains_list: [row.domain], per_page: 1 },
11850
11909
  }))
11851
11910
  .run({ key: 'domain' });
11852
11911
 
11853
- Reuse needs the same play, tool id, map name, row key, and compatible logic.
11854
- To refresh, change the id/map key or set staleAfterSeconds:
11912
+ Reuse needs the same play, tool id, dataset name, row key, and compatible logic.
11913
+ To refresh, change the id/dataset key or set staleAfterSeconds:
11855
11914
 
11856
11915
  .run({ key: 'domain', staleAfterSeconds: 86400 })
11857
11916
 
@@ -14006,11 +14065,11 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
14006
14065
  const rows = (list?.get() ?? []).slice(0, 100);
14007
14066
  // ${sampleRows}
14008
14067
  // columns: ${columns}
14009
- // .step('email_waterfall', (row, rowCtx) => rowCtx.runPlay('name_domain_email', 'name-and-domain-to-email', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), domain: String(row.domain ?? '') }, { description: 'Resolve email.' }))
14010
- // .step('phone_waterfall', (row, rowCtx) => rowCtx.runPlay('contact_phone', 'contact-to-phone', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), email: String(row.email ?? '') }, { description: 'Resolve phone.' }))
14011
- // ctx.map is idempotent by map key + row key; reruns reuse completed rows.
14068
+ // .withColumn('email_waterfall', (row, rowCtx) => rowCtx.runPlay('name_domain_email', 'name-and-domain-to-email', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), domain: String(row.domain ?? '') }, { description: 'Resolve email.' }))
14069
+ // .withColumn('phone_waterfall', (row, rowCtx) => rowCtx.runPlay('contact_phone', 'contact-to-phone', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), email: String(row.email ?? '') }, { description: 'Resolve phone.' }))
14070
+ // ctx.dataset is idempotent by dataset key + row key; reruns reuse completed rows.
14012
14071
  const enrichedData = await ctx
14013
- .map('enriched_data', rows)
14072
+ .dataset('enriched_data', rows)
14014
14073
  .run({
14015
14074
  key: ${rowKey},
14016
14075
  description: 'Enrich seeded rows.',
@@ -14831,8 +14890,8 @@ async function runPlayRunnerHealthCheck() {
14831
14890
  "",
14832
14891
  "export default definePlay('health-check', async (ctx) => {",
14833
14892
  " const rows = await ctx",
14834
- " .map('health_rows', [{ id: 'a' }, { id: 'b' }])",
14835
- " .step('echo', (row) => ({ ok: true, id: row.id }))",
14893
+ " .dataset('health_rows', [{ id: 'a' }, { id: 'b' }])",
14894
+ " .withColumn('echo', (row) => ({ ok: true, id: row.id }))",
14836
14895
  " .run({ key: 'id' });",
14837
14896
  " return { ok: true, rows, source: 'deepline health --play-runner' };",
14838
14897
  "});",