deepline 0.1.25 → 0.1.27

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.
@@ -0,0 +1,418 @@
1
+ import {
2
+ applyCsvRenameProjection,
3
+ type CsvRenameOptions,
4
+ } from '../../../../shared_libs/play-runtime/csv-rename';
5
+ import {
6
+ createDeferredPlayDataset,
7
+ isPlayDataset,
8
+ type PlayDataset,
9
+ type PlayDatasetInput,
10
+ type PlayDatasetKind,
11
+ type PlayDatasetWorkProgressSummary,
12
+ } from '../../../../shared_libs/plays/dataset';
13
+
14
+ export const WORKER_DATASET_PREVIEW_ROWS = 5;
15
+ export const WORKER_DATASET_IN_MEMORY_ROWS = 100;
16
+ const STREAM_MATERIALIZE_CHUNK_ROWS = 5_000;
17
+ const STREAM_ITERATE_CHUNK_ROWS = 1_000;
18
+
19
+ export type DatasetRow = Record<string, unknown>;
20
+ export type WorkerDatasetHandle<T extends DatasetRow> = PlayDataset<T>;
21
+ export type WorkerDatasetInput<T extends DatasetRow> = PlayDatasetInput<T>;
22
+
23
+ type DatasetChunkReader<T extends DatasetRow> = (
24
+ chunkSize: number,
25
+ ) => AsyncIterable<T[]>;
26
+
27
+ const datasetChunkReaders = new WeakMap<object, DatasetChunkReader<DatasetRow>>();
28
+ const datasetCountHints = new WeakMap<object, number | null>();
29
+
30
+ function cloneRow<T extends DatasetRow>(row: T): T {
31
+ return { ...row };
32
+ }
33
+
34
+ function registerChunkReader<T extends DatasetRow>(
35
+ dataset: PlayDataset<T>,
36
+ reader: DatasetChunkReader<T>,
37
+ countHint: number | null,
38
+ ): PlayDataset<T> {
39
+ datasetChunkReaders.set(
40
+ dataset as object,
41
+ reader as DatasetChunkReader<DatasetRow>,
42
+ );
43
+ datasetCountHints.set(dataset as object, countHint);
44
+ return dataset;
45
+ }
46
+
47
+ function normalizedChunkSize(chunkSize: number): number {
48
+ return Math.max(1, Math.floor(chunkSize));
49
+ }
50
+
51
+ export function createMaterializedDatasetHandle<T extends DatasetRow>(input: {
52
+ name: string;
53
+ rows: readonly T[];
54
+ datasetKind?: PlayDatasetKind;
55
+ count?: number;
56
+ sourceLabel?: string | null;
57
+ workProgress?: PlayDatasetWorkProgressSummary;
58
+ }): WorkerDatasetHandle<T> {
59
+ const datasetKind = input.datasetKind ?? 'map';
60
+ const materializedRows = input.rows.map(cloneRow);
61
+ const dataset = createDeferredPlayDataset({
62
+ datasetKind,
63
+ datasetId: `${datasetKind}:${input.name}`,
64
+ count: Math.max(0, Math.floor(input.count ?? materializedRows.length)),
65
+ previewRows: materializedRows
66
+ .slice(0, WORKER_DATASET_PREVIEW_ROWS)
67
+ .map(cloneRow),
68
+ sourceLabel: input.sourceLabel ?? null,
69
+ tableNamespace: input.name,
70
+ workProgress: input.workProgress,
71
+ resolvers: {
72
+ count: async () =>
73
+ Math.max(0, Math.floor(input.count ?? materializedRows.length)),
74
+ peek: async (limit) => materializedRows.slice(0, Math.max(0, limit)),
75
+ materialize: async (limit) =>
76
+ limit === undefined
77
+ ? materializedRows.map(cloneRow)
78
+ : materializedRows.slice(0, Math.max(0, limit)).map(cloneRow),
79
+ iterate: () =>
80
+ ({
81
+ async *[Symbol.asyncIterator]() {
82
+ for (const row of materializedRows) {
83
+ yield cloneRow(row);
84
+ }
85
+ },
86
+ }) as AsyncIterable<T>,
87
+ },
88
+ });
89
+ return registerChunkReader(
90
+ dataset,
91
+ async function* (chunkSize: number) {
92
+ const size = normalizedChunkSize(chunkSize);
93
+ for (let offset = 0; offset < materializedRows.length; offset += size) {
94
+ yield materializedRows.slice(offset, offset + size).map(cloneRow);
95
+ }
96
+ },
97
+ Math.max(0, Math.floor(input.count ?? materializedRows.length)),
98
+ );
99
+ }
100
+
101
+ export function createPersistedDatasetHandle<T extends DatasetRow>(input: {
102
+ playName: string;
103
+ name: string;
104
+ count: number;
105
+ datasetKind?: PlayDatasetKind;
106
+ previewRows?: readonly T[];
107
+ cachedRows?: readonly T[] | null;
108
+ sourceLabel?: string | null;
109
+ workProgress?: PlayDatasetWorkProgressSummary;
110
+ readRows: (input: { limit: number; offset: number }) => Promise<readonly T[]>;
111
+ trace?: (
112
+ phase: string,
113
+ ms: number,
114
+ extra?: Record<string, unknown>,
115
+ ) => void;
116
+ nowMs: () => number;
117
+ }): WorkerDatasetHandle<T> {
118
+ const datasetKind = input.datasetKind ?? 'map';
119
+ const count = Math.max(0, Math.floor(input.count));
120
+ const previewRows = (input.previewRows ?? [])
121
+ .slice(0, WORKER_DATASET_PREVIEW_ROWS)
122
+ .map(cloneRow);
123
+ const cachedRows =
124
+ input.cachedRows && input.cachedRows.length <= WORKER_DATASET_IN_MEMORY_ROWS
125
+ ? input.cachedRows.map(cloneRow)
126
+ : null;
127
+
128
+ async function loadRows(limit: number, offset: number): Promise<T[]> {
129
+ if (limit <= 0) {
130
+ return [];
131
+ }
132
+ const normalizedLimit = normalizedChunkSize(limit);
133
+ const normalizedOffset = Math.max(0, Math.floor(offset));
134
+ if (
135
+ cachedRows &&
136
+ normalizedOffset < cachedRows.length &&
137
+ normalizedOffset + normalizedLimit <= cachedRows.length
138
+ ) {
139
+ return cachedRows
140
+ .slice(normalizedOffset, normalizedOffset + normalizedLimit)
141
+ .map(cloneRow);
142
+ }
143
+ const startedAt = input.nowMs();
144
+ const rows = await input.readRows({
145
+ limit: normalizedLimit,
146
+ offset: normalizedOffset,
147
+ });
148
+ input.trace?.('dataset.read', input.nowMs() - startedAt, {
149
+ datasetKind,
150
+ playName: input.playName,
151
+ tableNamespace: input.name,
152
+ limit: normalizedLimit,
153
+ offset: normalizedOffset,
154
+ rows: rows.length,
155
+ });
156
+ return rows.map(cloneRow);
157
+ }
158
+
159
+ async function* readChunks(chunkSize: number): AsyncGenerator<T[], void, void> {
160
+ const size = normalizedChunkSize(chunkSize);
161
+ let offset = 0;
162
+ while (offset < count) {
163
+ const rows = await loadRows(Math.min(size, count - offset), offset);
164
+ if (rows.length === 0) {
165
+ return;
166
+ }
167
+ yield rows;
168
+ offset += rows.length;
169
+ }
170
+ }
171
+
172
+ const dataset = createDeferredPlayDataset({
173
+ datasetKind,
174
+ datasetId: `${datasetKind}:${input.name}`,
175
+ count,
176
+ backing: {
177
+ storage: 'neon_sheet',
178
+ sheet: {
179
+ playName: input.playName,
180
+ tableNamespace: input.name,
181
+ },
182
+ },
183
+ previewRows,
184
+ sourceLabel: input.sourceLabel ?? null,
185
+ tableNamespace: input.name,
186
+ workProgress: input.workProgress,
187
+ resolvers: {
188
+ count: async () => count,
189
+ peek: async (limit) => await loadRows(Math.max(0, limit), 0),
190
+ materialize: async (limit) => {
191
+ const rows: T[] = [];
192
+ const maxRows = limit ?? count;
193
+ for await (const chunk of readChunks(STREAM_MATERIALIZE_CHUNK_ROWS)) {
194
+ for (const row of chunk) {
195
+ if (rows.length >= maxRows) return rows;
196
+ rows.push(row);
197
+ }
198
+ }
199
+ return rows;
200
+ },
201
+ iterate: () =>
202
+ ({
203
+ async *[Symbol.asyncIterator]() {
204
+ for await (const chunk of readChunks(STREAM_ITERATE_CHUNK_ROWS)) {
205
+ for (const row of chunk) yield row;
206
+ }
207
+ },
208
+ }) as AsyncIterable<T>,
209
+ },
210
+ });
211
+ return registerChunkReader(dataset, (chunkSize) => readChunks(chunkSize), count);
212
+ }
213
+
214
+ export function createCsvDatasetHandle<T extends DatasetRow>(input: {
215
+ name: string;
216
+ logicalPath: string;
217
+ expectedBytes?: number | null;
218
+ renameOptions?: CsvRenameOptions;
219
+ open: () => Promise<AsyncIterable<Uint8Array> | null>;
220
+ streamRows: (
221
+ byteChunks: AsyncIterable<Uint8Array>,
222
+ chunkSize: number,
223
+ ) => AsyncIterable<T[]>;
224
+ trace?: (
225
+ phase: string,
226
+ ms: number,
227
+ extra?: Record<string, unknown>,
228
+ ) => void;
229
+ nowMs: () => number;
230
+ }): WorkerDatasetHandle<T> {
231
+ const datasetId = `csv:${input.name}`;
232
+ let cachedCount: number | null = null;
233
+
234
+ async function* readChunks(chunkSize: number): AsyncGenerator<T[], void, void> {
235
+ const startedAt = input.nowMs();
236
+ let yieldedRows = 0;
237
+ let yieldedChunks = 0;
238
+ let observedBytes = 0;
239
+ let observedByteChunks = 0;
240
+ const body = await input.open();
241
+ if (!body) {
242
+ throw new Error(
243
+ `ctx.csv("${input.logicalPath}"): dataset source is not reachable from the play runner.`,
244
+ );
245
+ }
246
+ const size = normalizedChunkSize(chunkSize);
247
+ const countedBody = {
248
+ async *[Symbol.asyncIterator]() {
249
+ for await (const chunk of body) {
250
+ observedBytes += chunk.byteLength;
251
+ observedByteChunks += 1;
252
+ yield chunk;
253
+ }
254
+ },
255
+ } satisfies AsyncIterable<Uint8Array>;
256
+ try {
257
+ for await (const chunk of input.streamRows(countedBody, size)) {
258
+ yieldedRows += chunk.length;
259
+ yieldedChunks += 1;
260
+ yield applyCsvRenameProjection(chunk, input.renameOptions) as T[];
261
+ }
262
+ } finally {
263
+ input.trace?.('csv.stream', input.nowMs() - startedAt, {
264
+ name: input.name,
265
+ logicalPath: input.logicalPath,
266
+ expectedBytes: input.expectedBytes ?? null,
267
+ observedBytes,
268
+ observedByteChunks,
269
+ yieldedRows,
270
+ yieldedChunks,
271
+ chunkSize: size,
272
+ });
273
+ }
274
+ if (
275
+ typeof input.expectedBytes === 'number' &&
276
+ input.expectedBytes > 0 &&
277
+ yieldedRows === 0
278
+ ) {
279
+ throw new Error(
280
+ observedBytes === 0
281
+ ? `ctx.csv("${input.logicalPath}"): dataset source yielded 0 bytes for ` +
282
+ `a non-empty ${input.expectedBytes} byte staged file.`
283
+ : `ctx.csv("${input.logicalPath}"): parsed 0 rows after reading ` +
284
+ `${observedBytes} bytes from a non-empty ${input.expectedBytes} byte dataset source.`,
285
+ );
286
+ }
287
+ }
288
+
289
+ const dataset = createDeferredPlayDataset({
290
+ datasetKind: 'csv',
291
+ datasetId,
292
+ count: 0,
293
+ previewRows: [],
294
+ tableNamespace: input.name,
295
+ resolvers: {
296
+ count: async () => {
297
+ if (cachedCount !== null) return cachedCount;
298
+ let total = 0;
299
+ for await (const chunk of readChunks(STREAM_MATERIALIZE_CHUNK_ROWS)) {
300
+ total += chunk.length;
301
+ }
302
+ cachedCount = total;
303
+ return total;
304
+ },
305
+ peek: async (limit) => {
306
+ const rows: T[] = [];
307
+ for await (const chunk of readChunks(Math.max(1, limit))) {
308
+ for (const row of chunk) {
309
+ rows.push(row);
310
+ if (rows.length >= limit) return rows;
311
+ }
312
+ }
313
+ return rows;
314
+ },
315
+ materialize: async (limit) => {
316
+ const rows: T[] = [];
317
+ const maxRows = limit ?? Number.POSITIVE_INFINITY;
318
+ for await (const chunk of readChunks(STREAM_MATERIALIZE_CHUNK_ROWS)) {
319
+ for (const row of chunk) {
320
+ if (rows.length >= maxRows) return rows;
321
+ rows.push(row);
322
+ }
323
+ }
324
+ return rows;
325
+ },
326
+ iterate: () =>
327
+ ({
328
+ async *[Symbol.asyncIterator]() {
329
+ for await (const chunk of readChunks(STREAM_ITERATE_CHUNK_ROWS)) {
330
+ for (const row of chunk) yield row;
331
+ }
332
+ },
333
+ }) as AsyncIterable<T>,
334
+ },
335
+ });
336
+ return registerChunkReader(dataset, (chunkSize) => readChunks(chunkSize), null);
337
+ }
338
+
339
+ export function createInlineDatasetHandle<T extends DatasetRow>(
340
+ rows: readonly T[],
341
+ metadata?: {
342
+ name?: string;
343
+ kind?: PlayDatasetKind;
344
+ sourceLabel?: string | null;
345
+ },
346
+ ): WorkerDatasetHandle<T> {
347
+ return createMaterializedDatasetHandle({
348
+ name: metadata?.name ?? 'csv',
349
+ rows,
350
+ datasetKind: metadata?.kind ?? 'csv',
351
+ sourceLabel: metadata?.sourceLabel ?? null,
352
+ });
353
+ }
354
+
355
+ export function datasetRowCountHint<T extends DatasetRow>(
356
+ input: WorkerDatasetInput<T>,
357
+ ): number | null {
358
+ if (Array.isArray(input)) return input.length;
359
+ if (isPlayDataset<T>(input)) {
360
+ const hinted = datasetCountHints.get(input as object);
361
+ if (hinted !== undefined) return hinted;
362
+ }
363
+ return null;
364
+ }
365
+
366
+ export function isDatasetHandle<T extends DatasetRow>(
367
+ value: unknown,
368
+ ): value is WorkerDatasetHandle<T> {
369
+ return isPlayDataset<T>(value);
370
+ }
371
+
372
+ export function iterDatasetChunks<T extends DatasetRow>(
373
+ input: WorkerDatasetInput<T>,
374
+ chunkSize: number,
375
+ ): AsyncIterable<T[]> {
376
+ const size = normalizedChunkSize(chunkSize);
377
+ if (isPlayDataset<T>(input)) {
378
+ const chunkReader = datasetChunkReaders.get(input as object);
379
+ if (chunkReader) {
380
+ return chunkReader(size) as AsyncIterable<T[]>;
381
+ }
382
+ }
383
+ return {
384
+ async *[Symbol.asyncIterator]() {
385
+ let chunk: T[] = [];
386
+ const flush = function* () {
387
+ if (chunk.length > 0) {
388
+ yield chunk;
389
+ chunk = [];
390
+ }
391
+ };
392
+ const push = function* (row: T) {
393
+ chunk.push(row);
394
+ if (chunk.length >= size) {
395
+ yield chunk;
396
+ chunk = [];
397
+ }
398
+ };
399
+
400
+ if (isPlayDataset<T>(input)) {
401
+ for await (const row of input) yield* push(row);
402
+ yield* flush();
403
+ return;
404
+ }
405
+ if (
406
+ input != null &&
407
+ typeof input === 'object' &&
408
+ Symbol.asyncIterator in input
409
+ ) {
410
+ for await (const row of input as AsyncIterable<T>) yield* push(row);
411
+ yield* flush();
412
+ return;
413
+ }
414
+ for (const row of input as Iterable<T>) yield* push(row);
415
+ yield* flush();
416
+ },
417
+ };
418
+ }
@@ -225,7 +225,11 @@ function updatePlayLiveStatusState(
225
225
  : state.runId;
226
226
  const status = normalizeLiveStatus(payload.status) ?? state.status;
227
227
  const logs = readStringArray(payload.logs);
228
- if (logs.length > 0 || event.type === 'play.run.snapshot') {
228
+ if (
229
+ logs.length > 0 ||
230
+ event.type === 'play.run.snapshot' ||
231
+ event.type === 'play.run.final_status'
232
+ ) {
229
233
  state.logs = logs;
230
234
  }
231
235
  if ('result' in payload) {
@@ -43,7 +43,7 @@ export {
43
43
  extractDefinedPlayName,
44
44
  } from '../../../shared_libs/plays/bundling/index.js';
45
45
 
46
- const PLAY_BUNDLE_CACHE_VERSION = 26;
46
+ const PLAY_BUNDLE_CACHE_VERSION = 30;
47
47
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
48
48
  const SDK_PACKAGE_ROOT = resolve(MODULE_DIR, '..', '..');
49
49
  const SOURCE_REPO_ROOT = resolve(SDK_PACKAGE_ROOT, '..');
@@ -8,13 +8,11 @@
8
8
  *
9
9
  * What it does:
10
10
  * - Exposes thin functions that look like the in-bundle implementations
11
- * they're replacing (validate, runtime-api call, …).
11
+ * they're replacing (runtime-api call, staged dataset IO, …).
12
12
  * - Each function calls `env.HARNESS.<method>(...)` — the typed RPC
13
13
  * stub provided by the Cloudflare service binding.
14
14
  *
15
15
  * What it does NOT do:
16
- * - Does NOT import zod (that's the whole point — zod stays in the
17
- * harness Worker).
18
16
  * - Does NOT import the harness Worker's `PlayHarness` class (that
19
17
  * would re-bundle the whole harness; only TYPES are imported).
20
18
  * - Does NOT cache RPC results across plays — caching belongs to
@@ -36,8 +34,10 @@ import type {
36
34
  PreloadedRuntimeDbSessionInput,
37
35
  RuntimeApiCallInput,
38
36
  RuntimeApiCallResult,
39
- RuntimePayloadSchemaId,
40
- ValidatePayloadResult,
37
+ SheetDatasetRowsInput,
38
+ SheetDatasetRowsResult,
39
+ StagedFileChunkInput,
40
+ StagedFileChunkResult,
41
41
  } from '../../../apps/play-harness-worker/src/rpc-types';
42
42
 
43
43
  /**
@@ -46,9 +46,6 @@ import type {
46
46
  * promise-returning RPC stubs; the type matches `PlayHarnessRpc`.
47
47
  */
48
48
  export type HarnessBinding = PlayHarnessRpc;
49
- type HarnessFetchBinding = HarnessBinding & {
50
- fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
51
- };
52
49
 
53
50
  /**
54
51
  * Module-level holder for the binding. Set by the per-play harness on
@@ -122,6 +119,24 @@ export async function harnessRuntimeApiCall(
122
119
  return requireBinding().runtimeApiCall(input);
123
120
  }
124
121
 
122
+ /**
123
+ * Read a bounded staged-file byte range through typed harness RPC. This is the
124
+ * only staged-file data path for per-play Workers; there is intentionally no
125
+ * HTTP fetch fallback because stale parallel paths caused empty CSV streams in
126
+ * preview.
127
+ */
128
+ export async function harnessReadStagedFileChunk(
129
+ input: StagedFileChunkInput,
130
+ ): Promise<StagedFileChunkResult> {
131
+ return requireBinding().readStagedFileChunk(input);
132
+ }
133
+
134
+ export async function harnessReadSheetDatasetRows(
135
+ input: SheetDatasetRowsInput,
136
+ ): Promise<SheetDatasetRowsResult> {
137
+ return requireBinding().readSheetDatasetRows(input);
138
+ }
139
+
125
140
  /**
126
141
  * Warm Postgres sessions in the long-lived harness Worker. This preserves the
127
142
  * map fast path without bundling the Neon client into every dynamic play.
@@ -145,6 +160,7 @@ export async function harnessStartSheetDataset(input: {
145
160
  sheetContract: unknown;
146
161
  rows: Array<Record<string, unknown>>;
147
162
  runId: string;
163
+ inputOffset?: number;
148
164
  userEmail?: string | null;
149
165
  preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
150
166
  }): Promise<{
@@ -153,6 +169,7 @@ export async function harnessStartSheetDataset(input: {
153
169
  pendingRows: Array<Record<string, unknown>>;
154
170
  completedRows: Array<Record<string, unknown>>;
155
171
  tableNamespace: string;
172
+ timings?: Array<Record<string, unknown>>;
156
173
  }> {
157
174
  return requireBinding().startSheetDataset(input);
158
175
  }
@@ -175,50 +192,3 @@ export async function harnessPersistCompletedSheetRows(input: {
175
192
  }): Promise<{ ok: true; rowsWritten: number; tableNamespace: string }> {
176
193
  return requireBinding().persistCompletedMapRows(input);
177
194
  }
178
-
179
- /**
180
- * Stream a staged R2 object through the harness Worker. Unlike the legacy
181
- * signed-URL helper this never touches the Vercel runtime route, and it keeps
182
- * large CSV bodies as streams instead of serializing them through RPC.
183
- */
184
- export async function harnessFetchStagedFile(input: {
185
- executorToken: string;
186
- storageKey: string;
187
- method?: 'GET' | 'HEAD';
188
- range?: { offset: number; length: number };
189
- }): Promise<Response> {
190
- const binding = requireBinding() as HarnessFetchBinding;
191
- if (typeof binding.fetch !== 'function') {
192
- throw new Error(
193
- '[harness-stub] env.HARNESS does not expose fetch(); cannot stream staged R2 files through the harness.',
194
- );
195
- }
196
- const url = new URL('https://play-harness.internal/r2/staged-file');
197
- url.searchParams.set('storageKey', input.storageKey);
198
- const headers: Record<string, string> = {
199
- authorization: `Bearer ${input.executorToken}`,
200
- };
201
- if (input.range) {
202
- const end = input.range.offset + input.range.length - 1;
203
- headers.range = `bytes=${input.range.offset}-${end}`;
204
- }
205
- return binding.fetch(url.toString(), {
206
- method: input.method ?? 'GET',
207
- headers,
208
- });
209
- }
210
-
211
- /**
212
- * Validate a payload against a named schema. The schema definitions
213
- * (and zod itself) live in the harness Worker — see
214
- * `apps/play-harness-worker/src/leaves/validate.ts → KNOWN_SCHEMAS`.
215
- *
216
- * Use a schema id of the form `tool:<provider>:<operation>` for tool
217
- * inputs, or `runtime-api:<action>` for runtime-api request bodies.
218
- */
219
- export async function harnessValidatePayload(
220
- schemaId: RuntimePayloadSchemaId,
221
- payload: unknown,
222
- ): Promise<ValidatePayloadResult> {
223
- return requireBinding().validatePayload({ schemaId, payload });
224
- }
@@ -1,2 +1,2 @@
1
- export const SDK_VERSION = "0.1.25";
1
+ export const SDK_VERSION = "0.1.27";
2
2
  export const SDK_API_CONTRACT = "2026-05-runs-v2";
@@ -126,13 +126,29 @@ export function buildExecutionPlan(
126
126
  }
127
127
 
128
128
  export function chooseMapChunkSize(input: {
129
- totalRows: number;
129
+ /**
130
+ * Known input row count. Pass null for streaming datasets where counting
131
+ * would require an extra full scan; unknown counts use the preferred chunk.
132
+ */
133
+ totalRows: number | null;
130
134
  mapCount: number;
131
135
  stepsPerChunk: number;
132
136
  preferredChunkSize?: number | null;
133
137
  softWorkflowStepBudget?: number | null;
134
138
  }): number {
135
- const totalRows = Math.max(0, Math.floor(input.totalRows));
139
+ const totalRows =
140
+ typeof input.totalRows === 'number' && Number.isFinite(input.totalRows)
141
+ ? Math.max(0, Math.floor(input.totalRows))
142
+ : null;
143
+ const preferred = Math.max(
144
+ 1,
145
+ Math.floor(
146
+ input.preferredChunkSize ?? EXECUTION_PLAN_DEFAULTS.largeMapChunkSize,
147
+ ),
148
+ );
149
+ if (totalRows === null) {
150
+ return preferred;
151
+ }
136
152
  if (totalRows <= EXECUTION_PLAN_DEFAULTS.inlineRowsLimit) {
137
153
  return Math.max(1, totalRows || 1);
138
154
  }
@@ -157,12 +173,6 @@ export function chooseMapChunkSize(input: {
157
173
  1,
158
174
  Math.ceil(totalRows / maxChunksPerMap),
159
175
  );
160
- const preferred = Math.max(
161
- 1,
162
- Math.floor(
163
- input.preferredChunkSize ?? EXECUTION_PLAN_DEFAULTS.largeMapChunkSize,
164
- ),
165
- );
166
176
  return Math.max(preferred, minimumSurvivalChunkSize);
167
177
  }
168
178
 
@@ -52,13 +52,14 @@ export type PlaySchedulerSubmitInput = {
52
52
  storageKey?: string;
53
53
  fileName?: string;
54
54
  logicalPath?: string;
55
+ contentType?: string;
56
+ bytes?: number;
55
57
  } | null;
56
58
  inlineCsv?: { name: string; rows: Record<string, unknown>[] } | null;
57
59
  /**
58
- * Tiny input files (<= WORKERS_EDGE_INLINE_INPUT_MAX_BYTES) carried as raw
59
- * base64 bytes through the scheduler submit so the cf-workflows backend can
60
- * build inlineCsv without an R2 PUT+GET round trip. Mirrors the
61
- * Temporal-side `inlineInputFile` fast path.
60
+ * Temporal-only escape hatch for small legacy inputs. Workers-edge CSV
61
+ * inputs must be staged and streamed by handle instead of carried as raw
62
+ * scheduler payload bytes.
62
63
  */
63
64
  inlineInputFile?: {
64
65
  logicalPath: string;