deepline 0.1.70 → 0.1.72

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.72",
233
+ apiContract: "2026-06-dataset-column-syntax-cutover",
234
234
  supportPolicy: {
235
- latest: "0.1.70",
235
+ latest: "0.1.72",
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;
@@ -10091,7 +10091,7 @@ function activeRunId(run) {
10091
10091
  }
10092
10092
  function formatActiveRunConflictError(input2) {
10093
10093
  const lines = [
10094
- `Active run exists for ${input2.playName}. Use --force to supersede, or inspect/stop the active run first.`
10094
+ `Active run exists for ${input2.playName}. Inspect or stop the active run first.`
10095
10095
  ];
10096
10096
  for (const run of input2.activeRuns.slice(0, 3)) {
10097
10097
  const runId = activeRunId(run);
@@ -10108,13 +10108,71 @@ function formatActiveRunConflictError(input2) {
10108
10108
  ` stop: deepline runs stop ${runId} --reason "stale lock" --json`
10109
10109
  );
10110
10110
  }
10111
- lines.push(` rerun: add --force to the same deepline plays run command`);
10111
+ lines.push(
10112
+ ` rerun: start another run with the same deepline plays run command`
10113
+ );
10112
10114
  return lines.join("\n");
10113
10115
  }
10116
+ var PLAY_SYNTAX_MIGRATION_ERROR_MARKERS = [
10117
+ "ctx.map(...) has been replaced by ctx.dataset(...)",
10118
+ "Dataset .step(...) has been replaced by .withColumn(...)"
10119
+ ];
10120
+ function stringArrayField(record, key) {
10121
+ const value = record[key];
10122
+ if (!Array.isArray(value)) return [];
10123
+ return value.filter((entry) => typeof entry === "string");
10124
+ }
10125
+ function extractPlayValidationErrors(value) {
10126
+ if (value instanceof DeeplineError) {
10127
+ return extractPlayValidationErrors(value.details);
10128
+ }
10129
+ if (!isRecord4(value)) {
10130
+ return [];
10131
+ }
10132
+ const directErrors = stringArrayField(value, "errors");
10133
+ if (directErrors.length > 0) {
10134
+ return directErrors;
10135
+ }
10136
+ for (const key of ["response", "error", "details"]) {
10137
+ const nestedErrors = extractPlayValidationErrors(value[key]);
10138
+ if (nestedErrors.length > 0) {
10139
+ return nestedErrors;
10140
+ }
10141
+ }
10142
+ return [];
10143
+ }
10144
+ function orderPlayValidationErrors(errors) {
10145
+ const uniqueErrors = [...new Set(errors)];
10146
+ const migrationErrors = uniqueErrors.filter(
10147
+ (error) => PLAY_SYNTAX_MIGRATION_ERROR_MARKERS.some(
10148
+ (marker) => error.includes(marker)
10149
+ )
10150
+ );
10151
+ const remainingErrors = uniqueErrors.filter(
10152
+ (error) => !migrationErrors.includes(error)
10153
+ );
10154
+ return [...migrationErrors, ...remainingErrors];
10155
+ }
10156
+ function normalizePlayValidationError(error) {
10157
+ const validationErrors = extractPlayValidationErrors(error);
10158
+ if (validationErrors.length === 0) {
10159
+ return error;
10160
+ }
10161
+ const message = orderPlayValidationErrors(validationErrors).join("\n");
10162
+ if (!message.trim()) {
10163
+ return error;
10164
+ }
10165
+ return new DeeplineError(
10166
+ message,
10167
+ error instanceof DeeplineError ? error.statusCode : void 0,
10168
+ error instanceof DeeplineError ? error.code : "PLAY_VALIDATION_ERROR",
10169
+ error instanceof DeeplineError ? error.details : void 0
10170
+ );
10171
+ }
10114
10172
  function normalizePlayStartError(error, playName) {
10115
10173
  const activeRuns = extractActiveRunsFromError(error);
10116
10174
  if (activeRuns.length === 0) {
10117
- return error;
10175
+ return normalizePlayValidationError(error);
10118
10176
  }
10119
10177
  return new DeeplineError(
10120
10178
  formatActiveRunConflictError({ playName, activeRuns }),
@@ -10277,7 +10335,7 @@ function writeStartedPlayRun(input2) {
10277
10335
  );
10278
10336
  }
10279
10337
  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.";
10338
+ 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
10339
  let filePath = null;
10282
10340
  let playName = null;
10283
10341
  let input2 = null;
@@ -10640,7 +10698,10 @@ async function handleFileBackedRun(options) {
10640
10698
  progress.phase("compiled play");
10641
10699
  } catch (error) {
10642
10700
  progress.fail();
10643
- console.error(error instanceof Error ? error.message : String(error));
10701
+ const normalizedError = normalizePlayValidationError(error);
10702
+ console.error(
10703
+ normalizedError instanceof Error ? normalizedError.message : String(normalizedError)
10704
+ );
10644
10705
  return 1;
10645
10706
  }
10646
10707
  const bundleResult = graph.root;
@@ -11783,7 +11844,7 @@ function registerPlayCommands(program) {
11783
11844
  Concepts:
11784
11845
  Plays are durable cloud workflows.
11785
11846
  Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
11786
- ctx.map adds row keys and row progress.
11847
+ ctx.dataset adds row keys and row progress.
11787
11848
  Named play runs use the live revision unless --latest or --revision-id is set.
11788
11849
  Running a local file does not make it live; use set-live/publish explicitly.
11789
11850
 
@@ -11828,7 +11889,9 @@ Notes:
11828
11889
  a fire-and-forget run id.
11829
11890
  The play page opens in your browser as soon as the run starts; use --no-open
11830
11891
  to only print the URL.
11831
- --force supersedes active runs; it does not bypass completed reuse.
11892
+ Concurrent runs for the same play are allowed. --force is accepted for
11893
+ compatibility, but it does not cancel active sibling runs or bypass completed
11894
+ reuse.
11832
11895
  This command starts cloud work and may spend Deepline credits through tool calls.
11833
11896
 
11834
11897
  Idempotent execution:
@@ -11836,19 +11899,19 @@ Idempotent execution:
11836
11899
 
11837
11900
  await ctx.tools.execute({ id: 'company_lookup', tool, input });
11838
11901
 
11839
- For rows, use ctx.map plus a stable row key:
11902
+ For rows, use ctx.dataset plus a stable row key:
11840
11903
 
11841
11904
  const rows = await ctx
11842
- .map('companies_v1', companies)
11843
- .step('cto', (row, ctx) => ctx.tools.execute({
11905
+ .dataset('companies_v1', companies)
11906
+ .withColumn('cto', (row, ctx) => ctx.tools.execute({
11844
11907
  id: 'find_cto',
11845
11908
  tool: 'apollo_search_people_with_match',
11846
11909
  input: { q_organization_domains_list: [row.domain], per_page: 1 },
11847
11910
  }))
11848
11911
  .run({ key: 'domain' });
11849
11912
 
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:
11913
+ Reuse needs the same play, tool id, dataset name, row key, and compatible logic.
11914
+ To refresh, change the id/dataset key or set staleAfterSeconds:
11852
11915
 
11853
11916
  .run({ key: 'domain', staleAfterSeconds: 86400 })
11854
11917
 
@@ -11867,7 +11930,7 @@ Examples:
11867
11930
  ).option("--watch", "Compatibility alias; run waits by default").option("--wait", "Compatibility alias; run waits by default").option("--no-wait", "Start the run and return immediately").option(
11868
11931
  "--logs",
11869
11932
  "When output is non-interactive, stream play logs to stderr while waiting"
11870
- ).option("--tail-timeout-ms <ms>", "Timeout while watching the run stream").option("--force", "Supersede any active runs for this play").option("--no-open", "Print the play page URL without opening a browser").option("--json", "Emit JSON output").option("--full", "Debug only: with --json, emit the raw status payload").addHelpText(
11933
+ ).option("--tail-timeout-ms <ms>", "Timeout while watching the run stream").option("--force", "Compatibility flag; active sibling runs are allowed").option("--no-open", "Print the play page URL without opening a browser").option("--json", "Emit JSON output").option("--full", "Debug only: with --json, emit the raw status payload").addHelpText(
11871
11934
  "afterAll",
11872
11935
  `
11873
11936
  Pass-through input flags:
@@ -14003,11 +14066,11 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
14003
14066
  const rows = (list?.get() ?? []).slice(0, 100);
14004
14067
  // ${sampleRows}
14005
14068
  // 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.
14069
+ // .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.' }))
14070
+ // .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.' }))
14071
+ // ctx.dataset is idempotent by dataset key + row key; reruns reuse completed rows.
14009
14072
  const enrichedData = await ctx
14010
- .map('enriched_data', rows)
14073
+ .dataset('enriched_data', rows)
14011
14074
  .run({
14012
14075
  key: ${rowKey},
14013
14076
  description: 'Enrich seeded rows.',
@@ -14821,8 +14884,8 @@ async function runPlayRunnerHealthCheck() {
14821
14884
  "",
14822
14885
  "export default definePlay('health-check', async (ctx) => {",
14823
14886
  " const rows = await ctx",
14824
- " .map('health_rows', [{ id: 'a' }, { id: 'b' }])",
14825
- " .step('echo', (row) => ({ ok: true, id: row.id }))",
14887
+ " .dataset('health_rows', [{ id: 'a' }, { id: 'b' }])",
14888
+ " .withColumn('echo', (row) => ({ ok: true, id: row.id }))",
14826
14889
  " .run({ key: 'id' });",
14827
14890
  " return { ok: true, rows, source: 'deepline health --play-runner' };",
14828
14891
  "});",
@@ -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.72",
210
+ apiContract: "2026-06-dataset-column-syntax-cutover",
211
211
  supportPolicy: {
212
- latest: "0.1.70",
212
+ latest: "0.1.72",
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;
@@ -10094,7 +10094,7 @@ function activeRunId(run) {
10094
10094
  }
10095
10095
  function formatActiveRunConflictError(input2) {
10096
10096
  const lines = [
10097
- `Active run exists for ${input2.playName}. Use --force to supersede, or inspect/stop the active run first.`
10097
+ `Active run exists for ${input2.playName}. Inspect or stop the active run first.`
10098
10098
  ];
10099
10099
  for (const run of input2.activeRuns.slice(0, 3)) {
10100
10100
  const runId = activeRunId(run);
@@ -10111,13 +10111,71 @@ function formatActiveRunConflictError(input2) {
10111
10111
  ` stop: deepline runs stop ${runId} --reason "stale lock" --json`
10112
10112
  );
10113
10113
  }
10114
- lines.push(` rerun: add --force to the same deepline plays run command`);
10114
+ lines.push(
10115
+ ` rerun: start another run with the same deepline plays run command`
10116
+ );
10115
10117
  return lines.join("\n");
10116
10118
  }
10119
+ var PLAY_SYNTAX_MIGRATION_ERROR_MARKERS = [
10120
+ "ctx.map(...) has been replaced by ctx.dataset(...)",
10121
+ "Dataset .step(...) has been replaced by .withColumn(...)"
10122
+ ];
10123
+ function stringArrayField(record, key) {
10124
+ const value = record[key];
10125
+ if (!Array.isArray(value)) return [];
10126
+ return value.filter((entry) => typeof entry === "string");
10127
+ }
10128
+ function extractPlayValidationErrors(value) {
10129
+ if (value instanceof DeeplineError) {
10130
+ return extractPlayValidationErrors(value.details);
10131
+ }
10132
+ if (!isRecord4(value)) {
10133
+ return [];
10134
+ }
10135
+ const directErrors = stringArrayField(value, "errors");
10136
+ if (directErrors.length > 0) {
10137
+ return directErrors;
10138
+ }
10139
+ for (const key of ["response", "error", "details"]) {
10140
+ const nestedErrors = extractPlayValidationErrors(value[key]);
10141
+ if (nestedErrors.length > 0) {
10142
+ return nestedErrors;
10143
+ }
10144
+ }
10145
+ return [];
10146
+ }
10147
+ function orderPlayValidationErrors(errors) {
10148
+ const uniqueErrors = [...new Set(errors)];
10149
+ const migrationErrors = uniqueErrors.filter(
10150
+ (error) => PLAY_SYNTAX_MIGRATION_ERROR_MARKERS.some(
10151
+ (marker) => error.includes(marker)
10152
+ )
10153
+ );
10154
+ const remainingErrors = uniqueErrors.filter(
10155
+ (error) => !migrationErrors.includes(error)
10156
+ );
10157
+ return [...migrationErrors, ...remainingErrors];
10158
+ }
10159
+ function normalizePlayValidationError(error) {
10160
+ const validationErrors = extractPlayValidationErrors(error);
10161
+ if (validationErrors.length === 0) {
10162
+ return error;
10163
+ }
10164
+ const message = orderPlayValidationErrors(validationErrors).join("\n");
10165
+ if (!message.trim()) {
10166
+ return error;
10167
+ }
10168
+ return new DeeplineError(
10169
+ message,
10170
+ error instanceof DeeplineError ? error.statusCode : void 0,
10171
+ error instanceof DeeplineError ? error.code : "PLAY_VALIDATION_ERROR",
10172
+ error instanceof DeeplineError ? error.details : void 0
10173
+ );
10174
+ }
10117
10175
  function normalizePlayStartError(error, playName) {
10118
10176
  const activeRuns = extractActiveRunsFromError(error);
10119
10177
  if (activeRuns.length === 0) {
10120
- return error;
10178
+ return normalizePlayValidationError(error);
10121
10179
  }
10122
10180
  return new DeeplineError(
10123
10181
  formatActiveRunConflictError({ playName, activeRuns }),
@@ -10280,7 +10338,7 @@ function writeStartedPlayRun(input2) {
10280
10338
  );
10281
10339
  }
10282
10340
  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.";
10341
+ 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
10342
  let filePath = null;
10285
10343
  let playName = null;
10286
10344
  let input2 = null;
@@ -10643,7 +10701,10 @@ async function handleFileBackedRun(options) {
10643
10701
  progress.phase("compiled play");
10644
10702
  } catch (error) {
10645
10703
  progress.fail();
10646
- console.error(error instanceof Error ? error.message : String(error));
10704
+ const normalizedError = normalizePlayValidationError(error);
10705
+ console.error(
10706
+ normalizedError instanceof Error ? normalizedError.message : String(normalizedError)
10707
+ );
10647
10708
  return 1;
10648
10709
  }
10649
10710
  const bundleResult = graph.root;
@@ -11786,7 +11847,7 @@ function registerPlayCommands(program) {
11786
11847
  Concepts:
11787
11848
  Plays are durable cloud workflows.
11788
11849
  Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
11789
- ctx.map adds row keys and row progress.
11850
+ ctx.dataset adds row keys and row progress.
11790
11851
  Named play runs use the live revision unless --latest or --revision-id is set.
11791
11852
  Running a local file does not make it live; use set-live/publish explicitly.
11792
11853
 
@@ -11831,7 +11892,9 @@ Notes:
11831
11892
  a fire-and-forget run id.
11832
11893
  The play page opens in your browser as soon as the run starts; use --no-open
11833
11894
  to only print the URL.
11834
- --force supersedes active runs; it does not bypass completed reuse.
11895
+ Concurrent runs for the same play are allowed. --force is accepted for
11896
+ compatibility, but it does not cancel active sibling runs or bypass completed
11897
+ reuse.
11835
11898
  This command starts cloud work and may spend Deepline credits through tool calls.
11836
11899
 
11837
11900
  Idempotent execution:
@@ -11839,19 +11902,19 @@ Idempotent execution:
11839
11902
 
11840
11903
  await ctx.tools.execute({ id: 'company_lookup', tool, input });
11841
11904
 
11842
- For rows, use ctx.map plus a stable row key:
11905
+ For rows, use ctx.dataset plus a stable row key:
11843
11906
 
11844
11907
  const rows = await ctx
11845
- .map('companies_v1', companies)
11846
- .step('cto', (row, ctx) => ctx.tools.execute({
11908
+ .dataset('companies_v1', companies)
11909
+ .withColumn('cto', (row, ctx) => ctx.tools.execute({
11847
11910
  id: 'find_cto',
11848
11911
  tool: 'apollo_search_people_with_match',
11849
11912
  input: { q_organization_domains_list: [row.domain], per_page: 1 },
11850
11913
  }))
11851
11914
  .run({ key: 'domain' });
11852
11915
 
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:
11916
+ Reuse needs the same play, tool id, dataset name, row key, and compatible logic.
11917
+ To refresh, change the id/dataset key or set staleAfterSeconds:
11855
11918
 
11856
11919
  .run({ key: 'domain', staleAfterSeconds: 86400 })
11857
11920
 
@@ -11870,7 +11933,7 @@ Examples:
11870
11933
  ).option("--watch", "Compatibility alias; run waits by default").option("--wait", "Compatibility alias; run waits by default").option("--no-wait", "Start the run and return immediately").option(
11871
11934
  "--logs",
11872
11935
  "When output is non-interactive, stream play logs to stderr while waiting"
11873
- ).option("--tail-timeout-ms <ms>", "Timeout while watching the run stream").option("--force", "Supersede any active runs for this play").option("--no-open", "Print the play page URL without opening a browser").option("--json", "Emit JSON output").option("--full", "Debug only: with --json, emit the raw status payload").addHelpText(
11936
+ ).option("--tail-timeout-ms <ms>", "Timeout while watching the run stream").option("--force", "Compatibility flag; active sibling runs are allowed").option("--no-open", "Print the play page URL without opening a browser").option("--json", "Emit JSON output").option("--full", "Debug only: with --json, emit the raw status payload").addHelpText(
11874
11937
  "afterAll",
11875
11938
  `
11876
11939
  Pass-through input flags:
@@ -14006,11 +14069,11 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
14006
14069
  const rows = (list?.get() ?? []).slice(0, 100);
14007
14070
  // ${sampleRows}
14008
14071
  // 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.
14072
+ // .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.' }))
14073
+ // .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.' }))
14074
+ // ctx.dataset is idempotent by dataset key + row key; reruns reuse completed rows.
14012
14075
  const enrichedData = await ctx
14013
- .map('enriched_data', rows)
14076
+ .dataset('enriched_data', rows)
14014
14077
  .run({
14015
14078
  key: ${rowKey},
14016
14079
  description: 'Enrich seeded rows.',
@@ -14831,8 +14894,8 @@ async function runPlayRunnerHealthCheck() {
14831
14894
  "",
14832
14895
  "export default definePlay('health-check', async (ctx) => {",
14833
14896
  " const rows = await ctx",
14834
- " .map('health_rows', [{ id: 'a' }, { id: 'b' }])",
14835
- " .step('echo', (row) => ({ ok: true, id: row.id }))",
14897
+ " .dataset('health_rows', [{ id: 'a' }, { id: 'b' }])",
14898
+ " .withColumn('echo', (row) => ({ ok: true, id: row.id }))",
14836
14899
  " .run({ key: 'id' });",
14837
14900
  " return { ok: true, rows, source: 'deepline health --play-runner' };",
14838
14901
  "});",