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 +7 -7
- package/dist/cli/index.js +96 -33
- package/dist/cli/index.mjs +96 -33
- package/dist/index.d.mts +62 -39
- package/dist/index.d.ts +62 -39
- package/dist/index.js +12 -3
- package/dist/index.mjs +11 -3
- package/dist/repo/apps/play-runner-workers/src/entry.ts +34 -21
- package/dist/repo/sdk/src/client.ts +1 -1
- package/dist/repo/sdk/src/index.ts +3 -2
- package/dist/repo/sdk/src/play.ts +70 -30
- package/dist/repo/sdk/src/release.ts +3 -3
- package/dist/repo/sdk/src/types.ts +2 -2
- package/dist/repo/sdk/src/worker-play-entry.ts +13 -2
- package/dist/repo/shared_libs/play-runtime/db-session-plan.ts +1 -1
- package/dist/repo/shared_libs/play-runtime/execution-plan.ts +2 -2
- package/dist/repo/shared_libs/play-runtime/step-lifecycle-tracker.ts +4 -4
- package/dist/repo/shared_libs/plays/dataset.ts +2 -2
- package/dist/repo/shared_libs/plays/row-identity.ts +3 -3
- package/dist/repo/shared_libs/plays/static-pipeline.ts +12 -9
- package/package.json +1 -1
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.
|
|
216
|
-
.
|
|
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 `
|
|
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,
|
|
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",
|
|
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.
|
|
288
|
-
.
|
|
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.
|
|
233
|
-
apiContract: "2026-
|
|
232
|
+
version: "0.1.72",
|
|
233
|
+
apiContract: "2026-06-dataset-column-syntax-cutover",
|
|
234
234
|
supportPolicy: {
|
|
235
|
-
latest: "0.1.
|
|
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
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
7239
|
+
return `.withColumn(
|
|
7240
7240
|
${jsString(stepName)},
|
|
7241
|
-
|
|
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
|
|
7275
|
+
// delete or comment out legs you do not want before running. Later legs are gated with runIf(...).
|
|
7276
7276
|
${providerSteps.join("\n ")}
|
|
7277
|
-
.
|
|
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 =
|
|
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
|
-
.
|
|
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.
|
|
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.
|
|
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}.
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
11902
|
+
For rows, use ctx.dataset plus a stable row key:
|
|
11840
11903
|
|
|
11841
11904
|
const rows = await ctx
|
|
11842
|
-
.
|
|
11843
|
-
.
|
|
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,
|
|
11851
|
-
To refresh, change the id/
|
|
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", "
|
|
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
|
-
// .
|
|
14007
|
-
// .
|
|
14008
|
-
// ctx.
|
|
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
|
-
.
|
|
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
|
-
" .
|
|
14825
|
-
" .
|
|
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
|
"});",
|
package/dist/cli/index.mjs
CHANGED
|
@@ -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.
|
|
210
|
-
apiContract: "2026-
|
|
209
|
+
version: "0.1.72",
|
|
210
|
+
apiContract: "2026-06-dataset-column-syntax-cutover",
|
|
211
211
|
supportPolicy: {
|
|
212
|
-
latest: "0.1.
|
|
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
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
7242
|
+
return `.withColumn(
|
|
7243
7243
|
${jsString(stepName)},
|
|
7244
|
-
|
|
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
|
|
7278
|
+
// delete or comment out legs you do not want before running. Later legs are gated with runIf(...).
|
|
7279
7279
|
${providerSteps.join("\n ")}
|
|
7280
|
-
.
|
|
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 =
|
|
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
|
-
.
|
|
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.
|
|
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.
|
|
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}.
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
11905
|
+
For rows, use ctx.dataset plus a stable row key:
|
|
11843
11906
|
|
|
11844
11907
|
const rows = await ctx
|
|
11845
|
-
.
|
|
11846
|
-
.
|
|
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,
|
|
11854
|
-
To refresh, change the id/
|
|
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", "
|
|
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
|
-
// .
|
|
14010
|
-
// .
|
|
14011
|
-
// ctx.
|
|
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
|
-
.
|
|
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
|
-
" .
|
|
14835
|
-
" .
|
|
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
|
"});",
|