deepline 0.1.69 → 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.69",
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.69",
235
+ latest: "0.1.71",
236
236
  minimumSupported: "0.1.53",
237
237
  deprecatedBelow: "0.1.53"
238
238
  }
@@ -1037,6 +1037,7 @@ var DeeplineClient = class {
1037
1037
  ...request.inputFile ? { inputFile: request.inputFile } : {},
1038
1038
  ...request.packagedFiles?.length ? { packagedFiles: request.packagedFiles } : {},
1039
1039
  ...request.force ? { force: true } : {},
1040
+ ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
1040
1041
  ...request.profile ? { profile: request.profile } : {}
1041
1042
  };
1042
1043
  for await (const event of this.http.streamSse(
@@ -6884,7 +6885,7 @@ function playInspectionComments(playRef, indent) {
6884
6885
  function accessorExpression(base, field) {
6885
6886
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field) ? `${base}.${field}` : `${base}[${jsString(field)}]`;
6886
6887
  }
6887
- function needsWhenImport(options) {
6888
+ function needsRunIfImport(options) {
6888
6889
  return stageProviders(options.email).length > 1 || stageProviders(options.phone).length > 1;
6889
6890
  }
6890
6891
  function sourceCollectionTypeName(entity) {
@@ -7186,7 +7187,7 @@ function generateFinderPlayStep(input2) {
7186
7187
  " ",
7187
7188
  input2.stage.value
7188
7189
  );
7189
- return `.step(${jsString(outputField)}, async (row, rowCtx) => {
7190
+ return `.withColumn(${jsString(outputField)}, async (row, rowCtx) => {
7190
7191
  const ${input2.aggregateStepName}Input = ${payload};
7191
7192
  throw new Error(${jsString(`TODO: map ${input2.aggregateStepName}Input for ${input2.stage.value}, then delete this throw.`)});
7192
7193
  const ${input2.aggregateStepName}Result = await rowCtx.runPlay<${resultTypeName}>(
@@ -7232,12 +7233,12 @@ function generateFinderProviderStep(input2) {
7232
7233
  const stepName = input2.stepNames[input2.index];
7233
7234
  switch (input2.index) {
7234
7235
  case 0:
7235
- return `.step(${jsString(stepName)}, ${input2.resolver})`;
7236
+ return `.withColumn(${jsString(stepName)}, ${input2.resolver})`;
7236
7237
  default: {
7237
7238
  const priorCandidates = input2.stepNames.slice(0, input2.index).map((name) => `row.${name}`).join(", ");
7238
- return `.step(
7239
+ return `.withColumn(
7239
7240
  ${jsString(stepName)},
7240
- when(
7241
+ runIf(
7241
7242
  (row) => ![${priorCandidates}].some((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)}),
7242
7243
  ${input2.resolver},
7243
7244
  ),
@@ -7271,9 +7272,9 @@ function generateFinderProviderWaterfall(input2) {
7271
7272
  );
7272
7273
  const candidateNames = stepNames.map((name) => `row.${name}`).join(", ");
7273
7274
  return `// ${input2.aggregateStepName} provider waterfall. Each provider leg is active once its TODO throw is removed;
7274
- // 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(...).
7275
7276
  ${providerSteps.join("\n ")}
7276
- .step(${jsString(input2.aggregateStepName)}, (row) => {
7277
+ .withColumn(${jsString(input2.aggregateStepName)}, (row) => {
7277
7278
  const candidates = [${candidateNames}];
7278
7279
  const match = candidates.find((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)});
7279
7280
  return ${optionalFinderValueExpression("match", input2.outputField)} ?? null;
@@ -7328,7 +7329,7 @@ function generateBootstrapPlaySource(input2) {
7328
7329
  rowTypeDefinitions,
7329
7330
  finderPlayResultTypes
7330
7331
  ].filter((definition) => definition.trim().length > 0).join("\n\n");
7331
- const importNames = needsWhenImport(input2.options) ? "definePlay, when" : "definePlay";
7332
+ const importNames = needsRunIfImport(input2.options) ? "definePlay, runIf" : "definePlay";
7332
7333
  return `import { ${importNames} } from 'deepline';
7333
7334
 
7334
7335
  ${typeDefinitions}
@@ -7347,7 +7348,7 @@ export default definePlay(${jsString(input2.options.name)}, async (ctx, input: I
7347
7348
  }
7348
7349
 
7349
7350
  const rows = await ctx
7350
- .map('bootstrap_rows', rowsToProcess)${mapSteps}
7351
+ .dataset('bootstrap_rows', rowsToProcess)${mapSteps}
7351
7352
  .run({
7352
7353
  key: (_row, index) => index,
7353
7354
  description: ${jsString(`Bootstrap ${input2.options.template}: seed source rows, run requested stages, then return the mapped rows.`)},
@@ -7857,7 +7858,7 @@ function normalizePlayNameForSheet(value) {
7857
7858
  function normalizeTableNamespace(value) {
7858
7859
  return validateIdentifierPart(
7859
7860
  value,
7860
- "ctx.map() key",
7861
+ "ctx.dataset() key",
7861
7862
  MAP_KEY_NAMESPACE_MAX_LENGTH
7862
7863
  );
7863
7864
  }
@@ -7867,7 +7868,7 @@ function validatePlaySheetTableName(playName, tableNamespace) {
7867
7868
  const resolved = `${playSegment}_${keySegment}`;
7868
7869
  if (resolved.length > POSTGRES_IDENTIFIER_MAX_LENGTH) {
7869
7870
  throw new Error(
7870
- `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}".`
7871
7872
  );
7872
7873
  }
7873
7874
  return resolved;
@@ -7947,6 +7948,7 @@ ${EXTRACTED_GETTER_ERROR_HINT}`;
7947
7948
  }
7948
7949
 
7949
7950
  // src/cli/commands/play.ts
7951
+ var PLAY_START_STREAM_FAST_COMPLETION_WAIT_MS = 2500;
7950
7952
  var PLAY_RUN_RESERVED_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
7951
7953
  "--json",
7952
7954
  "--wait",
@@ -8889,6 +8891,10 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8889
8891
  let eventCount = 0;
8890
8892
  let firstRunIdMs = null;
8891
8893
  let lastPhase = null;
8894
+ const startRequest = {
8895
+ ...input2.request,
8896
+ waitForCompletionMs: typeof input2.request.waitForCompletionMs === "number" ? input2.request.waitForCompletionMs : PLAY_START_STREAM_FAST_COMPLETION_WAIT_MS
8897
+ };
8892
8898
  const timeout = input2.waitTimeoutMs === null ? null : setTimeout(
8893
8899
  () => {
8894
8900
  timedOut = true;
@@ -8897,7 +8903,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8897
8903
  Math.max(1, input2.waitTimeoutMs)
8898
8904
  );
8899
8905
  try {
8900
- for await (const event of input2.client.startPlayRunStream(input2.request, {
8906
+ for await (const event of input2.client.startPlayRunStream(startRequest, {
8901
8907
  signal: controller.signal
8902
8908
  })) {
8903
8909
  eventCount += 1;
@@ -10105,10 +10111,66 @@ function formatActiveRunConflictError(input2) {
10105
10111
  lines.push(` rerun: add --force to the same deepline plays run command`);
10106
10112
  return lines.join("\n");
10107
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
+ }
10108
10170
  function normalizePlayStartError(error, playName) {
10109
10171
  const activeRuns = extractActiveRunsFromError(error);
10110
10172
  if (activeRuns.length === 0) {
10111
- return error;
10173
+ return normalizePlayValidationError(error);
10112
10174
  }
10113
10175
  return new DeeplineError(
10114
10176
  formatActiveRunConflictError({ playName, activeRuns }),
@@ -10271,7 +10333,7 @@ function writeStartedPlayRun(input2) {
10271
10333
  );
10272
10334
  }
10273
10335
  function parsePlayRunOptions(args) {
10274
- 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.";
10275
10337
  let filePath = null;
10276
10338
  let playName = null;
10277
10339
  let input2 = null;
@@ -10634,7 +10696,10 @@ async function handleFileBackedRun(options) {
10634
10696
  progress.phase("compiled play");
10635
10697
  } catch (error) {
10636
10698
  progress.fail();
10637
- 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
+ );
10638
10703
  return 1;
10639
10704
  }
10640
10705
  const bundleResult = graph.root;
@@ -11777,7 +11842,7 @@ function registerPlayCommands(program) {
11777
11842
  Concepts:
11778
11843
  Plays are durable cloud workflows.
11779
11844
  Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
11780
- ctx.map adds row keys and row progress.
11845
+ ctx.dataset adds row keys and row progress.
11781
11846
  Named play runs use the live revision unless --latest or --revision-id is set.
11782
11847
  Running a local file does not make it live; use set-live/publish explicitly.
11783
11848
 
@@ -11830,19 +11895,19 @@ Idempotent execution:
11830
11895
 
11831
11896
  await ctx.tools.execute({ id: 'company_lookup', tool, input });
11832
11897
 
11833
- For rows, use ctx.map plus a stable row key:
11898
+ For rows, use ctx.dataset plus a stable row key:
11834
11899
 
11835
11900
  const rows = await ctx
11836
- .map('companies_v1', companies)
11837
- .step('cto', (row, ctx) => ctx.tools.execute({
11901
+ .dataset('companies_v1', companies)
11902
+ .withColumn('cto', (row, ctx) => ctx.tools.execute({
11838
11903
  id: 'find_cto',
11839
11904
  tool: 'apollo_search_people_with_match',
11840
11905
  input: { q_organization_domains_list: [row.domain], per_page: 1 },
11841
11906
  }))
11842
11907
  .run({ key: 'domain' });
11843
11908
 
11844
- Reuse needs the same play, tool id, map name, row key, and compatible logic.
11845
- 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:
11846
11911
 
11847
11912
  .run({ key: 'domain', staleAfterSeconds: 86400 })
11848
11913
 
@@ -13997,11 +14062,11 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
13997
14062
  const rows = (list?.get() ?? []).slice(0, 100);
13998
14063
  // ${sampleRows}
13999
14064
  // columns: ${columns}
14000
- // .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.' }))
14001
- // .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.' }))
14002
- // 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.
14003
14068
  const enrichedData = await ctx
14004
- .map('enriched_data', rows)
14069
+ .dataset('enriched_data', rows)
14005
14070
  .run({
14006
14071
  key: ${rowKey},
14007
14072
  description: 'Enrich seeded rows.',
@@ -14815,8 +14880,8 @@ async function runPlayRunnerHealthCheck() {
14815
14880
  "",
14816
14881
  "export default definePlay('health-check', async (ctx) => {",
14817
14882
  " const rows = await ctx",
14818
- " .map('health_rows', [{ id: 'a' }, { id: 'b' }])",
14819
- " .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 }))",
14820
14885
  " .run({ key: 'id' });",
14821
14886
  " return { ok: true, rows, source: 'deepline health --play-runner' };",
14822
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.69",
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.69",
212
+ latest: "0.1.71",
213
213
  minimumSupported: "0.1.53",
214
214
  deprecatedBelow: "0.1.53"
215
215
  }
@@ -1014,6 +1014,7 @@ var DeeplineClient = class {
1014
1014
  ...request.inputFile ? { inputFile: request.inputFile } : {},
1015
1015
  ...request.packagedFiles?.length ? { packagedFiles: request.packagedFiles } : {},
1016
1016
  ...request.force ? { force: true } : {},
1017
+ ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
1017
1018
  ...request.profile ? { profile: request.profile } : {}
1018
1019
  };
1019
1020
  for await (const event of this.http.streamSse(
@@ -6887,7 +6888,7 @@ function playInspectionComments(playRef, indent) {
6887
6888
  function accessorExpression(base, field) {
6888
6889
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field) ? `${base}.${field}` : `${base}[${jsString(field)}]`;
6889
6890
  }
6890
- function needsWhenImport(options) {
6891
+ function needsRunIfImport(options) {
6891
6892
  return stageProviders(options.email).length > 1 || stageProviders(options.phone).length > 1;
6892
6893
  }
6893
6894
  function sourceCollectionTypeName(entity) {
@@ -7189,7 +7190,7 @@ function generateFinderPlayStep(input2) {
7189
7190
  " ",
7190
7191
  input2.stage.value
7191
7192
  );
7192
- return `.step(${jsString(outputField)}, async (row, rowCtx) => {
7193
+ return `.withColumn(${jsString(outputField)}, async (row, rowCtx) => {
7193
7194
  const ${input2.aggregateStepName}Input = ${payload};
7194
7195
  throw new Error(${jsString(`TODO: map ${input2.aggregateStepName}Input for ${input2.stage.value}, then delete this throw.`)});
7195
7196
  const ${input2.aggregateStepName}Result = await rowCtx.runPlay<${resultTypeName}>(
@@ -7235,12 +7236,12 @@ function generateFinderProviderStep(input2) {
7235
7236
  const stepName = input2.stepNames[input2.index];
7236
7237
  switch (input2.index) {
7237
7238
  case 0:
7238
- return `.step(${jsString(stepName)}, ${input2.resolver})`;
7239
+ return `.withColumn(${jsString(stepName)}, ${input2.resolver})`;
7239
7240
  default: {
7240
7241
  const priorCandidates = input2.stepNames.slice(0, input2.index).map((name) => `row.${name}`).join(", ");
7241
- return `.step(
7242
+ return `.withColumn(
7242
7243
  ${jsString(stepName)},
7243
- when(
7244
+ runIf(
7244
7245
  (row) => ![${priorCandidates}].some((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)}),
7245
7246
  ${input2.resolver},
7246
7247
  ),
@@ -7274,9 +7275,9 @@ function generateFinderProviderWaterfall(input2) {
7274
7275
  );
7275
7276
  const candidateNames = stepNames.map((name) => `row.${name}`).join(", ");
7276
7277
  return `// ${input2.aggregateStepName} provider waterfall. Each provider leg is active once its TODO throw is removed;
7277
- // 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(...).
7278
7279
  ${providerSteps.join("\n ")}
7279
- .step(${jsString(input2.aggregateStepName)}, (row) => {
7280
+ .withColumn(${jsString(input2.aggregateStepName)}, (row) => {
7280
7281
  const candidates = [${candidateNames}];
7281
7282
  const match = candidates.find((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)});
7282
7283
  return ${optionalFinderValueExpression("match", input2.outputField)} ?? null;
@@ -7331,7 +7332,7 @@ function generateBootstrapPlaySource(input2) {
7331
7332
  rowTypeDefinitions,
7332
7333
  finderPlayResultTypes
7333
7334
  ].filter((definition) => definition.trim().length > 0).join("\n\n");
7334
- const importNames = needsWhenImport(input2.options) ? "definePlay, when" : "definePlay";
7335
+ const importNames = needsRunIfImport(input2.options) ? "definePlay, runIf" : "definePlay";
7335
7336
  return `import { ${importNames} } from 'deepline';
7336
7337
 
7337
7338
  ${typeDefinitions}
@@ -7350,7 +7351,7 @@ export default definePlay(${jsString(input2.options.name)}, async (ctx, input: I
7350
7351
  }
7351
7352
 
7352
7353
  const rows = await ctx
7353
- .map('bootstrap_rows', rowsToProcess)${mapSteps}
7354
+ .dataset('bootstrap_rows', rowsToProcess)${mapSteps}
7354
7355
  .run({
7355
7356
  key: (_row, index) => index,
7356
7357
  description: ${jsString(`Bootstrap ${input2.options.template}: seed source rows, run requested stages, then return the mapped rows.`)},
@@ -7860,7 +7861,7 @@ function normalizePlayNameForSheet(value) {
7860
7861
  function normalizeTableNamespace(value) {
7861
7862
  return validateIdentifierPart(
7862
7863
  value,
7863
- "ctx.map() key",
7864
+ "ctx.dataset() key",
7864
7865
  MAP_KEY_NAMESPACE_MAX_LENGTH
7865
7866
  );
7866
7867
  }
@@ -7870,7 +7871,7 @@ function validatePlaySheetTableName(playName, tableNamespace) {
7870
7871
  const resolved = `${playSegment}_${keySegment}`;
7871
7872
  if (resolved.length > POSTGRES_IDENTIFIER_MAX_LENGTH) {
7872
7873
  throw new Error(
7873
- `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}".`
7874
7875
  );
7875
7876
  }
7876
7877
  return resolved;
@@ -7950,6 +7951,7 @@ ${EXTRACTED_GETTER_ERROR_HINT}`;
7950
7951
  }
7951
7952
 
7952
7953
  // src/cli/commands/play.ts
7954
+ var PLAY_START_STREAM_FAST_COMPLETION_WAIT_MS = 2500;
7953
7955
  var PLAY_RUN_RESERVED_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
7954
7956
  "--json",
7955
7957
  "--wait",
@@ -8892,6 +8894,10 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8892
8894
  let eventCount = 0;
8893
8895
  let firstRunIdMs = null;
8894
8896
  let lastPhase = null;
8897
+ const startRequest = {
8898
+ ...input2.request,
8899
+ waitForCompletionMs: typeof input2.request.waitForCompletionMs === "number" ? input2.request.waitForCompletionMs : PLAY_START_STREAM_FAST_COMPLETION_WAIT_MS
8900
+ };
8895
8901
  const timeout = input2.waitTimeoutMs === null ? null : setTimeout(
8896
8902
  () => {
8897
8903
  timedOut = true;
@@ -8900,7 +8906,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8900
8906
  Math.max(1, input2.waitTimeoutMs)
8901
8907
  );
8902
8908
  try {
8903
- for await (const event of input2.client.startPlayRunStream(input2.request, {
8909
+ for await (const event of input2.client.startPlayRunStream(startRequest, {
8904
8910
  signal: controller.signal
8905
8911
  })) {
8906
8912
  eventCount += 1;
@@ -10108,10 +10114,66 @@ function formatActiveRunConflictError(input2) {
10108
10114
  lines.push(` rerun: add --force to the same deepline plays run command`);
10109
10115
  return lines.join("\n");
10110
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
+ }
10111
10173
  function normalizePlayStartError(error, playName) {
10112
10174
  const activeRuns = extractActiveRunsFromError(error);
10113
10175
  if (activeRuns.length === 0) {
10114
- return error;
10176
+ return normalizePlayValidationError(error);
10115
10177
  }
10116
10178
  return new DeeplineError(
10117
10179
  formatActiveRunConflictError({ playName, activeRuns }),
@@ -10274,7 +10336,7 @@ function writeStartedPlayRun(input2) {
10274
10336
  );
10275
10337
  }
10276
10338
  function parsePlayRunOptions(args) {
10277
- 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.";
10278
10340
  let filePath = null;
10279
10341
  let playName = null;
10280
10342
  let input2 = null;
@@ -10637,7 +10699,10 @@ async function handleFileBackedRun(options) {
10637
10699
  progress.phase("compiled play");
10638
10700
  } catch (error) {
10639
10701
  progress.fail();
10640
- 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
+ );
10641
10706
  return 1;
10642
10707
  }
10643
10708
  const bundleResult = graph.root;
@@ -11780,7 +11845,7 @@ function registerPlayCommands(program) {
11780
11845
  Concepts:
11781
11846
  Plays are durable cloud workflows.
11782
11847
  Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
11783
- ctx.map adds row keys and row progress.
11848
+ ctx.dataset adds row keys and row progress.
11784
11849
  Named play runs use the live revision unless --latest or --revision-id is set.
11785
11850
  Running a local file does not make it live; use set-live/publish explicitly.
11786
11851
 
@@ -11833,19 +11898,19 @@ Idempotent execution:
11833
11898
 
11834
11899
  await ctx.tools.execute({ id: 'company_lookup', tool, input });
11835
11900
 
11836
- For rows, use ctx.map plus a stable row key:
11901
+ For rows, use ctx.dataset plus a stable row key:
11837
11902
 
11838
11903
  const rows = await ctx
11839
- .map('companies_v1', companies)
11840
- .step('cto', (row, ctx) => ctx.tools.execute({
11904
+ .dataset('companies_v1', companies)
11905
+ .withColumn('cto', (row, ctx) => ctx.tools.execute({
11841
11906
  id: 'find_cto',
11842
11907
  tool: 'apollo_search_people_with_match',
11843
11908
  input: { q_organization_domains_list: [row.domain], per_page: 1 },
11844
11909
  }))
11845
11910
  .run({ key: 'domain' });
11846
11911
 
11847
- Reuse needs the same play, tool id, map name, row key, and compatible logic.
11848
- 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:
11849
11914
 
11850
11915
  .run({ key: 'domain', staleAfterSeconds: 86400 })
11851
11916
 
@@ -14000,11 +14065,11 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
14000
14065
  const rows = (list?.get() ?? []).slice(0, 100);
14001
14066
  // ${sampleRows}
14002
14067
  // columns: ${columns}
14003
- // .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.' }))
14004
- // .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.' }))
14005
- // 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.
14006
14071
  const enrichedData = await ctx
14007
- .map('enriched_data', rows)
14072
+ .dataset('enriched_data', rows)
14008
14073
  .run({
14009
14074
  key: ${rowKey},
14010
14075
  description: 'Enrich seeded rows.',
@@ -14825,8 +14890,8 @@ async function runPlayRunnerHealthCheck() {
14825
14890
  "",
14826
14891
  "export default definePlay('health-check', async (ctx) => {",
14827
14892
  " const rows = await ctx",
14828
- " .map('health_rows', [{ id: 'a' }, { id: 'b' }])",
14829
- " .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 }))",
14830
14895
  " .run({ key: 'id' });",
14831
14896
  " return { ok: true, rows, source: 'deepline health --play-runner' };",
14832
14897
  "});",