deepline 0.1.119 → 0.1.121
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 +4 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +21 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/batching.ts +185 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-batch.ts +107 -0
- package/dist/{repo → bundling-sources}/sdk/src/client.ts +116 -12
- package/dist/bundling-sources/sdk/src/compat.ts +191 -0
- package/dist/bundling-sources/sdk/src/gtm.ts +146 -0
- package/dist/bundling-sources/sdk/src/helpers.ts +12 -0
- package/dist/{repo → bundling-sources}/sdk/src/index.ts +2 -1
- package/dist/{repo → bundling-sources}/sdk/src/play.ts +3 -1
- package/dist/{repo → bundling-sources}/sdk/src/plays/bundle-play-file.ts +17 -5
- package/dist/{repo → bundling-sources}/sdk/src/release.ts +2 -2
- package/dist/{repo → bundling-sources}/sdk/src/runs/observe-transport.ts +2 -3
- package/dist/bundling-sources/shared_libs/play-data-plane/index.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +838 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5510 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +261 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +828 -0
- package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +50 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/event-wait-tools.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +171 -0
- package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-diagnosis.ts +321 -0
- package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-target.ts +158 -0
- package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +50 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +119 -0
- package/dist/{repo → bundling-sources}/shared_libs/play-runtime/map-row-identity.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +636 -0
- package/dist/bundling-sources/shared_libs/play-runtime/postgres-json.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/progress-emitter.ts +197 -0
- package/dist/bundling-sources/shared_libs/play-runtime/projection.ts +262 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +143 -0
- package/dist/bundling-sources/shared_libs/play-runtime/public-play-contract.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +178 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +4015 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-constraints.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +238 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +53 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver.ts +149 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-batch-executor.ts +149 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result-types.ts +159 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tracing.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/waterfall-replay.ts +79 -0
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +139 -0
- package/dist/bundling-sources/shared_libs/plays/artifact-transport.ts +14 -0
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +49 -0
- package/dist/bundling-sources/shared_libs/plays/compiler-manifest.ts +41 -0
- package/dist/bundling-sources/shared_libs/plays/dataset-summary.ts +163 -0
- package/dist/bundling-sources/shared_libs/plays/definition.ts +267 -0
- package/dist/bundling-sources/shared_libs/plays/file-refs.ts +11 -0
- package/dist/bundling-sources/shared_libs/plays/input-contract.ts +146 -0
- package/dist/bundling-sources/shared_libs/plays/resolve-static-pipeline.ts +190 -0
- package/dist/bundling-sources/shared_libs/plays/runtime-validation.ts +417 -0
- package/dist/bundling-sources/shared_libs/plays/tool-codegen.ts +142 -0
- package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +274 -0
- package/dist/bundling-sources/shared_libs/temporal/preview-config.ts +150 -0
- package/dist/cli/index.js +811 -2207
- package/dist/cli/index.mjs +847 -2258
- package/dist/compiler-manifest-BjoRENv9.d.mts +227 -0
- package/dist/compiler-manifest-BjoRENv9.d.ts +227 -0
- package/dist/index.d.mts +8 -231
- package/dist/index.d.ts +8 -231
- package/dist/index.js +101 -15
- package/dist/index.mjs +101 -15
- package/dist/plays/bundle-play-file.d.mts +120 -0
- package/dist/plays/bundle-play-file.d.ts +120 -0
- package/dist/plays/bundle-play-file.mjs +1830 -0
- package/package.json +4 -9
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/child-play-await.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/child-play-submit.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/coordinator-entry.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/dedup-do.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/entry.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/csv-rows.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/dataset-handles.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/live-progress.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/receipts.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/row-isolation.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/tool-http-errors.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/workflow-instance-create.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/workflow-retry-state.ts +0 -0
- /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/workflow-retry.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/agent-runtime.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/config.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/errors.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/http.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/plays/harness-stub.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/plays/local-file-discovery.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/stream-reconnect.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/tool-output.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/types.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/version.ts +0 -0
- /package/dist/{repo → bundling-sources}/sdk/src/worker-play-entry.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-data-plane/cell-policy.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-data-plane/column-names.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-data-plane/sheet-contract.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/backend.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/batch-runtime.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/batching-types.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/cell-staleness.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/coordinator-headers.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/csv-rename.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/db-session-crypto.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/db-session-plan.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/db-session.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/dedup-backend.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/default-batch-strategies.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/email-status.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/execution-plan.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/extractor-targets.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/fullenrich-batching.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/governor/governor.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/governor/policy.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/governor/rate-state-backend.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/live-events.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/play-runtime-batching-registry.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/profiles.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/providers.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/run-failure.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/run-ledger.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/run-snapshot-stream.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/scheduler-backend.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/secret-capability.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/secret-redaction.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/step-lifecycle-tracker.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/step-program-dataset-builder.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/submit-limits.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/tool-result.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/work-receipts.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/plays/bootstrap-routes.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/plays/bundling/index.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/plays/bundling/limits.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/plays/contracts.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/plays/dataset.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/plays/row-identity.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/plays/secret-guardrails.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/plays/static-pipeline.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/security/outbound-url-policy.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/security/safe-fetch.ts +0 -0
- /package/dist/{repo → bundling-sources}/shared_libs/temporal/constants.ts +0 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import * as acorn from 'acorn';
|
|
2
|
+
import { fullAncestor as walkFullAncestor } from 'acorn-walk';
|
|
3
|
+
import type { PlayBundleArtifact } from './artifact-types';
|
|
4
|
+
import type { PlayStructuredDefinition } from './definition';
|
|
5
|
+
import { validatePlayStructuredDefinition } from './definition';
|
|
6
|
+
import {
|
|
7
|
+
MAP_KEY_NAMESPACE_MAX_LENGTH,
|
|
8
|
+
normalizeTableNamespace,
|
|
9
|
+
} from './row-identity';
|
|
10
|
+
import { DISALLOWED_RUN_JAVASCRIPT_TOOL_MESSAGE } from '../play-runtime/runtime-constraints';
|
|
11
|
+
|
|
12
|
+
export async function validatePlay(
|
|
13
|
+
code: string | null | undefined,
|
|
14
|
+
definition?: PlayStructuredDefinition | null,
|
|
15
|
+
codeFormat: 'function' | 'cjs_module' | 'esm_module' = 'function',
|
|
16
|
+
validationSource?: string | null,
|
|
17
|
+
artifact?: PlayBundleArtifact | null,
|
|
18
|
+
): Promise<{ valid: boolean; errors: string[] }> {
|
|
19
|
+
const errors: string[] = [];
|
|
20
|
+
|
|
21
|
+
if (definition) {
|
|
22
|
+
return validatePlayStructuredDefinition(definition);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const sourceForValidation = validationSource ?? code ?? '';
|
|
26
|
+
|
|
27
|
+
if (!sourceForValidation.trim()) {
|
|
28
|
+
return { valid: false, errors: ['Play code is required.'] };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (codeFormat === 'cjs_module' && code?.trim()) {
|
|
33
|
+
new Function('module', 'exports', 'require', code);
|
|
34
|
+
} else if (codeFormat === 'esm_module' && code?.trim()) {
|
|
35
|
+
// esm_module bundles target Cloudflare Workers; their top-level uses
|
|
36
|
+
// import/export which `new Function` rejects. Skip the in-host parse
|
|
37
|
+
// check — the bundler already typechecked + esbuild parsed the source.
|
|
38
|
+
// The play Worker runtime catches any actual runtime errors.
|
|
39
|
+
} else if (codeFormat !== 'cjs_module') {
|
|
40
|
+
new Function('ctx', 'input', `return (${code})(ctx, input)`);
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
errors.push(`Parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
errors.push(...validatePlayMapStructure(sourceForValidation));
|
|
47
|
+
errors.push(...validateRuntimeSyntax(sourceForValidation));
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
artifact &&
|
|
51
|
+
artifact.codeFormat !== 'cjs_module' &&
|
|
52
|
+
artifact.codeFormat !== 'esm_module'
|
|
53
|
+
) {
|
|
54
|
+
errors.push(
|
|
55
|
+
'Play artifact codeFormat must be "cjs_module" or "esm_module".',
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { valid: errors.length === 0, errors };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function validatePlayMapStructure(code: string): string[] {
|
|
63
|
+
const errors: string[] = [];
|
|
64
|
+
const calledPlayNames = new Set<string>();
|
|
65
|
+
let ast: acorn.Node;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
ast = parsePlayAst(code);
|
|
69
|
+
} catch {
|
|
70
|
+
return errors;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
walkFullAncestor(ast, (node, _state, ancestors) => {
|
|
74
|
+
if (
|
|
75
|
+
node.type === 'CallExpression' &&
|
|
76
|
+
usesDisallowedRunJavascriptTool(node as acorn.CallExpression) &&
|
|
77
|
+
hasMapResolverAncestor(ancestors)
|
|
78
|
+
) {
|
|
79
|
+
errors.push(DISALLOWED_RUN_JAVASCRIPT_TOOL_MESSAGE);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isDeprecatedCtxMapCall(node)) {
|
|
83
|
+
errors.push(
|
|
84
|
+
'ctx.map(...) has been replaced by ctx.dataset(...). Use ctx.dataset("leads", rows).withColumn("company", row => row.domain).run({ key: "lead_id" }).',
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!isCtxDatasetCall(node)) {
|
|
90
|
+
const callExpression =
|
|
91
|
+
(node as acorn.Node).type === 'CallExpression'
|
|
92
|
+
? (node as unknown as acorn.CallExpression)
|
|
93
|
+
: null;
|
|
94
|
+
if (
|
|
95
|
+
callExpression &&
|
|
96
|
+
callExpression.callee.type === 'MemberExpression' &&
|
|
97
|
+
callExpression.callee.property.type === 'Identifier' &&
|
|
98
|
+
callExpression.callee.property.name === 'runPlay'
|
|
99
|
+
) {
|
|
100
|
+
const firstArgument = callExpression.arguments[0];
|
|
101
|
+
if (
|
|
102
|
+
firstArgument?.type === 'Literal' &&
|
|
103
|
+
typeof firstArgument.value === 'string'
|
|
104
|
+
) {
|
|
105
|
+
calledPlayNames.add(firstArgument.value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
callExpression &&
|
|
110
|
+
callExpression.callee.type === 'MemberExpression' &&
|
|
111
|
+
callExpression.callee.property.type === 'Identifier' &&
|
|
112
|
+
callExpression.callee.property.name === 'waterfall'
|
|
113
|
+
) {
|
|
114
|
+
const firstArgument = callExpression.arguments[0];
|
|
115
|
+
if (firstArgument?.type === 'ObjectExpression') {
|
|
116
|
+
const minResultsProperty = firstArgument.properties.find(
|
|
117
|
+
(prop) =>
|
|
118
|
+
prop.type === 'Property' &&
|
|
119
|
+
prop.key.type === 'Identifier' &&
|
|
120
|
+
prop.key.name === 'minResults',
|
|
121
|
+
);
|
|
122
|
+
if (!minResultsProperty) {
|
|
123
|
+
errors.push(
|
|
124
|
+
'Inline ctx.waterfall({...}) calls must declare minResults.',
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (hasMapResolverAncestor(ancestors)) {
|
|
133
|
+
errors.push(
|
|
134
|
+
'Nested ctx.dataset() is not supported. Flatten work into a single dataset definition.',
|
|
135
|
+
);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
extractValidatedMapTableNamespace(node, errors);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const playNameMatch = code.match(
|
|
143
|
+
/define(?:Play|Workflow)\s*\(\s*['"`]([^'"`]+)['"`]/,
|
|
144
|
+
);
|
|
145
|
+
const definedPlayName = playNameMatch?.[1]?.trim();
|
|
146
|
+
if (definedPlayName && calledPlayNames.has(definedPlayName)) {
|
|
147
|
+
errors.push(
|
|
148
|
+
`Recursive play graph detected: ${definedPlayName} -> ${definedPlayName}. Use a different child play or refactor the shared logic.`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return [...new Set(errors)];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function validateRuntimeSyntax(code: string): string[] {
|
|
156
|
+
const errors: string[] = [];
|
|
157
|
+
let ast: acorn.Node;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
ast = parsePlayAst(code);
|
|
161
|
+
} catch {
|
|
162
|
+
return errors;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
walkFullAncestor(ast, (node) => {
|
|
166
|
+
if (node.type === 'ImportExpression') {
|
|
167
|
+
errors.push(
|
|
168
|
+
'Dynamic import() is not allowed in plays. Use static imports instead.',
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (node.type !== 'CallExpression') {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const callNode = node as acorn.CallExpression;
|
|
178
|
+
if (
|
|
179
|
+
callNode.callee.type !== 'Identifier' ||
|
|
180
|
+
callNode.callee.name !== 'require'
|
|
181
|
+
) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const firstArgument = callNode.arguments[0];
|
|
186
|
+
const isLiteralString =
|
|
187
|
+
firstArgument?.type === 'Literal' &&
|
|
188
|
+
typeof firstArgument.value === 'string';
|
|
189
|
+
|
|
190
|
+
if (!isLiteralString) {
|
|
191
|
+
errors.push(
|
|
192
|
+
'Dynamic require() is not allowed in plays. Use require("literal") only.',
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return [...new Set(errors)];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function parsePlayAst(code: string): acorn.Node {
|
|
201
|
+
try {
|
|
202
|
+
return acorn.parse(code, {
|
|
203
|
+
ecmaVersion: 'latest',
|
|
204
|
+
sourceType: 'module',
|
|
205
|
+
allowAwaitOutsideFunction: true,
|
|
206
|
+
}) as acorn.Node;
|
|
207
|
+
} catch {
|
|
208
|
+
return acorn.parse(`const __play = ${code};`, {
|
|
209
|
+
ecmaVersion: 'latest',
|
|
210
|
+
sourceType: 'module',
|
|
211
|
+
}) as acorn.Node;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function usesDisallowedRunJavascriptTool(node: acorn.CallExpression): boolean {
|
|
216
|
+
const callee = node.callee;
|
|
217
|
+
if (callee.type !== 'MemberExpression') {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (
|
|
222
|
+
callee.property.type !== 'Identifier' ||
|
|
223
|
+
callee.property.name !== 'tool'
|
|
224
|
+
) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const firstArgument = node.arguments[0];
|
|
229
|
+
return (
|
|
230
|
+
firstArgument?.type === 'Literal' &&
|
|
231
|
+
firstArgument.value === 'run_javascript'
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function extractValidatedMapTableNamespace(
|
|
236
|
+
node: acorn.CallExpression,
|
|
237
|
+
errors: string[],
|
|
238
|
+
): string | null {
|
|
239
|
+
const keyArgument = node.arguments[0];
|
|
240
|
+
const rowsArgument = node.arguments[1];
|
|
241
|
+
if (!keyArgument) {
|
|
242
|
+
errors.push(
|
|
243
|
+
'ctx.dataset() requires a string literal dataset key as the first argument, e.g. ctx.dataset("leads", rows).withColumn("company", row => row.domain).run({ key: "lead_id" }).',
|
|
244
|
+
);
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!rowsArgument) {
|
|
249
|
+
errors.push(
|
|
250
|
+
'ctx.dataset() requires rows as the second argument, e.g. ctx.dataset("leads", rows).withColumn("company", row => row.domain).run({ key: "lead_id" }).',
|
|
251
|
+
);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (keyArgument.type !== 'Literal' || typeof keyArgument.value !== 'string') {
|
|
256
|
+
errors.push(
|
|
257
|
+
'ctx.dataset() requires a string literal key as the first argument so Deepline can precompute idempotency.',
|
|
258
|
+
);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!keyArgument.value.trim()) {
|
|
263
|
+
errors.push(
|
|
264
|
+
'ctx.dataset() requires a non-empty string key as the first argument.',
|
|
265
|
+
);
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
normalizeTableNamespace(keyArgument.value);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
errors.push(
|
|
273
|
+
error instanceof Error
|
|
274
|
+
? `${error.message} Example: ctx.dataset("leads", rows).withColumn("company", row => row.domain).run({ key: "lead_id", description: "..." }).`
|
|
275
|
+
: `ctx.dataset() key must normalize to <= ${MAP_KEY_NAMESPACE_MAX_LENGTH} characters.`,
|
|
276
|
+
);
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (rowsArgument.type === 'ObjectExpression') {
|
|
281
|
+
errors.push(
|
|
282
|
+
'ctx.dataset() key must not be an object. Use ctx.dataset("leads", rows).withColumn(...).run({ key: "lead_id" }).',
|
|
283
|
+
);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
if (
|
|
287
|
+
rowsArgument.type === 'FunctionExpression' ||
|
|
288
|
+
isFunctionNode(rowsArgument)
|
|
289
|
+
) {
|
|
290
|
+
errors.push('ctx.dataset() requires rows as the second argument.');
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const optionsArgument = node.arguments[2];
|
|
295
|
+
if (optionsArgument) {
|
|
296
|
+
errors.push(
|
|
297
|
+
'ctx.dataset() accepts only a dataset key and rows. Add columns with .withColumn(...) and pass row identity options to .run({ key: "lead_id" }).',
|
|
298
|
+
);
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return keyArgument.value.trim();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function isCtxDatasetCall(node: acorn.Node): node is acorn.CallExpression {
|
|
306
|
+
if (node.type !== 'CallExpression') {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
const callee = (node as acorn.CallExpression).callee;
|
|
310
|
+
if (callee.type !== 'MemberExpression') {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
if (
|
|
314
|
+
callee.property.type !== 'Identifier' ||
|
|
315
|
+
callee.property.name !== 'dataset'
|
|
316
|
+
) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
if (callee.object.type !== 'Identifier') {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
return callee.object.name === 'ctx' || callee.object.name.endsWith('Ctx');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isDeprecatedCtxMapCall(node: acorn.Node): node is acorn.CallExpression {
|
|
326
|
+
if (node.type !== 'CallExpression') return false;
|
|
327
|
+
const callee = (node as acorn.CallExpression).callee;
|
|
328
|
+
return (
|
|
329
|
+
callee.type === 'MemberExpression' &&
|
|
330
|
+
callee.property.type === 'Identifier' &&
|
|
331
|
+
callee.property.name === 'map' &&
|
|
332
|
+
callee.object.type === 'Identifier' &&
|
|
333
|
+
(callee.object.name === 'ctx' || callee.object.name.endsWith('Ctx'))
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function isFunctionNode(
|
|
338
|
+
node: acorn.Node | null | undefined,
|
|
339
|
+
): node is
|
|
340
|
+
| acorn.FunctionDeclaration
|
|
341
|
+
| acorn.FunctionExpression
|
|
342
|
+
| acorn.ArrowFunctionExpression {
|
|
343
|
+
return Boolean(
|
|
344
|
+
node &&
|
|
345
|
+
(node.type === 'FunctionDeclaration' ||
|
|
346
|
+
node.type === 'FunctionExpression' ||
|
|
347
|
+
node.type === 'ArrowFunctionExpression'),
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function hasMapResolverAncestor(ancestors: acorn.Node[]): boolean {
|
|
352
|
+
for (let index = 0; index < ancestors.length; index += 1) {
|
|
353
|
+
const node = ancestors[index];
|
|
354
|
+
if (!isFunctionNode(node)) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const parent = index >= 1 ? ancestors[index - 1] : null;
|
|
359
|
+
const grandparent = index >= 2 ? ancestors[index - 2] : null;
|
|
360
|
+
const greatGrandparent = index >= 3 ? ancestors[index - 3] : null;
|
|
361
|
+
|
|
362
|
+
if (
|
|
363
|
+
parent?.type === 'CallExpression' &&
|
|
364
|
+
isCtxDatasetCall(parent) &&
|
|
365
|
+
parent.arguments[2] === node
|
|
366
|
+
) {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (
|
|
371
|
+
parent?.type === 'CallExpression' &&
|
|
372
|
+
isMapBuilderStepCall(parent) &&
|
|
373
|
+
parent.arguments[1] === node
|
|
374
|
+
) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (
|
|
379
|
+
parent?.type === 'Property' &&
|
|
380
|
+
(parent as acorn.Property).value === node &&
|
|
381
|
+
grandparent?.type === 'ObjectExpression' &&
|
|
382
|
+
greatGrandparent?.type === 'CallExpression' &&
|
|
383
|
+
isCtxDatasetCall(greatGrandparent) &&
|
|
384
|
+
greatGrandparent.arguments[2] === grandparent
|
|
385
|
+
) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function isMapBuilderStepCall(node: acorn.Node): node is acorn.CallExpression {
|
|
394
|
+
if (node.type !== 'CallExpression') return false;
|
|
395
|
+
const callee = (node as acorn.CallExpression).callee;
|
|
396
|
+
if (callee.type !== 'MemberExpression') return false;
|
|
397
|
+
if (
|
|
398
|
+
callee.property.type !== 'Identifier' ||
|
|
399
|
+
callee.property.name !== 'withColumn'
|
|
400
|
+
) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
let current: acorn.Node = callee.object as acorn.Node;
|
|
404
|
+
while (current.type === 'CallExpression') {
|
|
405
|
+
if (isCtxDatasetCall(current)) return true;
|
|
406
|
+
const nestedCallee = (current as acorn.CallExpression).callee;
|
|
407
|
+
if (
|
|
408
|
+
nestedCallee.type !== 'MemberExpression' ||
|
|
409
|
+
nestedCallee.property.type !== 'Identifier' ||
|
|
410
|
+
nestedCallee.property.name !== 'withColumn'
|
|
411
|
+
) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
current = nestedCallee.object as acorn.Node;
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const KNOWN_GETTER_NAMES = new Set([
|
|
2
|
+
'company_domain',
|
|
3
|
+
'company_linkedin_url',
|
|
4
|
+
'company_name',
|
|
5
|
+
'domain',
|
|
6
|
+
'email',
|
|
7
|
+
'email_status',
|
|
8
|
+
'first_name',
|
|
9
|
+
'full_name',
|
|
10
|
+
'job_change',
|
|
11
|
+
'job_change_status',
|
|
12
|
+
'job_changed',
|
|
13
|
+
'last_name',
|
|
14
|
+
'linkedin',
|
|
15
|
+
'personal_email',
|
|
16
|
+
'phone',
|
|
17
|
+
'phone_status',
|
|
18
|
+
'status',
|
|
19
|
+
'title',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function renderToolCodegenString(value: string): string {
|
|
23
|
+
return JSON.stringify(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function renderToolRowPathExpression(
|
|
27
|
+
path: string,
|
|
28
|
+
rowName = 'row',
|
|
29
|
+
): string {
|
|
30
|
+
const parts = String(path || '')
|
|
31
|
+
.replace(/\[(\d+)\]/g, '.$1')
|
|
32
|
+
.split('.')
|
|
33
|
+
.map((part) => part.trim())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
if (parts.length === 0) return rowName;
|
|
36
|
+
return parts.reduce((expr, part, index) => {
|
|
37
|
+
if (index === 0) return `${rowName}[${renderToolCodegenString(part)}]`;
|
|
38
|
+
return `(${expr} as Record<string, unknown> | null | undefined)?.[${renderToolCodegenString(part)}]`;
|
|
39
|
+
}, rowName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function renderToolPayloadExpression(
|
|
43
|
+
value: unknown,
|
|
44
|
+
rowName = 'row',
|
|
45
|
+
): string {
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
return `[${value.map((entry) => renderToolPayloadExpression(entry, rowName)).join(', ')}]`;
|
|
48
|
+
}
|
|
49
|
+
if (value && typeof value === 'object') {
|
|
50
|
+
const entries = Object.entries(value as Record<string, unknown>).sort(
|
|
51
|
+
([left], [right]) => left.localeCompare(right),
|
|
52
|
+
);
|
|
53
|
+
return `{ ${entries
|
|
54
|
+
.map(
|
|
55
|
+
([key, entry]) =>
|
|
56
|
+
`${renderToolCodegenString(key)}: ${renderToolPayloadExpression(entry, rowName)}`,
|
|
57
|
+
)
|
|
58
|
+
.join(', ')} }`;
|
|
59
|
+
}
|
|
60
|
+
if (typeof value !== 'string') return JSON.stringify(value);
|
|
61
|
+
|
|
62
|
+
const exact = value.match(/^\{\{\s*([^{}]+?)\s*\}\}$/);
|
|
63
|
+
if (exact) return renderToolRowPathExpression(exact[1] ?? '', rowName);
|
|
64
|
+
|
|
65
|
+
const pieces: string[] = [];
|
|
66
|
+
let cursor = 0;
|
|
67
|
+
for (const match of value.matchAll(/\{\{\s*([^{}]+?)\s*\}\}/g)) {
|
|
68
|
+
const index = match.index ?? 0;
|
|
69
|
+
if (index > cursor) {
|
|
70
|
+
pieces.push(renderToolCodegenString(value.slice(cursor, index)));
|
|
71
|
+
}
|
|
72
|
+
pieces.push(
|
|
73
|
+
`String(${renderToolRowPathExpression(match[1] ?? '', rowName)} ?? '')`,
|
|
74
|
+
);
|
|
75
|
+
cursor = index + match[0].length;
|
|
76
|
+
}
|
|
77
|
+
if (pieces.length === 0) return renderToolCodegenString(value);
|
|
78
|
+
if (cursor < value.length) {
|
|
79
|
+
pieces.push(renderToolCodegenString(value.slice(cursor)));
|
|
80
|
+
}
|
|
81
|
+
return pieces.join(' + ');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function renderExtractedValueGetterExpression(
|
|
85
|
+
toolResultVar: string,
|
|
86
|
+
targetKey: string,
|
|
87
|
+
): string {
|
|
88
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(targetKey)
|
|
89
|
+
? `${toolResultVar}.extractedValues.${targetKey}?.get()`
|
|
90
|
+
: `${toolResultVar}.extractedValues[${renderToolCodegenString(targetKey)}]?.get()`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getterFromLegacyPath(path: string): string | null {
|
|
94
|
+
const last = String(path || '')
|
|
95
|
+
.replace(/\[(\d+)\]/g, '.$1')
|
|
96
|
+
.split('.')
|
|
97
|
+
.map((part) => part.trim())
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.at(-1);
|
|
100
|
+
if (!last) return null;
|
|
101
|
+
const normalized = last.replace(/[^A-Za-z0-9_]/g, '_');
|
|
102
|
+
return KNOWN_GETTER_NAMES.has(normalized) ? normalized : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getterFromLegacyExtractJs(
|
|
106
|
+
extractJs: string | undefined,
|
|
107
|
+
fallbackAlias?: string,
|
|
108
|
+
): string | null {
|
|
109
|
+
const source = extractJs?.trim();
|
|
110
|
+
if (!source) {
|
|
111
|
+
const aliasGetter = fallbackAlias?.replace(/[^A-Za-z0-9_]/g, '_');
|
|
112
|
+
return aliasGetter && KNOWN_GETTER_NAMES.has(aliasGetter)
|
|
113
|
+
? aliasGetter
|
|
114
|
+
: null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const directExtract = source.match(
|
|
118
|
+
/\bextract\(\s*["'][^"']+["']\s*,\s*output_data\s*,\s*["']([^"']+)["']\s*\)/,
|
|
119
|
+
);
|
|
120
|
+
if (directExtract?.[1]) {
|
|
121
|
+
return getterFromLegacyPath(
|
|
122
|
+
directExtract[1] === 'linkedin_url' ? 'linkedin' : directExtract[1],
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const pick = source.match(
|
|
127
|
+
/\b(?:pick|extract|target)\(\s*(\[[\s\S]*?\]|["'][^"']+["'])\s*\)/,
|
|
128
|
+
);
|
|
129
|
+
if (!pick?.[1]) return null;
|
|
130
|
+
try {
|
|
131
|
+
const parsed = JSON.parse(pick[1].replace(/'/g, '"')) as unknown;
|
|
132
|
+
const paths = Array.isArray(parsed) ? parsed : [parsed];
|
|
133
|
+
for (const path of paths) {
|
|
134
|
+
if (typeof path !== 'string') continue;
|
|
135
|
+
const getter = getterFromLegacyPath(path);
|
|
136
|
+
if (getter) return getter;
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|