deepline 0.1.54 → 0.1.56

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.
@@ -2,7 +2,14 @@ import { createHash } from 'node:crypto';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { mkdir, readFile, realpath, stat, writeFile } from 'node:fs/promises';
4
4
  import { tmpdir } from 'node:os';
5
- import { basename, dirname, extname, isAbsolute, join, resolve } from 'node:path';
5
+ import {
6
+ basename,
7
+ dirname,
8
+ extname,
9
+ isAbsolute,
10
+ join,
11
+ resolve,
12
+ } from 'node:path';
6
13
  import { builtinModules } from 'node:module';
7
14
  import { build, type Message, type Plugin } from 'esbuild';
8
15
  import {
@@ -37,11 +44,23 @@ const PLAY_ARTIFACT_CACHE_DIR = join(
37
44
  `deepline-play-artifacts-v${PLAY_BUNDLE_CACHE_VERSION}`,
38
45
  );
39
46
  const PLAY_PROXY_NAMESPACE = 'deepline-play-runtime-ref';
40
- const SOURCE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json'];
47
+ const SOURCE_EXTENSIONS = [
48
+ '.ts',
49
+ '.tsx',
50
+ '.mts',
51
+ '.cts',
52
+ '.js',
53
+ '.jsx',
54
+ '.mjs',
55
+ '.cjs',
56
+ '.json',
57
+ ];
41
58
  const WORKERS_PLAY_ENTRY_VIRTUAL = 'deepline-play-entry';
42
59
  const PLAY_SOURCE_FILE_PATTERN = /\.play\.(?:[cm]?[jt]sx?)$/i;
43
60
  const NODE_BUILTIN_SET = new Set(
44
- builtinModules.flatMap((name) => (name.startsWith('node:') ? [name, name.slice(5)] : [name, `node:${name}`])),
61
+ builtinModules.flatMap((name) =>
62
+ name.startsWith('node:') ? [name, name.slice(5)] : [name, `node:${name}`],
63
+ ),
45
64
  );
46
65
 
47
66
  export type {
@@ -87,7 +106,9 @@ export type PlayBundlingAdapter = {
87
106
  sdkWorkersEntryFile: string;
88
107
  workersHarnessEntryFile: string;
89
108
  workersHarnessFilesDir: string;
90
- discoverPackagedLocalFiles(filePath: string): Promise<PlayLocalFileDiscoveryResult>;
109
+ discoverPackagedLocalFiles(
110
+ filePath: string,
111
+ ): Promise<PlayLocalFileDiscoveryResult>;
91
112
  typecheckPlaySource?(input: {
92
113
  sourceCode: string;
93
114
  sourcePath: string;
@@ -229,18 +250,25 @@ function isPlaySourceFile(filePath: string): boolean {
229
250
  function stripCommentsToSpaces(source: string): string {
230
251
  return source
231
252
  .replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, ' '))
232
- .replace(/(^|[^:])\/\/.*$/gm, (match, prefix: string) =>
233
- prefix + ' '.repeat(Math.max(0, match.length - prefix.length)),
253
+ .replace(
254
+ /(^|[^:])\/\/.*$/gm,
255
+ (match, prefix: string) =>
256
+ prefix + ' '.repeat(Math.max(0, match.length - prefix.length)),
234
257
  );
235
258
  }
236
259
 
237
- function lineAndColumnAt(source: string, index: number): { line: number; column: number } {
260
+ function lineAndColumnAt(
261
+ source: string,
262
+ index: number,
263
+ ): { line: number; column: number } {
238
264
  const prefix = source.slice(0, index);
239
265
  const lines = prefix.split('\n');
240
266
  return { line: lines.length, column: lines[lines.length - 1]!.length + 1 };
241
267
  }
242
268
 
243
- function findSourceImportReferences(sourceCode: string): SourceImportReference[] {
269
+ function findSourceImportReferences(
270
+ sourceCode: string,
271
+ ): SourceImportReference[] {
244
272
  const source = stripCommentsToSpaces(sourceCode);
245
273
  const references: SourceImportReference[] = [];
246
274
  const addReference = (
@@ -261,17 +289,29 @@ function findSourceImportReferences(sourceCode: string): SourceImportReference[]
261
289
  const staticImportPattern =
262
290
  /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?(['"])([^'"\n]+)\1/g;
263
291
  for (const match of source.matchAll(staticImportPattern)) {
264
- addReference(match[2], match.index! + match[0].lastIndexOf(match[1]!), 'static');
292
+ addReference(
293
+ match[2],
294
+ match.index! + match[0].lastIndexOf(match[1]!),
295
+ 'static',
296
+ );
265
297
  }
266
298
 
267
299
  const dynamicImportPattern = /\bimport\s*\(\s*(['"])([^'"\n]+)\1/g;
268
300
  for (const match of source.matchAll(dynamicImportPattern)) {
269
- addReference(match[2], match.index! + match[0].lastIndexOf(match[1]!), 'dynamic-import');
301
+ addReference(
302
+ match[2],
303
+ match.index! + match[0].lastIndexOf(match[1]!),
304
+ 'dynamic-import',
305
+ );
270
306
  }
271
307
 
272
308
  const requirePattern = /\brequire\s*\(\s*(['"])([^'"\n]+)\1/g;
273
309
  for (const match of source.matchAll(requirePattern)) {
274
- addReference(match[2], match.index! + match[0].lastIndexOf(match[1]!), 'require');
310
+ addReference(
311
+ match[2],
312
+ match.index! + match[0].lastIndexOf(match[1]!),
313
+ 'require',
314
+ );
275
315
  }
276
316
 
277
317
  const literalDynamicImportIndexes = new Set(
@@ -297,18 +337,27 @@ function findSourceImportReferences(sourceCode: string): SourceImportReference[]
297
337
  }
298
338
 
299
339
  return references.sort((left, right) =>
300
- left.line === right.line ? left.column - right.column : left.line - right.line,
340
+ left.line === right.line
341
+ ? left.column - right.column
342
+ : left.line - right.line,
301
343
  );
302
344
  }
303
345
 
304
346
  function unquoteStringLiteral(literal: string): string | null {
305
347
  const trimmed = literal.trim();
306
348
  const quote = trimmed[0];
307
- if ((quote !== '"' && quote !== "'") || trimmed[trimmed.length - 1] !== quote) {
349
+ if (
350
+ (quote !== '"' && quote !== "'") ||
351
+ trimmed[trimmed.length - 1] !== quote
352
+ ) {
308
353
  return null;
309
354
  }
310
355
  try {
311
- return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
356
+ return JSON.parse(
357
+ quote === '"'
358
+ ? trimmed
359
+ : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`,
360
+ );
312
361
  } catch {
313
362
  return trimmed.slice(1, -1);
314
363
  }
@@ -345,7 +394,8 @@ function findMatchingBrace(source: string, openIndex: number): number {
345
394
 
346
395
  export function extractDefinedPlayName(sourceCode: string): string | null {
347
396
  const source = stripCommentsToSpaces(sourceCode);
348
- const callPattern = /(?:\b[A-Za-z_$][\w$]*\s*\.\s*)?\b(?:definePlay|defineWorkflow)\s*\(/g;
397
+ const callPattern =
398
+ /(?:\b[A-Za-z_$][\w$]*\s*\.\s*)?\b(?:definePlay|defineWorkflow)\s*\(/g;
349
399
  for (const match of source.matchAll(callPattern)) {
350
400
  const openParen = match.index! + match[0].length - 1;
351
401
  const firstArgStart = openParen + 1;
@@ -354,7 +404,9 @@ export function extractDefinedPlayName(sourceCode: string): string | null {
354
404
  const argIndex = firstArgStart + firstNonSpace;
355
405
  const quote = source[argIndex];
356
406
  if (quote === '"' || quote === "'") {
357
- const literalMatch = source.slice(argIndex).match(/^(['"])(?:\\.|(?!\1)[\s\S])*\1/);
407
+ const literalMatch = source
408
+ .slice(argIndex)
409
+ .match(/^(['"])(?:\\.|(?!\1)[\s\S])*\1/);
358
410
  const value = literalMatch ? unquoteStringLiteral(literalMatch[0]) : null;
359
411
  if (value?.trim()) return value.trim();
360
412
  }
@@ -362,7 +414,9 @@ export function extractDefinedPlayName(sourceCode: string): string | null {
362
414
  const closeBrace = findMatchingBrace(source, argIndex);
363
415
  if (closeBrace < 0) continue;
364
416
  const objectSource = source.slice(argIndex + 1, closeBrace);
365
- const idMatch = objectSource.match(/(?:^|[,{\s])(?:id|['"]id['"])\s*:\s*(['"])([\s\S]*?)\1/);
417
+ const idMatch = objectSource.match(
418
+ /(?:^|[,{\s])(?:id|['"]id['"])\s*:\s*(['"])([\s\S]*?)\1/,
419
+ );
366
420
  if (idMatch?.[2]?.trim()) {
367
421
  return idMatch[2].trim();
368
422
  }
@@ -526,7 +580,12 @@ function workersNodeBuiltinStubPlugin(): Plugin {
526
580
  // These are the node builtins NOT exposed by Cloudflare's nodejs_compat.
527
581
  // Keep this list narrow — anything supported (path, crypto, etc.) should
528
582
  // pass through as `external: ['node:*']` in the build config.
529
- const UNSUPPORTED = new Set(['node:fs', 'node:fs/promises', 'node:os', 'node:child_process']);
583
+ const UNSUPPORTED = new Set([
584
+ 'node:fs',
585
+ 'node:fs/promises',
586
+ 'node:os',
587
+ 'node:child_process',
588
+ ]);
530
589
  return {
531
590
  name: 'deepline-workers-node-builtin-stub',
532
591
  setup(buildContext) {
@@ -613,7 +672,8 @@ function zodNonEnglishLocaleStubPlugin(): Plugin {
613
672
  // Match any zod v4 locale module path EXCEPT en + en variants.
614
673
  // The filter uses a coarse regex; the namespace handler then
615
674
  // double-checks with a precise basename test before stubbing.
616
- const LOCALE_PATH_FILTER = /[\\/]zod[\\/]v4[\\/]locales[\\/][^\\/]+\.(?:c?js|mjs)$/;
675
+ const LOCALE_PATH_FILTER =
676
+ /[\\/]zod[\\/]v4[\\/]locales[\\/][^\\/]+\.(?:c?js|mjs)$/;
617
677
  const NAMESPACE = 'deepline-zod-locale-stub';
618
678
  return {
619
679
  name: 'deepline-zod-non-english-locale-stub',
@@ -631,16 +691,13 @@ function zodNonEnglishLocaleStubPlugin(): Plugin {
631
691
  // Anything else: stub with an empty default export.
632
692
  return { path: args.path, namespace: NAMESPACE };
633
693
  });
634
- buildContext.onLoad(
635
- { filter: /.*/, namespace: NAMESPACE },
636
- () => ({
637
- // zod locales export a default object literal. Empty object is
638
- // structurally compatible — accessing any locale key returns
639
- // undefined and zod falls back to its built-in English.
640
- contents: 'export default {};',
641
- loader: 'js',
642
- }),
643
- );
694
+ buildContext.onLoad({ filter: /.*/, namespace: NAMESPACE }, () => ({
695
+ // zod locales export a default object literal. Empty object is
696
+ // structurally compatible — accessing any locale key returns
697
+ // undefined and zod falls back to its built-in English.
698
+ contents: 'export default {};',
699
+ loader: 'js',
700
+ }));
644
701
  },
645
702
  };
646
703
  }
@@ -735,7 +792,10 @@ function importedPlayProxyPlugin(
735
792
  }
736
793
 
737
794
  const dependenciesByPath = new Map(
738
- importedPlayDependencies.map((dependency) => [dependency.filePath, dependency]),
795
+ importedPlayDependencies.map((dependency) => [
796
+ dependency.filePath,
797
+ dependency,
798
+ ]),
739
799
  );
740
800
 
741
801
  return {
@@ -759,19 +819,23 @@ function importedPlayProxyPlugin(
759
819
  };
760
820
  });
761
821
 
762
- buildContext.onLoad({ filter: /.*/, namespace: PLAY_PROXY_NAMESPACE }, async (args) => {
763
- const dependency = (args.pluginData as ImportedPlayDependency | undefined)
764
- ?? dependenciesByPath.get(args.path);
765
- if (!dependency) {
766
- return null;
767
- }
822
+ buildContext.onLoad(
823
+ { filter: /.*/, namespace: PLAY_PROXY_NAMESPACE },
824
+ async (args) => {
825
+ const dependency =
826
+ (args.pluginData as ImportedPlayDependency | undefined) ??
827
+ dependenciesByPath.get(args.path);
828
+ if (!dependency) {
829
+ return null;
830
+ }
768
831
 
769
- return {
770
- contents: buildImportedPlayProxyModule(dependency.playName),
771
- loader: 'ts',
772
- resolveDir: dirname(args.path),
773
- };
774
- });
832
+ return {
833
+ contents: buildImportedPlayProxyModule(dependency.playName),
834
+ loader: 'ts',
835
+ resolveDir: dirname(args.path),
836
+ };
837
+ },
838
+ );
775
839
  },
776
840
  };
777
841
  }
@@ -785,21 +849,32 @@ async function fileExists(filePath: string): Promise<boolean> {
785
849
  }
786
850
  }
787
851
 
788
- async function resolveLocalImport(fromFile: string, specifier: string): Promise<string> {
852
+ async function resolveLocalImport(
853
+ fromFile: string,
854
+ specifier: string,
855
+ ): Promise<string> {
789
856
  if (specifier.startsWith('file:')) {
790
857
  return normalizeLocalPath(new URL(specifier).pathname);
791
858
  }
792
859
 
793
- const base = isAbsolute(specifier) ? resolve(specifier) : resolve(dirname(fromFile), specifier);
860
+ const base = isAbsolute(specifier)
861
+ ? resolve(specifier)
862
+ : resolve(dirname(fromFile), specifier);
794
863
  const candidates: string[] = [base];
795
864
  const explicitExtension = extname(base).toLowerCase();
796
865
 
797
866
  if (!explicitExtension) {
798
- candidates.push(...SOURCE_EXTENSIONS.map((extension) => `${base}${extension}`));
799
- candidates.push(...SOURCE_EXTENSIONS.map((extension) => join(base, `index${extension}`)));
867
+ candidates.push(
868
+ ...SOURCE_EXTENSIONS.map((extension) => `${base}${extension}`),
869
+ );
870
+ candidates.push(
871
+ ...SOURCE_EXTENSIONS.map((extension) => join(base, `index${extension}`)),
872
+ );
800
873
  } else if (['.js', '.jsx', '.mjs', '.cjs'].includes(explicitExtension)) {
801
874
  const stem = base.slice(0, -explicitExtension.length);
802
- candidates.push(...SOURCE_EXTENSIONS.map((extension) => `${stem}${extension}`));
875
+ candidates.push(
876
+ ...SOURCE_EXTENSIONS.map((extension) => `${stem}${extension}`),
877
+ );
803
878
  }
804
879
 
805
880
  for (const candidate of candidates) {
@@ -808,7 +883,9 @@ async function resolveLocalImport(fromFile: string, specifier: string): Promise<
808
883
  }
809
884
  }
810
885
 
811
- throw new Error(`Could not resolve local import "${specifier}" from ${fromFile}`);
886
+ throw new Error(
887
+ `Could not resolve local import "${specifier}" from ${fromFile}`,
888
+ );
812
889
  }
813
890
 
814
891
  function resolvePackageImport(
@@ -876,7 +953,9 @@ async function analyzeSourceGraph(
876
953
  }
877
954
 
878
955
  if (NODE_BUILTIN_SET.has(specifier)) {
879
- nodeBuiltins.add(specifier.startsWith('node:') ? specifier : `node:${specifier}`);
956
+ nodeBuiltins.add(
957
+ specifier.startsWith('node:') ? specifier : `node:${specifier}`,
958
+ );
880
959
  return;
881
960
  }
882
961
 
@@ -914,7 +993,11 @@ async function analyzeSourceGraph(
914
993
  );
915
994
  }
916
995
 
917
- const packageImport = resolvePackageImport(specifier, absolutePath, adapter);
996
+ const packageImport = resolvePackageImport(
997
+ specifier,
998
+ absolutePath,
999
+ adapter,
1000
+ );
918
1001
  packages.set(packageImport.name, packageImport.version);
919
1002
  };
920
1003
 
@@ -976,8 +1059,9 @@ async function analyzeSourceGraph(
976
1059
  .sort((left, right) => left.name.localeCompare(right.name)),
977
1060
  },
978
1061
  playName,
979
- importedPlayDependencies: [...importedPlayDependencies.values()]
980
- .sort((left, right) => left.filePath.localeCompare(right.filePath)),
1062
+ importedPlayDependencies: [...importedPlayDependencies.values()].sort(
1063
+ (left, right) => left.filePath.localeCompare(right.filePath),
1064
+ ),
981
1065
  };
982
1066
  }
983
1067
 
@@ -995,14 +1079,19 @@ async function computeWorkersHarnessFingerprintWithAdapter(
995
1079
  adapter: PlayBundlingAdapter,
996
1080
  ): Promise<string> {
997
1081
  const { readdir } = await import('node:fs/promises');
998
- const entries = await readdir(adapter.workersHarnessFilesDir, { withFileTypes: true });
1082
+ const entries = await readdir(adapter.workersHarnessFilesDir, {
1083
+ withFileTypes: true,
1084
+ });
999
1085
  const tsFiles = entries
1000
1086
  .filter((e) => e.isFile() && /\.[cm]?ts$/.test(e.name))
1001
1087
  .map((e) => e.name)
1002
1088
  .sort();
1003
1089
  const parts: Array<{ name: string; hash: string }> = [];
1004
1090
  for (const name of tsFiles) {
1005
- const contents = await readFile(join(adapter.workersHarnessFilesDir, name), 'utf-8');
1091
+ const contents = await readFile(
1092
+ join(adapter.workersHarnessFilesDir, name),
1093
+ 'utf-8',
1094
+ );
1006
1095
  parts.push({ name, hash: sha256(contents) });
1007
1096
  }
1008
1097
  return sha256(JSON.stringify(parts));
@@ -1137,6 +1226,18 @@ type EsbuildBundleOutput = {
1137
1226
  outputExtension: 'cjs' | 'mjs';
1138
1227
  };
1139
1228
 
1229
+ type PlayArtifactTargetAdapter = {
1230
+ artifactKind: PlayArtifactKind;
1231
+ codeFormat: PlayBundleArtifact['codeFormat'];
1232
+ includeWorkersHarnessInGraphHash: boolean;
1233
+ runEsbuild(input: {
1234
+ entryFile: string;
1235
+ importedPlayDependencies: ImportedPlayDependency[];
1236
+ adapter: PlayBundlingAdapter;
1237
+ exportName: string;
1238
+ }): Promise<EsbuildBundleOutput | string[]>;
1239
+ };
1240
+
1140
1241
  async function runEsbuildForCjsNode(
1141
1242
  entryFile: string,
1142
1243
  importedPlayDependencies: ImportedPlayDependency[],
@@ -1268,12 +1369,60 @@ async function runEsbuildForEsmWorkers(
1268
1369
  };
1269
1370
  }
1270
1371
 
1372
+ const PLAY_ARTIFACT_TARGET_ADAPTERS: Record<
1373
+ PlayArtifactKind,
1374
+ PlayArtifactTargetAdapter
1375
+ > = {
1376
+ [PLAY_ARTIFACT_KINDS.cjsNode20]: {
1377
+ artifactKind: PLAY_ARTIFACT_KINDS.cjsNode20,
1378
+ codeFormat: 'cjs_module',
1379
+ includeWorkersHarnessInGraphHash: false,
1380
+ runEsbuild: ({
1381
+ entryFile,
1382
+ importedPlayDependencies,
1383
+ adapter,
1384
+ exportName,
1385
+ }) =>
1386
+ runEsbuildForCjsNode(
1387
+ entryFile,
1388
+ importedPlayDependencies,
1389
+ adapter,
1390
+ exportName,
1391
+ ),
1392
+ },
1393
+ [PLAY_ARTIFACT_KINDS.esmWorkers]: {
1394
+ artifactKind: PLAY_ARTIFACT_KINDS.esmWorkers,
1395
+ codeFormat: 'esm_module',
1396
+ includeWorkersHarnessInGraphHash: true,
1397
+ runEsbuild: ({
1398
+ entryFile,
1399
+ importedPlayDependencies,
1400
+ adapter,
1401
+ exportName,
1402
+ }) =>
1403
+ runEsbuildForEsmWorkers(
1404
+ entryFile,
1405
+ importedPlayDependencies,
1406
+ adapter,
1407
+ exportName,
1408
+ ),
1409
+ },
1410
+ };
1411
+
1412
+ function resolvePlayArtifactTargetAdapter(
1413
+ artifactKind: PlayArtifactKind,
1414
+ ): PlayArtifactTargetAdapter {
1415
+ return PLAY_ARTIFACT_TARGET_ADAPTERS[artifactKind];
1416
+ }
1417
+
1271
1418
  export async function bundlePlayFile(
1272
1419
  filePath: string,
1273
1420
  options: BundlePlayFileCoreOptions,
1274
1421
  ): Promise<BundledPlayFileResult> {
1275
1422
  const adapter = options.adapter;
1276
- const target: PlayArtifactKind = options.target ?? PLAY_ARTIFACT_KINDS.cjsNode20;
1423
+ const target: PlayArtifactKind =
1424
+ options.target ?? PLAY_ARTIFACT_KINDS.cjsNode20;
1425
+ const targetAdapter = resolvePlayArtifactTargetAdapter(target);
1277
1426
  const exportName = options.exportName?.trim() || 'default';
1278
1427
  assertValidExportName(exportName);
1279
1428
  const absolutePath = await normalizeLocalPath(filePath);
@@ -1291,8 +1440,9 @@ export async function bundlePlayFile(
1291
1440
  // changes and gets a fresh deploy. This is the file-watcher-equivalent
1292
1441
  // for hot-reload of the harness: editing entry.ts → next play run
1293
1442
  // re-bundles + redeploys automatically.
1294
- if (target === PLAY_ARTIFACT_KINDS.esmWorkers) {
1295
- const harnessFingerprint = await computeWorkersHarnessFingerprintWithAdapter(adapter);
1443
+ if (targetAdapter.includeWorkersHarnessInGraphHash) {
1444
+ const harnessFingerprint =
1445
+ await computeWorkersHarnessFingerprintWithAdapter(adapter);
1296
1446
  analysis.graphHash = sha256(
1297
1447
  `${analysis.graphHash}\nworkers-harness:${harnessFingerprint}`,
1298
1448
  );
@@ -1303,7 +1453,9 @@ export async function bundlePlayFile(
1303
1453
  sourcePath: absolutePath,
1304
1454
  importedFilePaths: [
1305
1455
  ...analysis.importPolicy.localFiles,
1306
- ...analysis.importedPlayDependencies.map((dependency) => dependency.filePath),
1456
+ ...analysis.importedPlayDependencies.map(
1457
+ (dependency) => dependency.filePath,
1458
+ ),
1307
1459
  ],
1308
1460
  })) ?? []),
1309
1461
  ];
@@ -1318,8 +1470,13 @@ export async function bundlePlayFile(
1318
1470
  // Cache lookup happens after validation because a bundle cache hit is keyed
1319
1471
  // by source and target, while cloud descriptor typecheck results also depend
1320
1472
  // on generated tool metadata.
1321
- const cachedArtifact = await readArtifactCache(analysis.graphHash, target, adapter);
1322
- const discoveredFiles = await adapter.discoverPackagedLocalFiles(absolutePath);
1473
+ const cachedArtifact = await readArtifactCache(
1474
+ analysis.graphHash,
1475
+ target,
1476
+ adapter,
1477
+ );
1478
+ const discoveredFiles =
1479
+ await adapter.discoverPackagedLocalFiles(absolutePath);
1323
1480
  if (cachedArtifact) {
1324
1481
  const cachedArtifactSizeError = getBundleSizeError(
1325
1482
  absolutePath,
@@ -1347,10 +1504,12 @@ export async function bundlePlayFile(
1347
1504
  };
1348
1505
  }
1349
1506
 
1350
- const buildOutcome =
1351
- target === PLAY_ARTIFACT_KINDS.esmWorkers
1352
- ? await runEsbuildForEsmWorkers(absolutePath, analysis.importedPlayDependencies, adapter, exportName)
1353
- : await runEsbuildForCjsNode(absolutePath, analysis.importedPlayDependencies, adapter, exportName);
1507
+ const buildOutcome = await targetAdapter.runEsbuild({
1508
+ entryFile: absolutePath,
1509
+ importedPlayDependencies: analysis.importedPlayDependencies,
1510
+ adapter,
1511
+ exportName,
1512
+ });
1354
1513
  if (Array.isArray(buildOutcome)) {
1355
1514
  return {
1356
1515
  success: false,
@@ -1380,11 +1539,8 @@ export async function bundlePlayFile(
1380
1539
  };
1381
1540
  }
1382
1541
 
1383
- const codeFormat: PlayBundleArtifact['codeFormat'] =
1384
- target === PLAY_ARTIFACT_KINDS.esmWorkers ? 'esm_module' : 'cjs_module';
1385
-
1386
1542
  const artifact: PlayBundleArtifact = {
1387
- codeFormat,
1543
+ codeFormat: targetAdapter.codeFormat,
1388
1544
  artifactKind: target,
1389
1545
  entryFile: absolutePath,
1390
1546
  virtualFilename,
@@ -1416,7 +1572,7 @@ export async function bundlePlayFile(
1416
1572
  } catch (error) {
1417
1573
  if (error && typeof error === 'object' && 'errors' in error) {
1418
1574
  const errors = Array.isArray((error as { errors?: Message[] }).errors)
1419
- ? ((error as { errors: Message[] }).errors).map(formatEsbuildMessage)
1575
+ ? (error as { errors: Message[] }).errors.map(formatEsbuildMessage)
1420
1576
  : ['Play bundling failed.'];
1421
1577
  return {
1422
1578
  success: false,
@@ -55,14 +55,34 @@ export type PlayDatasetInput<T> =
55
55
  | AsyncIterable<T>
56
56
  | PlayDataset<T>;
57
57
 
58
+ /**
59
+ * Durable handle for rows produced by `ctx.csv(...)` or `ctx.map(...).run()`.
60
+ *
61
+ * A `PlayDataset` is not a normal in-memory array. It points at runtime-managed
62
+ * rows, usually backed by persisted sheet storage, and carries metadata such as
63
+ * dataset kind, dataset id, table namespace, count, and preview rows.
64
+ *
65
+ * Pass dataset handles directly into later `ctx.map(...)` stages by default so
66
+ * Deepline keeps row progress, retries, memory use, and table output under
67
+ * runtime control. Use `count()` and `peek()` for bounded inspection. Use
68
+ * `materialize(limit)` or async iteration only when the dataset is intentionally
69
+ * small and bounded.
70
+ */
58
71
  export interface PlayDataset<T> extends AsyncIterable<T> {
59
72
  readonly [PLAY_DATASET_BRAND]: true;
73
+ /** Dataset kind. */
60
74
  readonly datasetKind: PlayDatasetKind;
75
+ /** Dataset id. */
61
76
  readonly datasetId: string;
77
+ /** Backing store info. */
62
78
  readonly backing?: PlayDatasetBacking;
79
+ /** Display label. */
63
80
  readonly sourceLabel?: string | null;
81
+ /** Runtime table name. */
64
82
  readonly tableNamespace?: string | null;
83
+ /** Row count. */
65
84
  count(): Promise<number>;
85
+ /** Preview rows. */
66
86
  peek(limit?: number): Promise<T[]>;
67
87
  /**
68
88
  * Explicit escape hatch for bounded result sets.
@@ -310,7 +330,11 @@ export async function materializePlayDatasetInput<T>(
310
330
  input: PlayDatasetInput<T>,
311
331
  ): Promise<T[]> {
312
332
  if (isPlayDataset(input)) {
313
- return await input.materialize();
333
+ const rows: T[] = [];
334
+ for await (const row of input) {
335
+ rows.push(row);
336
+ }
337
+ return rows;
314
338
  }
315
339
 
316
340
  if (Array.isArray(input)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {