deepline 0.1.10 → 0.1.12

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.
Files changed (34) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/index.js +509 -353
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/cli/index.mjs +513 -358
  5. package/dist/cli/index.mjs.map +1 -1
  6. package/dist/index.d.mts +250 -305
  7. package/dist/index.d.ts +250 -305
  8. package/dist/index.js +174 -286
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +174 -285
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +23 -13
  13. package/dist/repo/apps/play-runner-workers/src/entry.ts +581 -1220
  14. package/dist/repo/sdk/src/cli/commands/play.ts +381 -247
  15. package/dist/repo/sdk/src/cli/commands/tools.ts +1 -1
  16. package/dist/repo/sdk/src/cli/dataset-stats.ts +86 -12
  17. package/dist/repo/sdk/src/client.ts +54 -51
  18. package/dist/repo/sdk/src/index.ts +7 -16
  19. package/dist/repo/sdk/src/play.ts +122 -135
  20. package/dist/repo/sdk/src/plays/bundle-play-file.ts +6 -3
  21. package/dist/repo/sdk/src/tool-output.ts +0 -111
  22. package/dist/repo/sdk/src/types.ts +2 -0
  23. package/dist/repo/sdk/src/version.ts +1 -1
  24. package/dist/repo/sdk/src/worker-play-entry.ts +3 -0
  25. package/dist/repo/shared_libs/play-runtime/context.ts +510 -267
  26. package/dist/repo/shared_libs/play-runtime/csv-rename.ts +180 -0
  27. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +13 -1
  28. package/dist/repo/shared_libs/play-runtime/tool-result.ts +139 -114
  29. package/dist/repo/shared_libs/plays/bundling/index.ts +68 -5
  30. package/dist/repo/shared_libs/plays/compiler-manifest.ts +1 -1
  31. package/dist/repo/shared_libs/plays/dataset.ts +1 -1
  32. package/dist/repo/shared_libs/plays/runtime-validation.ts +8 -28
  33. package/package.json +1 -1
  34. package/dist/repo/apps/play-runner-workers/src/runtime/tool-result.ts +0 -184
@@ -99,6 +99,15 @@ export type PlayBundlingAdapter = {
99
99
  warnAboutNonDevelopmentBundling?(filePath: string): void;
100
100
  };
101
101
 
102
+ function assertValidExportName(exportName: string): void {
103
+ if (exportName === 'default') return;
104
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(exportName)) {
105
+ throw new Error(
106
+ `Invalid play export name "${exportName}". Named prebuilt exports must be valid JavaScript identifiers.`,
107
+ );
108
+ }
109
+ }
110
+
102
111
  export type BundledPlayFileSuccess = {
103
112
  success: true;
104
113
  artifact: PlayBundleArtifact;
@@ -409,6 +418,32 @@ function workersPlayEntryAliasPlugin(playFilePath: string): Plugin {
409
418
  };
410
419
  }
411
420
 
421
+ function workersNamedPlayEntryAliasPlugin(
422
+ playFilePath: string,
423
+ exportName: string,
424
+ ): Plugin {
425
+ return {
426
+ name: 'deepline-workers-named-play-entry-alias',
427
+ setup(buildContext) {
428
+ buildContext.onResolve(
429
+ { filter: new RegExp(`^${WORKERS_PLAY_ENTRY_VIRTUAL}$`) },
430
+ () => ({
431
+ path: `${playFilePath}.${exportName}.entry.ts`,
432
+ namespace: 'deepline-named-play-entry',
433
+ }),
434
+ );
435
+ buildContext.onLoad(
436
+ { filter: /.*/, namespace: 'deepline-named-play-entry' },
437
+ () => ({
438
+ contents: `export { ${exportName} as default } from ${JSON.stringify(playFilePath)};\n`,
439
+ loader: 'ts',
440
+ resolveDir: dirname(playFilePath),
441
+ }),
442
+ );
443
+ },
444
+ };
445
+ }
446
+
412
447
  /**
413
448
  * Cloudflare Workers' `nodejs_compat` flag covers most node builtins (path,
414
449
  * crypto, buffer, async_hooks, ...) but NOT `node:fs` / `node:fs/promises` /
@@ -1068,6 +1103,7 @@ export type BundlePlayFileOptions = {
1068
1103
  * kind is cached independently on disk.
1069
1104
  */
1070
1105
  target?: PlayArtifactKind;
1106
+ exportName?: string;
1071
1107
  };
1072
1108
 
