deepline 0.1.70 → 0.1.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/cli/index.js +88 -29
- package/dist/cli/index.mjs +88 -29
- package/dist/index.d.mts +60 -37
- package/dist/index.d.ts +60 -37
- 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/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 +1 -1
- 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.71",
|
|
233
|
+
apiContract: "2026-06-dataset-column-syntax-cutover",
|
|
234
234
|
supportPolicy: {
|
|
235
|
-
latest: "0.1.
|
|
235
|
+
latest: "0.1.71",
|
|
236
236
|
minimumSupported: "0.1.53",
|
|
237
237
|
deprecatedBelow: "0.1.53"
|
|
238
238
|
}
|
|
@@ -6885,7 +6885,7 @@ function playInspectionComments(playRef, indent) {
|
|
|
6885
6885
|
function accessorExpression(base, field) {
|
|
6886
6886
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field) ? `${base}.${field}` : `${base}[${jsString(field)}]`;
|
|
6887
6887
|
}
|
|
6888
|
-
function
|
|
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;
|
|
@@ -10111,10 +10111,66 @@ function formatActiveRunConflictError(input2) {
|
|
|
10111
10111
|
lines.push(` rerun: add --force to the same deepline plays run command`);
|
|
10112
10112
|
return lines.join("\n");
|
|
10113
10113
|
}
|
|
10114
|
+
var PLAY_SYNTAX_MIGRATION_ERROR_MARKERS = [
|
|
10115
|
+
"ctx.map(...) has been replaced by ctx.dataset(...)",
|
|
10116
|
+
"Dataset .step(...) has been replaced by .withColumn(...)"
|
|
10117
|
+
];
|
|
10118
|
+
function stringArrayField(record, key) {
|
|
10119
|
+
const value = record[key];
|
|
10120
|
+
if (!Array.isArray(value)) return [];
|
|
10121
|
+
return value.filter((entry) => typeof entry === "string");
|
|
10122
|
+
}
|
|
10123
|
+
function extractPlayValidationErrors(value) {
|
|
10124
|
+
if (value instanceof DeeplineError) {
|
|
10125
|
+
return extractPlayValidationErrors(value.details);
|
|
10126
|
+
}
|
|
10127
|
+
if (!isRecord4(value)) {
|
|
10128
|
+
return [];
|
|
10129
|
+
}
|
|
10130
|
+
const directErrors = stringArrayField(value, "errors");
|
|
10131
|
+
if (directErrors.length > 0) {
|
|
10132
|
+
return directErrors;
|
|
10133
|
+
}
|
|
10134
|
+
for (const key of ["response", "error", "details"]) {
|
|
10135
|
+
const nestedErrors = extractPlayValidationErrors(value[key]);
|
|
10136
|
+
if (nestedErrors.length > 0) {
|
|
10137
|
+
return nestedErrors;
|
|
10138
|
+
}
|
|
10139
|
+
}
|
|
10140
|
+
return [];
|
|
10141
|
+
}
|
|
10142
|
+
function orderPlayValidationErrors(errors) {
|
|
10143
|
+
const uniqueErrors = [...new Set(errors)];
|
|
10144
|
+
const migrationErrors = uniqueErrors.filter(
|
|
10145
|
+
(error) => PLAY_SYNTAX_MIGRATION_ERROR_MARKERS.some(
|
|
10146
|
+
(marker) => error.includes(marker)
|
|
10147
|
+
)
|
|
10148
|
+
);
|
|
10149
|
+
const remainingErrors = uniqueErrors.filter(
|
|
10150
|
+
(error) => !migrationErrors.includes(error)
|
|
10151
|
+
);
|
|
10152
|
+
return [...migrationErrors, ...remainingErrors];
|
|
10153
|
+
}
|
|
10154
|
+
function normalizePlayValidationError(error) {
|
|
10155
|
+
const validationErrors = extractPlayValidationErrors(error);
|
|
10156
|
+
if (validationErrors.length === 0) {
|
|
10157
|
+
return error;
|
|
10158
|
+
}
|
|
10159
|
+
const message = orderPlayValidationErrors(validationErrors).join("\n");
|
|
10160
|
+
if (!message.trim()) {
|
|
10161
|
+
return error;
|
|
10162
|
+
}
|
|
10163
|
+
return new DeeplineError(
|
|
10164
|
+
message,
|
|
10165
|
+
error instanceof DeeplineError ? error.statusCode : void 0,
|
|
10166
|
+
error instanceof DeeplineError ? error.code : "PLAY_VALIDATION_ERROR",
|
|
10167
|
+
error instanceof DeeplineError ? error.details : void 0
|
|
10168
|
+
);
|
|
10169
|
+
}
|
|
10114
10170
|
function normalizePlayStartError(error, playName) {
|
|
10115
10171
|
const activeRuns = extractActiveRunsFromError(error);
|
|
10116
10172
|
if (activeRuns.length === 0) {
|
|
10117
|
-
return error;
|
|
10173
|
+
return normalizePlayValidationError(error);
|
|
10118
10174
|
}
|
|
10119
10175
|
return new DeeplineError(
|
|
10120
10176
|
formatActiveRunConflictError({ playName, activeRuns }),
|
|
@@ -10277,7 +10333,7 @@ function writeStartedPlayRun(input2) {
|
|
|
10277
10333
|
);
|
|
10278
10334
|
}
|
|
10279
10335
|
function parsePlayRunOptions(args) {
|
|
10280
|
-
const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.
|
|
10336
|
+
const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.dataset guidance.";
|
|
10281
10337
|
let filePath = null;
|
|
10282
10338
|
let playName = null;
|
|
10283
10339
|
let input2 = null;
|
|
@@ -10640,7 +10696,10 @@ async function handleFileBackedRun(options) {
|
|
|
10640
10696
|
progress.phase("compiled play");
|
|
10641
10697
|
} catch (error) {
|
|
10642
10698
|
progress.fail();
|
|
10643
|
-
|
|
10699
|
+
const normalizedError = normalizePlayValidationError(error);
|
|
10700
|
+
console.error(
|
|
10701
|
+
normalizedError instanceof Error ? normalizedError.message : String(normalizedError)
|
|
10702
|
+
);
|
|
10644
10703
|
return 1;
|
|
10645
10704
|
}
|
|
10646
10705
|
const bundleResult = graph.root;
|
|
@@ -11783,7 +11842,7 @@ function registerPlayCommands(program) {
|
|
|
11783
11842
|
Concepts:
|
|
11784
11843
|
Plays are durable cloud workflows.
|
|
11785
11844
|
Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
|
|
11786
|
-
ctx.
|
|
11845
|
+
ctx.dataset adds row keys and row progress.
|
|
11787
11846
|
Named play runs use the live revision unless --latest or --revision-id is set.
|
|
11788
11847
|
Running a local file does not make it live; use set-live/publish explicitly.
|
|
11789
11848
|
|
|
@@ -11836,19 +11895,19 @@ Idempotent execution:
|
|
|
11836
11895
|
|
|
11837
11896
|
await ctx.tools.execute({ id: 'company_lookup', tool, input });
|
|
11838
11897
|
|
|
11839
|
-
For rows, use ctx.
|
|
11898
|
+
For rows, use ctx.dataset plus a stable row key:
|
|
11840
11899
|
|
|
11841
11900
|
const rows = await ctx
|
|
11842
|
-
.
|
|
11843
|
-
.
|
|
11901
|
+
.dataset('companies_v1', companies)
|
|
11902
|
+
.withColumn('cto', (row, ctx) => ctx.tools.execute({
|
|
11844
11903
|
id: 'find_cto',
|
|
11845
11904
|
tool: 'apollo_search_people_with_match',
|
|
11846
11905
|
input: { q_organization_domains_list: [row.domain], per_page: 1 },
|
|
11847
11906
|
}))
|
|
11848
11907
|
.run({ key: 'domain' });
|
|
11849
11908
|
|
|
11850
|
-
Reuse needs the same play, tool id,
|
|
11851
|
-
To refresh, change the id/
|
|
11909
|
+
Reuse needs the same play, tool id, dataset name, row key, and compatible logic.
|
|
11910
|
+
To refresh, change the id/dataset key or set staleAfterSeconds:
|
|
11852
11911
|
|
|
11853
11912
|
.run({ key: 'domain', staleAfterSeconds: 86400 })
|
|
11854
11913
|
|
|
@@ -14003,11 +14062,11 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
|
|
|
14003
14062
|
const rows = (list?.get() ?? []).slice(0, 100);
|
|
14004
14063
|
// ${sampleRows}
|
|
14005
14064
|
// columns: ${columns}
|
|
14006
|
-
// .
|
|
14007
|
-
// .
|
|
14008
|
-
// ctx.
|
|
14065
|
+
// .withColumn('email_waterfall', (row, rowCtx) => rowCtx.runPlay('name_domain_email', 'name-and-domain-to-email', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), domain: String(row.domain ?? '') }, { description: 'Resolve email.' }))
|
|
14066
|
+
// .withColumn('phone_waterfall', (row, rowCtx) => rowCtx.runPlay('contact_phone', 'contact-to-phone', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), email: String(row.email ?? '') }, { description: 'Resolve phone.' }))
|
|
14067
|
+
// ctx.dataset is idempotent by dataset key + row key; reruns reuse completed rows.
|
|
14009
14068
|
const enrichedData = await ctx
|
|
14010
|
-
.
|
|
14069
|
+
.dataset('enriched_data', rows)
|
|
14011
14070
|
.run({
|
|
14012
14071
|
key: ${rowKey},
|
|
14013
14072
|
description: 'Enrich seeded rows.',
|
|
@@ -14821,8 +14880,8 @@ async function runPlayRunnerHealthCheck() {
|
|
|
14821
14880
|
"",
|
|
14822
14881
|
"export default definePlay('health-check', async (ctx) => {",
|
|
14823
14882
|
" const rows = await ctx",
|
|
14824
|
-
" .
|
|
14825
|
-
" .
|
|
14883
|
+
" .dataset('health_rows', [{ id: 'a' }, { id: 'b' }])",
|
|
14884
|
+
" .withColumn('echo', (row) => ({ ok: true, id: row.id }))",
|
|
14826
14885
|
" .run({ key: 'id' });",
|
|
14827
14886
|
" return { ok: true, rows, source: 'deepline health --play-runner' };",
|
|
14828
14887
|
"});",
|
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.71",
|
|
210
|
+
apiContract: "2026-06-dataset-column-syntax-cutover",
|
|
211
211
|
supportPolicy: {
|
|
212
|
-
latest: "0.1.
|
|
212
|
+
latest: "0.1.71",
|
|
213
213
|
minimumSupported: "0.1.53",
|
|
214
214
|
deprecatedBelow: "0.1.53"
|
|
215
215
|
}
|
|
@@ -6888,7 +6888,7 @@ function playInspectionComments(playRef, indent) {
|
|
|
6888
6888
|
function accessorExpression(base, field) {
|
|
6889
6889
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field) ? `${base}.${field}` : `${base}[${jsString(field)}]`;
|
|
6890
6890
|
}
|
|
6891
|
-
function
|
|
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;
|
|
@@ -10114,10 +10114,66 @@ function formatActiveRunConflictError(input2) {
|
|
|
10114
10114
|
lines.push(` rerun: add --force to the same deepline plays run command`);
|
|
10115
10115
|
return lines.join("\n");
|
|
10116
10116
|
}
|
|
10117
|
+
var PLAY_SYNTAX_MIGRATION_ERROR_MARKERS = [
|
|
10118
|
+
"ctx.map(...) has been replaced by ctx.dataset(...)",
|
|
10119
|
+
"Dataset .step(...) has been replaced by .withColumn(...)"
|
|
10120
|
+
];
|
|
10121
|
+
function stringArrayField(record, key) {
|
|
10122
|
+
const value = record[key];
|
|
10123
|
+
if (!Array.isArray(value)) return [];
|
|
10124
|
+
return value.filter((entry) => typeof entry === "string");
|
|
10125
|
+
}
|
|
10126
|
+
function extractPlayValidationErrors(value) {
|
|
10127
|
+
if (value instanceof DeeplineError) {
|
|
10128
|
+
return extractPlayValidationErrors(value.details);
|
|
10129
|
+
}
|
|
10130
|
+
if (!isRecord4(value)) {
|
|
10131
|
+
return [];
|
|
10132
|
+
}
|
|
10133
|
+
const directErrors = stringArrayField(value, "errors");
|
|
10134
|
+
if (directErrors.length > 0) {
|
|
10135
|
+
return directErrors;
|
|
10136
|
+
}
|
|
10137
|
+
for (const key of ["response", "error", "details"]) {
|
|
10138
|
+
const nestedErrors = extractPlayValidationErrors(value[key]);
|
|
10139
|
+
if (nestedErrors.length > 0) {
|
|
10140
|
+
return nestedErrors;
|
|
10141
|
+
}
|
|
10142
|
+
}
|
|
10143
|
+
return [];
|
|
10144
|
+
}
|
|
10145
|
+
function orderPlayValidationErrors(errors) {
|
|
10146
|
+
const uniqueErrors = [...new Set(errors)];
|
|
10147
|
+
const migrationErrors = uniqueErrors.filter(
|
|
10148
|
+
(error) => PLAY_SYNTAX_MIGRATION_ERROR_MARKERS.some(
|
|
10149
|
+
(marker) => error.includes(marker)
|
|
10150
|
+
)
|
|
10151
|
+
);
|
|
10152
|
+
const remainingErrors = uniqueErrors.filter(
|
|
10153
|
+
(error) => !migrationErrors.includes(error)
|
|
10154
|
+
);
|
|
10155
|
+
return [...migrationErrors, ...remainingErrors];
|
|
10156
|
+
}
|
|
10157
|
+
function normalizePlayValidationError(error) {
|
|
10158
|
+
const validationErrors = extractPlayValidationErrors(error);
|
|
10159
|
+
if (validationErrors.length === 0) {
|
|
10160
|
+
return error;
|
|
10161
|
+
}
|
|
10162
|
+
const message = orderPlayValidationErrors(validationErrors).join("\n");
|
|
10163
|
+
if (!message.trim()) {
|
|
10164
|
+
return error;
|
|
10165
|
+
}
|
|
10166
|
+
return new DeeplineError(
|
|
10167
|
+
message,
|
|
10168
|
+
error instanceof DeeplineError ? error.statusCode : void 0,
|
|
10169
|
+
error instanceof DeeplineError ? error.code : "PLAY_VALIDATION_ERROR",
|
|
10170
|
+
error instanceof DeeplineError ? error.details : void 0
|
|
10171
|
+
);
|
|
10172
|
+
}
|
|
10117
10173
|
function normalizePlayStartError(error, playName) {
|
|
10118
10174
|
const activeRuns = extractActiveRunsFromError(error);
|
|
10119
10175
|
if (activeRuns.length === 0) {
|
|
10120
|
-
return error;
|
|
10176
|
+
return normalizePlayValidationError(error);
|
|
10121
10177
|
}
|
|
10122
10178
|
return new DeeplineError(
|
|
10123
10179
|
formatActiveRunConflictError({ playName, activeRuns }),
|
|
@@ -10280,7 +10336,7 @@ function writeStartedPlayRun(input2) {
|
|
|
10280
10336
|
);
|
|
10281
10337
|
}
|
|
10282
10338
|
function parsePlayRunOptions(args) {
|
|
10283
|
-
const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.
|
|
10339
|
+
const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.dataset guidance.";
|
|
10284
10340
|
let filePath = null;
|
|
10285
10341
|
let playName = null;
|
|
10286
10342
|
let input2 = null;
|
|
@@ -10643,7 +10699,10 @@ async function handleFileBackedRun(options) {
|
|
|
10643
10699
|
progress.phase("compiled play");
|
|
10644
10700
|
} catch (error) {
|
|
10645
10701
|
progress.fail();
|
|
10646
|
-
|
|
10702
|
+
const normalizedError = normalizePlayValidationError(error);
|
|
10703
|
+
console.error(
|
|
10704
|
+
normalizedError instanceof Error ? normalizedError.message : String(normalizedError)
|
|
10705
|
+
);
|
|
10647
10706
|
return 1;
|
|
10648
10707
|
}
|
|
10649
10708
|
const bundleResult = graph.root;
|
|
@@ -11786,7 +11845,7 @@ function registerPlayCommands(program) {
|
|
|
11786
11845
|
Concepts:
|
|
11787
11846
|
Plays are durable cloud workflows.
|
|
11788
11847
|
Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
|
|
11789
|
-
ctx.
|
|
11848
|
+
ctx.dataset adds row keys and row progress.
|
|
11790
11849
|
Named play runs use the live revision unless --latest or --revision-id is set.
|
|
11791
11850
|
Running a local file does not make it live; use set-live/publish explicitly.
|
|
11792
11851
|
|
|
@@ -11839,19 +11898,19 @@ Idempotent execution:
|
|
|
11839
11898
|
|
|
11840
11899
|
await ctx.tools.execute({ id: 'company_lookup', tool, input });
|
|
11841
11900
|
|
|
11842
|
-
For rows, use ctx.
|
|
11901
|
+
For rows, use ctx.dataset plus a stable row key:
|
|
11843
11902
|
|
|
11844
11903
|
const rows = await ctx
|
|
11845
|
-
.
|
|
11846
|
-
.
|
|
11904
|
+
.dataset('companies_v1', companies)
|
|
11905
|
+
.withColumn('cto', (row, ctx) => ctx.tools.execute({
|
|
11847
11906
|
id: 'find_cto',
|
|
11848
11907
|
tool: 'apollo_search_people_with_match',
|
|
11849
11908
|
input: { q_organization_domains_list: [row.domain], per_page: 1 },
|
|
11850
11909
|
}))
|
|
11851
11910
|
.run({ key: 'domain' });
|
|
11852
11911
|
|
|
11853
|
-
Reuse needs the same play, tool id,
|
|
11854
|
-
To refresh, change the id/
|
|
11912
|
+
Reuse needs the same play, tool id, dataset name, row key, and compatible logic.
|
|
11913
|
+
To refresh, change the id/dataset key or set staleAfterSeconds:
|
|
11855
11914
|
|
|
11856
11915
|
.run({ key: 'domain', staleAfterSeconds: 86400 })
|
|
11857
11916
|
|
|
@@ -14006,11 +14065,11 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
|
|
|
14006
14065
|
const rows = (list?.get() ?? []).slice(0, 100);
|
|
14007
14066
|
// ${sampleRows}
|
|
14008
14067
|
// columns: ${columns}
|
|
14009
|
-
// .
|
|
14010
|
-
// .
|
|
14011
|
-
// ctx.
|
|
14068
|
+
// .withColumn('email_waterfall', (row, rowCtx) => rowCtx.runPlay('name_domain_email', 'name-and-domain-to-email', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), domain: String(row.domain ?? '') }, { description: 'Resolve email.' }))
|
|
14069
|
+
// .withColumn('phone_waterfall', (row, rowCtx) => rowCtx.runPlay('contact_phone', 'contact-to-phone', { first_name: String(row.first_name ?? ''), last_name: String(row.last_name ?? ''), email: String(row.email ?? '') }, { description: 'Resolve phone.' }))
|
|
14070
|
+
// ctx.dataset is idempotent by dataset key + row key; reruns reuse completed rows.
|
|
14012
14071
|
const enrichedData = await ctx
|
|
14013
|
-
.
|
|
14072
|
+
.dataset('enriched_data', rows)
|
|
14014
14073
|
.run({
|
|
14015
14074
|
key: ${rowKey},
|
|
14016
14075
|
description: 'Enrich seeded rows.',
|
|
@@ -14831,8 +14890,8 @@ async function runPlayRunnerHealthCheck() {
|
|
|
14831
14890
|
"",
|
|
14832
14891
|
"export default definePlay('health-check', async (ctx) => {",
|
|
14833
14892
|
" const rows = await ctx",
|
|
14834
|
-
" .
|
|
14835
|
-
" .
|
|
14893
|
+
" .dataset('health_rows', [{ id: 'a' }, { id: 'b' }])",
|
|
14894
|
+
" .withColumn('echo', (row) => ({ ok: true, id: row.id }))",
|
|
14836
14895
|
" .run({ key: 'id' });",
|
|
14837
14896
|
" return { ok: true, rows, source: 'deepline health --play-runner' };",
|
|
14838
14897
|
"});",
|