1073
1109
  export type BundlePlayFileCoreOptions = BundlePlayFileOptions & {
@@ -1084,11 +1120,25 @@ async function runEsbuildForCjsNode(
1084
1120
  entryFile: string,
1085
1121
  importedPlayDependencies: ImportedPlayDependency[],
1086
1122
  adapter: PlayBundlingAdapter,
1123
+ exportName: string,
1087
1124
  ): Promise<EsbuildBundleOutput | string[]> {
1088
1125
  const sdkAliasPlugin = localSdkAliasPlugin(adapter);
1089
1126
  const playProxyPlugin = importedPlayProxyPlugin(importedPlayDependencies);
1127
+ const namedExportShim =
1128
+ exportName === 'default'
1129
+ ? null
1130
+ : `export { ${exportName} as default } from ${JSON.stringify(entryFile)};\n`;
1090
1131
  const result = await build({
1091
- entryPoints: [entryFile],
1132
+ ...(namedExportShim
1133
+ ? {
1134
+ stdin: {
1135
+ contents: namedExportShim,
1136
+ resolveDir: dirname(entryFile),
1137
+ sourcefile: `${basename(entryFile)}.${exportName}.entry.ts`,
1138
+ loader: 'ts' as const,
1139
+ },
1140
+ }
1141
+ : { entryPoints: [entryFile] }),
1092
1142
  absWorkingDir: adapter.projectRoot,
1093
1143
  bundle: true,
1094
1144
  format: 'cjs',
@@ -1121,10 +1171,14 @@ async function runEsbuildForEsmWorkers(
1121
1171
  playEntryFile: string,
1122
1172
  importedPlayDependencies: ImportedPlayDependency[],
1123
1173
  adapter: PlayBundlingAdapter,
1174
+ exportName: string,
1124
1175
  ): Promise<EsbuildBundleOutput | string[]> {
1125
1176
  const sdkAliasPlugin = localSdkAliasPlugin(adapter, { workersRuntime: true });
1126
1177
  const playProxyPlugin = importedPlayProxyPlugin(importedPlayDependencies);
1127
- const playEntryAlias = workersPlayEntryAliasPlugin(playEntryFile);
1178
+ const playEntryAlias =
1179
+ exportName === 'default'
1180
+ ? workersPlayEntryAliasPlugin(playEntryFile)
1181
+ : workersNamedPlayEntryAliasPlugin(playEntryFile, exportName);
1128
1182
  const result = await build({
1129
1183
  // Entry is the Workers harness; it imports the play via the virtual
1130
1184
  // `deepline-play-entry` alias resolved by workersPlayEntryAliasPlugin.
@@ -1199,11 +1253,16 @@ export async function bundlePlayFile(
1199
1253
  ): Promise<BundledPlayFileResult> {
1200
1254
  const adapter = options.adapter;
1201
1255
  const target: PlayArtifactKind = options.target ?? PLAY_ARTIFACT_KINDS.cjsNode20;
1256
+ const exportName = options.exportName?.trim() || 'default';
1257
+ assertValidExportName(exportName);
1202
1258
  const absolutePath = await normalizeLocalPath(filePath);
1203
1259
  adapter.warnAboutNonDevelopmentBundling?.(absolutePath);
1204
1260
 
1205
1261
  try {
1206
1262
  const analysis = await analyzeSourceGraph(absolutePath, adapter);
1263
+ analysis.graphHash = sha256(
1264
+ `${analysis.graphHash}\nentry-export:${exportName}`,
1265
+ );
1207
1266
  // For esm_workers builds, the harness source files (entry.ts +
1208
1267
  // peer DO/coordinator types it imports) are bundled INTO every play
1209
1268
  // artifact. So any harness edit must produce a different graphHash so
@@ -1275,8 +1334,8 @@ export async function bundlePlayFile(
1275
1334
  }
1276
1335
  const buildOutcome =
1277
1336
  target === PLAY_ARTIFACT_KINDS.esmWorkers
1278
- ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter)
1279
- : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter);
1337
+ ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter, exportName)
1338
+ : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter, exportName);
1280
1339
  if (Array.isArray(buildOutcome)) {
1281
1340
  return {
1282
1341
  success: false,
@@ -1287,7 +1346,11 @@ export async function bundlePlayFile(
1287
1346
  const { bundledCode, sourceMapText, outputExtension } = buildOutcome;
1288
1347
 
1289
1348
  const normalizedSourceMap = normalizeSourceMapForRuntime(sourceMapText);
1290
- const virtualFilename = `/virtual/deepline-plays/${analysis.graphHash}/${basename(absolutePath).replace(/\.[^.]+$/, '')}.${outputExtension}`;
1349
+ const virtualBaseName =
1350
+ exportName === 'default'
1351
+ ? basename(absolutePath).replace(/\.[^.]+$/, '')
1352
+ : `${basename(absolutePath).replace(/\.[^.]+$/, '')}.${exportName}`;
1353
+ const virtualFilename = `/virtual/deepline-plays/${analysis.graphHash}/${virtualBaseName}.${outputExtension}`;
1291
1354
  const executableCode = `${bundledCode}\n//# sourceMappingURL=${basename(virtualFilename)}.map\n`;
1292
1355
  const bundleSizeError = getBundleSizeError(
1293
1356
  absolutePath,
@@ -179,7 +179,7 @@ export type PlayRuntimeManifest = {
179
179
  compiledAt: number;
180
180
  compilerVersion: string;
181
181
  sourceCode?: string | null;
182
- dynamicWorkerCode?: string | null;
182
+ bundledCode?: string | null;
183
183
  maxCreditsPerRun?: number | null;
184
184
  };
185
185
 
@@ -255,7 +255,7 @@ export function createPlayDataset<T>(
255
255
  metadata?.datasetId ??
256
256
  `${metadata?.kind ?? 'map'}:${metadata?.tableNamespace ?? metadata?.sourceLabel ?? 'inline'}`,
257
257
  count: materializedRows.length,
258
- previewRows: materializedRows.slice(0, 10),
258
+ previewRows: materializedRows.slice(0, 5),
259
259
  sourceLabel: metadata?.sourceLabel ?? null,
260
260
  tableNamespace: metadata?.tableNamespace ?? null,
261
261
  resolvers: {
@@ -233,14 +233,14 @@ function extractValidatedMapTableNamespace(
233
233
  const rowsArgument = node.arguments[1];
234
234
  if (!keyArgument) {
235
235
  errors.push(
236
- 'ctx.map() requires a string literal map key as the first argument, e.g. ctx.map("leads", rows, { key: "lead_id" }).step("company", row => row.domain).run(...).',
236
+ 'ctx.map() requires a string literal map key as the first argument, e.g. ctx.map("leads", rows).step("company", row => row.domain).run({ key: "lead_id" }).',
237
237
  );
238
238
  return null;
239
239
  }
240
240
 
241
241
  if (!rowsArgument) {
242
242
  errors.push(
243
- 'ctx.map() requires rows as the second argument, e.g. ctx.map("leads", rows, { key: "lead_id" }).step("company", row => row.domain).run(...).',
243
+ 'ctx.map() requires rows as the second argument, e.g. ctx.map("leads", rows).step("company", row => row.domain).run({ key: "lead_id" }).',
244
244
  );
245
245
  return null;
246
246
  }
@@ -264,7 +264,7 @@ function extractValidatedMapTableNamespace(
264
264
  } catch (error) {
265
265
  errors.push(
266
266
  error instanceof Error
267
- ? `${error.message} Example: ctx.map("leads", rows, { key: "lead_id" }).step("company", row => row.domain).run({ description: "..." }).`
267
+ ? `${error.message} Example: ctx.map("leads", rows).step("company", row => row.domain).run({ key: "lead_id", description: "..." }).`
268
268
  : `ctx.map() key must normalize to <= ${MAP_KEY_NAMESPACE_MAX_LENGTH} characters.`,
269
269
  );
270
270
  return null;
@@ -272,7 +272,7 @@ function extractValidatedMapTableNamespace(
272
272
 
273
273
  if (rowsArgument.type === 'ObjectExpression') {
274
274
  errors.push(
275
- 'ctx.map() key must not be an object. Use ctx.map("leads", rows, { key: "lead_id" }).step(...).run(...).',
275
+ 'ctx.map() key must not be an object. Use ctx.map("leads", rows).step(...).run({ key: "lead_id" }).',
276
276
  );
277
277
  return null;
278
278
  }
@@ -285,36 +285,16 @@ function extractValidatedMapTableNamespace(
285
285
  }
286
286
 
287
287
  const optionsArgument = node.arguments[2];
288
- if (optionsArgument && !isMapDefinitionOptionsNode(optionsArgument)) {
289
- errors.push('ctx.map() accepts key, rows, and map options only.');
288
+ if (optionsArgument) {
289
+ errors.push(
290
+ 'ctx.map() accepts only a map key and rows. Add steps with .step(...) and pass row identity options to .run({ key: "lead_id" }).',
291
+ );
290
292
  return null;
291
293
  }
292
294
 
293
295
  return keyArgument.value.trim();
294
296
  }
295
297
 
296
- function isMapDefinitionOptionsNode(node: acorn.Node): boolean {
297
- if (node.type !== 'ObjectExpression') return false;
298
- const properties = (
299
- node as acorn.Node & {
300
- properties: acorn.Node[];
301
- }
302
- ).properties;
303
- return properties.every((property) => {
304
- if (property.type !== 'Property') return false;
305
- const key = (property as acorn.Node & { key: acorn.Node }).key as
306
- | (acorn.Node & { type: 'Identifier'; name: string })
307
- | (acorn.Node & { type: 'Literal'; value: unknown });
308
- const name =
309
- key.type === 'Identifier'
310
- ? key.name
311
- : key.type === 'Literal' && typeof key.value === 'string'
312
- ? key.value
313
- : null;
314
- return name === 'key' || name === 'staleAfterSeconds';
315
- });
316
- }
317
-
318
298
  function isCtxMapCall(node: acorn.Node): node is acorn.CallExpression {
319
299
  if (node.type !== 'CallExpression') {
320
300
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,184 +0,0 @@
1
- /**
2
- * Per-play Worker tool result wrapper.
3
- *
4
- * User play code expects ergonomic getters like `result.getEmail()`. Keep this
5
- * local and dependency-free so those getters do not pull the shared runtime
6
- * module graph into every dynamic Worker.
7
- */
8
-
9
- export type ToolResultMetadataInput = {
10
- toolId: string;
11
- resultIdentityGetters?: Record<string, readonly string[]>;
12
- listExtractorPaths?: readonly string[];
13
- listIdentityGetters?: Record<string, readonly string[]>;
14
- };
15
-
16
- type ToolResultExecutionMetadata = {
17
- idempotent: true;
18
- cached: boolean;
19
- source: 'live' | 'checkpoint' | 'cache';
20
- cacheKey?: string;
21
- };
22
-
23
- export type ToolExecuteResult<TResult = unknown> = {
24
- status: string;
25
- result: TResult;
26
- _metadata: {
27
- toolId: string;
28
- execution: ToolResultExecutionMetadata;
29
- targets: Record<string, { value: unknown; path: string }>;
30
- lists: Record<string, { path: string; count: number | null; keys: Record<string, string> }>;
31
- };
32
- get<T = unknown>(target: string): T | null;
33
- getEmail(): string | null;
34
- getPhone(): string | null;
35
- getLinkedin(): string | null;
36
- list<T = Record<string, unknown>>(name?: string): T[] | null;
37
- listPick<const TKeys extends readonly string[]>(
38
- keys: TKeys,
39
- name?: string,
40
- ): Array<Record<TKeys[number], unknown>> | null;
41
- listKeys(name?: string): Record<string, string>;
42
- };
43
-
44
- type PathSegment = string | number;
45
-
46
- export function createToolExecuteResult<TResult = unknown>(input: {
47
- status: string;
48
- result: TResult;
49
- metadata: ToolResultMetadataInput;
50
- execution: ToolResultExecutionMetadata;
51
- }): ToolExecuteResult<TResult> {
52
- const targets: ToolExecuteResult<TResult>['_metadata']['targets'] = {};
53
- for (const [target, paths] of Object.entries(
54
- input.metadata.resultIdentityGetters ?? {},
55
- )) {
56
- const path = paths
57
- .map((rawPath) => String(rawPath || '').trim().replace(/^result\./, ''))
58
- .find((candidate) => candidate && getAtPath(input.result, candidate) != null);
59
- if (path) {
60
- targets[target] = { value: getAtPath(input.result, path), path };
61
- }
62
- }
63
- if (Object.keys(targets).length === 0 && isRecordLike(input.result)) {
64
- for (const target of ['email', 'phone', 'linkedin', 'domain', 'status']) {
65
- if (target in input.result && input.result[target] != null) {
66
- targets[target] = { value: input.result[target], path: target };
67
- }
68
- }
69
- }
70
-
71
- const lists: ToolExecuteResult<TResult>['_metadata']['lists'] = {};
72
- for (const rawPath of input.metadata.listExtractorPaths ?? []) {
73
- const path = String(rawPath || '').trim().replace(/^result\./, '');
74
- const rows = normalizeRows(getAtPath(input.result, path));
75
- if (!path || !rows) continue;
76
- const name = path.split('.').filter(Boolean).at(-1)?.replace(/\[\d+\]$/, '') || path;
77
- const keys = Object.fromEntries(
78
- Object.keys(rows[0] ?? {}).map((key) => [key, key]),
79
- );
80
- lists[name] = { path, count: rows.length, keys };
81
- }
82
-
83
- return {
84
- status: input.status,
85
- result: input.result,
86
- _metadata: {
87
- toolId: input.metadata.toolId,
88
- execution: input.execution,
89
- targets,
90
- lists,
91
- },
92
- get<T = unknown>(target: string): T | null {
93
- return (this._metadata.targets[target]?.value as T | undefined) ?? null;
94
- },
95
- getEmail(): string | null {
96
- const value = this.get('email');
97
- return typeof value === 'string' && value.trim() ? value : null;
98
- },
99
- getPhone(): string | null {
100
- const value = this.get('phone');
101
- return typeof value === 'string' && value.trim() ? value : null;
102
- },
103
- getLinkedin(): string | null {
104
- const value = this.get('linkedin');
105
- return typeof value === 'string' && value.trim() ? value : null;
106
- },
107
- list<T = Record<string, unknown>>(name?: string): T[] | null {
108
- const entryName = name ?? Object.keys(this._metadata.lists)[0];
109
- if (!entryName) return null;
110
- const list = this._metadata.lists[entryName];
111
- return list ? (normalizeRows(getAtPath(this.result, list.path)) as T[] | null) : null;
112
- },
113
- listPick<const TKeys extends readonly string[]>(
114
- keys: TKeys,
115
- name?: string,
116
- ): Array<Record<TKeys[number], unknown>> | null {
117
- const entryName = name ?? Object.keys(this._metadata.lists)[0];
118
- if (!entryName) return null;
119
- const listMetadata = this._metadata.lists[entryName];
120
- if (!listMetadata) return null;
121
- const rows = normalizeRows(getAtPath(this.result, listMetadata.path));
122
- if (!rows) return null;
123
- return rows.map((row) => {
124
- const picked: Record<string, unknown> = {};
125
- for (const key of keys) {
126
- const path = listMetadata.keys[key];
127
- picked[key] = path ? getAtPath(row, path) ?? null : null;
128
- }
129
- return picked as Record<TKeys[number], unknown>;
130
- });
131
- },
132
- listKeys(name?: string): Record<string, string> {
133
- const entryName = name ?? Object.keys(this._metadata.lists)[0];
134
- return entryName ? (this._metadata.lists[entryName]?.keys ?? {}) : {};
135
- },
136
- };
137
- }
138
-
139
- export function isToolExecuteResult(value: unknown): value is ToolExecuteResult {
140
- return (
141
- isRecordLike(value) &&
142
- typeof value.status === 'string' &&
143
- isRecordLike(value._metadata) &&
144
- 'result' in value
145
- );
146
- }
147
-
148
- function parseToolPath(path: string): PathSegment[] {
149
- const segments: PathSegment[] = [];
150
- for (const rawPart of path.split('.').filter(Boolean)) {
151
- const bracketPattern = /([^\[\]]+)|\[(\d+)\]/g;
152
- let matched = false;
153
- for (const match of rawPart.matchAll(bracketPattern)) {
154
- matched = true;
155
- if (match[1]) segments.push(match[1]);
156
- else if (match[2]) segments.push(Number(match[2]));
157
- }
158
- if (!matched) segments.push(rawPart);
159
- }
160
- return segments;
161
- }
162
-
163
- function getAtPath(root: unknown, path: string): unknown {
164
- let current = root;
165
- for (const segment of parseToolPath(path)) {
166
- if (typeof segment === 'number') {
167
- if (!Array.isArray(current)) return undefined;
168
- current = current[segment];
169
- } else {
170
- if (!isRecordLike(current)) return undefined;
171
- current = current[segment];
172
- }
173
- }
174
- return current;
175
- }
176
-
177
- function normalizeRows(value: unknown): Record<string, unknown>[] | null {
178
- if (!Array.isArray(value)) return null;
179
- return value.map((entry) => (isRecordLike(entry) ? entry : { value: entry }));
180
- }
181
-
182
- function isRecordLike(value: unknown): value is Record<string, unknown> {
183
- return value != null && typeof value === 'object' && !Array.isArray(value);
184
- }