deepline 0.3.128 → 0.3.129
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/dist/bundling-sources/sdk/src/client.ts +34 -9
- package/dist/bundling-sources/sdk/src/http.ts +22 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
- package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
- package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
- package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
- package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
- package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
- package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +98 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +885 -73
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
- package/dist/cli/index.js +250 -38
- package/dist/cli/index.mjs +250 -38
- package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
- package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +231 -35
- package/dist/index.mjs +231 -35
- package/dist/install-integrity.json +31 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +65 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -107,6 +107,8 @@ import {
|
|
|
107
107
|
PLAY_RUNTIME_SHEET_ATTEMPT_LEASE_TTL_MS,
|
|
108
108
|
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
109
109
|
} from './lease-policy';
|
|
110
|
+
import { BoundedBatchFunction } from './bounded-batch-function';
|
|
111
|
+
import { jsonByteLengthUpTo } from './output-size-limits';
|
|
110
112
|
import {
|
|
111
113
|
parseRuntimeTestFaultCounts,
|
|
112
114
|
PLAY_RUNTIME_TEST_FAULT_HEADER,
|
|
@@ -334,6 +336,49 @@ const RUNTIME_API_RESOLVE_PLAY_RETRY_DELAYS_MS = [
|
|
|
334
336
|
] as const;
|
|
335
337
|
const RUNTIME_API_DEFAULT_RETRY_AFTER_MS = 2_000;
|
|
336
338
|
const RUNTIME_API_REQUEST_TIMEOUT_MS = 30_000;
|
|
339
|
+
/**
|
|
340
|
+
* One gateway dataset-read response must fit below the 16 MiB request/response
|
|
341
|
+
* transport ceiling, leaving room for the JSON envelope and proxy framing.
|
|
342
|
+
* This is a decoded-body budget; it is enforced on both sides of the hop.
|
|
343
|
+
*/
|
|
344
|
+
export const RUNTIME_SHEET_READ_BATCH_MAX_RESPONSE_BYTES = 12 * 1024 * 1024;
|
|
345
|
+
export const RUNTIME_SHEET_READ_BATCH_MAX_PAGES = 4;
|
|
346
|
+
const RUNTIME_SHEET_READ_BATCH_MAX_IN_FLIGHT = 2;
|
|
347
|
+
const RUNTIME_SHEET_READ_BATCH_MAX_RETAINED_PAGES = 8;
|
|
348
|
+
const RUNTIME_SHEET_READ_BATCH_MAX_RETAINED_REQUEST_BYTES = 64 * 1024;
|
|
349
|
+
const RUNTIME_SHEET_READ_BATCH_FLUSH_MS = 5;
|
|
350
|
+
/**
|
|
351
|
+
* A page queues only until an in-flight batch frees its slot, and the in-flight
|
|
352
|
+
* batches run in parallel, so the longest honest wait is one worst-case runtime
|
|
353
|
+
* API call: its request timeout on every attempt plus the whole retry ladder.
|
|
354
|
+
* Past that the lane is wedged rather than busy, and a wedged lane must fail
|
|
355
|
+
* loudly instead of parking a map row on a promise nothing will settle.
|
|
356
|
+
*/
|
|
357
|
+
export const RUNTIME_SHEET_READ_BATCH_MAX_QUEUE_WAIT_MS =
|
|
358
|
+
RUNTIME_API_REQUEST_TIMEOUT_MS * (RUNTIME_API_RETRY_DELAYS_MS.length + 1) +
|
|
359
|
+
RUNTIME_API_RETRY_DELAYS_MS.reduce((total, delay) => total + delay, 0);
|
|
360
|
+
|
|
361
|
+
export type RuntimeSheetDatasetPageInput = {
|
|
362
|
+
limit: number;
|
|
363
|
+
offset: number;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export type RuntimeSheetDatasetPage = {
|
|
367
|
+
rows: Record<string, unknown>[];
|
|
368
|
+
limit: number;
|
|
369
|
+
offset: number;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export class RuntimeSheetDatasetReadBatchTooLargeError extends Error {
|
|
373
|
+
readonly code = 'runtime_sheet_read_batch_too_large';
|
|
374
|
+
|
|
375
|
+
constructor(readonly observedBytes: number) {
|
|
376
|
+
super(
|
|
377
|
+
`Runtime dataset read batch exceeds the ${RUNTIME_SHEET_READ_BATCH_MAX_RESPONSE_BYTES} byte decoded response budget (observed at least ${observedBytes} bytes).`,
|
|
378
|
+
);
|
|
379
|
+
this.name = 'RuntimeSheetDatasetReadBatchTooLargeError';
|
|
380
|
+
}
|
|
381
|
+
}
|
|
337
382
|
// A sheet admission can legitimately wait behind two large transactional
|
|
338
383
|
// writes. Keep the caller alive long enough for the FIFO admission queue to
|
|
339
384
|
// make progress instead of aborting a healthy queued start after 30 seconds.
|
|
@@ -514,6 +559,10 @@ type RuntimeApiActionRequest =
|
|
|
514
559
|
action: 'runtime_sheet_read_rows';
|
|
515
560
|
input: Parameters<typeof readRuntimeSheetDatasetRows>[1];
|
|
516
561
|
}
|
|
562
|
+
| {
|
|
563
|
+
action: 'runtime_sheet_read_rows_batch';
|
|
564
|
+
input: Parameters<typeof readRuntimeSheetDatasetRowsBatch>[1];
|
|
565
|
+
}
|
|
517
566
|
| {
|
|
518
567
|
action: 'runtime_sheet_read_row_keys';
|
|
519
568
|
input: Parameters<typeof readRuntimeSheetDatasetRowKeys>[1];
|
|
@@ -632,6 +681,76 @@ function normalizeRuntimeUserEmail(
|
|
|
632
681
|
return email ? email : null;
|
|
633
682
|
}
|
|
634
683
|
|
|
684
|
+
class RuntimeApiResponseTooLargeError extends Error {
|
|
685
|
+
constructor(
|
|
686
|
+
readonly action: RuntimeApiActionRequest['action'],
|
|
687
|
+
readonly limitBytes: number,
|
|
688
|
+
readonly observedBytes: number,
|
|
689
|
+
) {
|
|
690
|
+
super(
|
|
691
|
+
`Runtime API ${action} response exceeds the ${limitBytes} byte decoded-body budget (observed at least ${observedBytes} bytes).`,
|
|
692
|
+
);
|
|
693
|
+
this.name = 'RuntimeApiResponseTooLargeError';
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function runtimeApiResponseByteLimit(
|
|
698
|
+
action: RuntimeApiActionRequest['action'],
|
|
699
|
+
): number {
|
|
700
|
+
return action === 'runtime_sheet_read_rows_batch'
|
|
701
|
+
? RUNTIME_SHEET_READ_BATCH_MAX_RESPONSE_BYTES
|
|
702
|
+
: 16 * 1024 * 1024;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function readRuntimeApiResponseJson(input: {
|
|
706
|
+
response: Response;
|
|
707
|
+
action: RuntimeApiActionRequest['action'];
|
|
708
|
+
}): Promise<unknown> {
|
|
709
|
+
const limitBytes = runtimeApiResponseByteLimit(input.action);
|
|
710
|
+
const declaredLength = Number(
|
|
711
|
+
input.response.headers.get('content-length') ?? 0,
|
|
712
|
+
);
|
|
713
|
+
if (Number.isFinite(declaredLength) && declaredLength > limitBytes) {
|
|
714
|
+
await input.response.body?.cancel().catch(() => undefined);
|
|
715
|
+
throw new RuntimeApiResponseTooLargeError(
|
|
716
|
+
input.action,
|
|
717
|
+
limitBytes,
|
|
718
|
+
declaredLength,
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
if (!input.response.body) return null;
|
|
722
|
+
const reader = input.response.body.getReader();
|
|
723
|
+
const chunks: Uint8Array[] = [];
|
|
724
|
+
let bytes = 0;
|
|
725
|
+
try {
|
|
726
|
+
while (true) {
|
|
727
|
+
const { done, value } = await reader.read();
|
|
728
|
+
if (done) break;
|
|
729
|
+
const nextBytes = bytes + value.byteLength;
|
|
730
|
+
if (nextBytes > limitBytes) {
|
|
731
|
+
await reader.cancel().catch(() => undefined);
|
|
732
|
+
throw new RuntimeApiResponseTooLargeError(
|
|
733
|
+
input.action,
|
|
734
|
+
limitBytes,
|
|
735
|
+
nextBytes,
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
chunks.push(value);
|
|
739
|
+
bytes = nextBytes;
|
|
740
|
+
}
|
|
741
|
+
} finally {
|
|
742
|
+
reader.releaseLock();
|
|
743
|
+
}
|
|
744
|
+
const body = new Uint8Array(bytes);
|
|
745
|
+
let offset = 0;
|
|
746
|
+
for (const chunk of chunks) {
|
|
747
|
+
body.set(chunk, offset);
|
|
748
|
+
offset += chunk.byteLength;
|
|
749
|
+
}
|
|
750
|
+
if (body.byteLength === 0) return null;
|
|
751
|
+
return JSON.parse(new TextDecoder().decode(body)) as unknown;
|
|
752
|
+
}
|
|
753
|
+
|
|
635
754
|
async function postRuntimeApi<TResponse>(
|
|
636
755
|
context: RuntimeApiContext,
|
|
637
756
|
body: RuntimeApiActionRequest,
|
|
@@ -668,8 +787,14 @@ async function postRuntimeApi<TResponse>(
|
|
|
668
787
|
});
|
|
669
788
|
let parsedValue: unknown;
|
|
670
789
|
try {
|
|
671
|
-
parsedValue = await
|
|
790
|
+
parsedValue = await readRuntimeApiResponseJson({
|
|
791
|
+
response,
|
|
792
|
+
action: body.action,
|
|
793
|
+
});
|
|
672
794
|
} catch (error) {
|
|
795
|
+
if (error instanceof RuntimeApiResponseTooLargeError) {
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
673
798
|
if (abortController.signal.aborted || context.abortSignal?.aborted) {
|
|
674
799
|
throw error;
|
|
675
800
|
}
|
|
@@ -683,6 +808,9 @@ async function postRuntimeApi<TResponse>(
|
|
|
683
808
|
: null;
|
|
684
809
|
} catch (error) {
|
|
685
810
|
clearTimeout(timeout);
|
|
811
|
+
if (error instanceof RuntimeApiResponseTooLargeError) {
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
686
814
|
if (context.abortSignal?.aborted) {
|
|
687
815
|
throw context.abortSignal.reason ?? error;
|
|
688
816
|
}
|
|
@@ -750,6 +878,18 @@ async function postRuntimeApi<TResponse>(
|
|
|
750
878
|
: typeof parsed?.debug_error === 'string'
|
|
751
879
|
? parsed.debug_error
|
|
752
880
|
: null;
|
|
881
|
+
if (
|
|
882
|
+
response.status === 413 &&
|
|
883
|
+
body.action === 'runtime_sheet_read_rows_batch' &&
|
|
884
|
+
parsed?.code === 'runtime_sheet_read_batch_too_large'
|
|
885
|
+
) {
|
|
886
|
+
throw new RuntimeSheetDatasetReadBatchTooLargeError(
|
|
887
|
+
typeof parsed.observed_bytes === 'number' &&
|
|
888
|
+
Number.isFinite(parsed.observed_bytes)
|
|
889
|
+
? parsed.observed_bytes
|
|
890
|
+
: RUNTIME_SHEET_READ_BATCH_MAX_RESPONSE_BYTES + 1,
|
|
891
|
+
);
|
|
892
|
+
}
|
|
753
893
|
if (
|
|
754
894
|
response.status === 503 &&
|
|
755
895
|
parsed?.code === 'runtime_postgres_admission_backpressure'
|
|
@@ -916,7 +1056,7 @@ function runtimeApiMaxAttempts(
|
|
|
916
1056
|
}
|
|
917
1057
|
|
|
918
1058
|
export function isTransientRuntimeApiErrorMessage(message: string): boolean {
|
|
919
|
-
return /timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|requested endpoint could not be found, or you don't have access|RUNTIME_SCHEDULER_(?:SATURATED|UNAVAILABLE|SCHEMA_NOT_READY)|Runtime scheduler DB pool is saturated|Runtime scheduler DB circuit breaker is open|Runtime
|
|
1059
|
+
return /timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|requested endpoint could not be found, or you don't have access|Neon API request failed \((?:408|429|502|503|504)\)|RUNTIME_SCHEDULER_(?:SATURATED|UNAVAILABLE|SCHEMA_NOT_READY)|RUNTIME_RATE_STATE_UNAVAILABLE|Runtime scheduler DB pool is saturated|Runtime scheduler DB circuit breaker is open|Runtime rate state .* is unavailable/i.test(
|
|
920
1060
|
message,
|
|
921
1061
|
);
|
|
922
1062
|
}
|
|
@@ -5355,7 +5495,12 @@ export async function completeRuntimeWorkReceipt(
|
|
|
5355
5495
|
input.runId,
|
|
5356
5496
|
],
|
|
5357
5497
|
);
|
|
5358
|
-
if (insertedRows[0])
|
|
5498
|
+
if (insertedRows[0]) {
|
|
5499
|
+
return {
|
|
5500
|
+
...mapRuntimeWorkReceiptRow(insertedRows[0]),
|
|
5501
|
+
completionDisposition: 'persisted',
|
|
5502
|
+
};
|
|
5503
|
+
}
|
|
5359
5504
|
const { rows: winnerRows } = await client.query<
|
|
5360
5505
|
Record<string, unknown>
|
|
5361
5506
|
>(
|
|
@@ -5368,7 +5513,12 @@ export async function completeRuntimeWorkReceipt(
|
|
|
5368
5513
|
LIMIT 1`,
|
|
5369
5514
|
[workReceiptKeyHex(input.key), RECEIPT_STATUS_COMPLETED],
|
|
5370
5515
|
);
|
|
5371
|
-
return winnerRows[0]
|
|
5516
|
+
return winnerRows[0]
|
|
5517
|
+
? {
|
|
5518
|
+
...mapRuntimeWorkReceiptRow(winnerRows[0]),
|
|
5519
|
+
completionDisposition: 'existing',
|
|
5520
|
+
}
|
|
5521
|
+
: null;
|
|
5372
5522
|
}
|
|
5373
5523
|
const runAttempt = normalizeRuntimeRunAttempt(input.runAttempt);
|
|
5374
5524
|
const { rows } = await client.query<Record<string, unknown>>(
|
|
@@ -6857,11 +7007,46 @@ type CompleteRuntimeMapRowChunksInput = {
|
|
|
6857
7007
|
outputFields: string[];
|
|
6858
7008
|
};
|
|
6859
7009
|
|
|
7010
|
+
/**
|
|
7011
|
+
* Why a terminal row write was rejected as stale. Runtime Sheet rows are fenced
|
|
7012
|
+
* by their schedule-time `_write_version` alone (the legacy attempt-lease
|
|
7013
|
+
* predicates are stubbed to TRUE/FALSE in sheet-attempt-sql.ts), so a stale
|
|
7014
|
+
* versioned write always means the row is owned by a different admission than
|
|
7015
|
+
* the one the writer carries:
|
|
7016
|
+
* - `newer_write_version`: a later admission (rerun, resumed attempt, or a
|
|
7017
|
+
* concurrent run of the same play) re-admitted the row after this writer
|
|
7018
|
+
* admitted it.
|
|
7019
|
+
* - `older_write_version`: the row was admitted under an older version than
|
|
7020
|
+
* the writer sent; the writer's version never admitted this row.
|
|
7021
|
+
* - `already_terminal`: an unversioned writer hit an already-enriched row.
|
|
7022
|
+
* Nothing is being overwritten, so this is the one benign reason.
|
|
7023
|
+
* - `unreported`: the gateway returned stale keys without reasons (predates
|
|
7024
|
+
* this field). Treated as superseded: every stale versioned write is.
|
|
7025
|
+
*/
|
|
7026
|
+
export type RuntimeMapRowsStaleReason =
|
|
7027
|
+
| 'newer_write_version'
|
|
7028
|
+
| 'older_write_version'
|
|
7029
|
+
| 'already_terminal'
|
|
7030
|
+
| 'unreported';
|
|
7031
|
+
|
|
7032
|
+
export type RuntimeMapRowsStaleRow = {
|
|
7033
|
+
key: string;
|
|
7034
|
+
reason: RuntimeMapRowsStaleReason;
|
|
7035
|
+
/** The admission version currently stamped on the sheet row. */
|
|
7036
|
+
rowVersionStamp: number | null;
|
|
7037
|
+
};
|
|
7038
|
+
|
|
6860
7039
|
export type RuntimeMapRowsWriteResult = {
|
|
6861
7040
|
/** Submitted row identities durably accepted, including idempotent replays. */
|
|
6862
7041
|
committedKeys: string[];
|
|
6863
7042
|
/** Submitted row identities rejected because a newer write owns the row. */
|
|
6864
7043
|
staleKeys: string[];
|
|
7044
|
+
/**
|
|
7045
|
+
* Why each stale key was rejected. Present only when the write produced stale
|
|
7046
|
+
* keys and the classifying side reported reasons; a gateway that predates
|
|
7047
|
+
* this field returns stale keys without it.
|
|
7048
|
+
*/
|
|
7049
|
+
staleRows?: RuntimeMapRowsStaleRow[];
|
|
6865
7050
|
/** Physical row mutations. Idempotent commits do not increment this count. */
|
|
6866
7051
|
updated: number;
|
|
6867
7052
|
/** Compatibility alias retained for callers that still call stale rows fenced. */
|
|
@@ -6874,16 +7059,67 @@ export type RuntimeMapRowsWriteResult = {
|
|
|
6874
7059
|
|
|
6875
7060
|
type RuntimeMapRowsMutationResult = {
|
|
6876
7061
|
updated: number;
|
|
6877
|
-
|
|
7062
|
+
fencedRows: RuntimeMapRowsStaleRow[];
|
|
6878
7063
|
conflictKeys: string[];
|
|
6879
7064
|
};
|
|
6880
7065
|
|
|
7066
|
+
const RUNTIME_MAP_ROWS_STALE_REASONS: ReadonlySet<RuntimeMapRowsStaleReason> =
|
|
7067
|
+
new Set([
|
|
7068
|
+
'newer_write_version',
|
|
7069
|
+
'older_write_version',
|
|
7070
|
+
'already_terminal',
|
|
7071
|
+
'unreported',
|
|
7072
|
+
]);
|
|
7073
|
+
|
|
7074
|
+
function parseRuntimeMapRowsFencedRows(
|
|
7075
|
+
value: unknown,
|
|
7076
|
+
): RuntimeMapRowsStaleRow[] {
|
|
7077
|
+
const entries =
|
|
7078
|
+
typeof value === 'string' ? (JSON.parse(value) as unknown) : value;
|
|
7079
|
+
if (entries == null) return [];
|
|
7080
|
+
if (!Array.isArray(entries)) {
|
|
7081
|
+
throw new Error(
|
|
7082
|
+
'Runtime Sheet terminal write returned a malformed fenced_rows column.',
|
|
7083
|
+
);
|
|
7084
|
+
}
|
|
7085
|
+
return entries.map((entry) => {
|
|
7086
|
+
const row = entry as Partial<RuntimeMapRowsStaleRow> | null;
|
|
7087
|
+
if (
|
|
7088
|
+
!row ||
|
|
7089
|
+
typeof row.key !== 'string' ||
|
|
7090
|
+
typeof row.reason !== 'string' ||
|
|
7091
|
+
!RUNTIME_MAP_ROWS_STALE_REASONS.has(row.reason)
|
|
7092
|
+
) {
|
|
7093
|
+
throw new Error(
|
|
7094
|
+
`Runtime Sheet terminal write returned a malformed fenced row: ${JSON.stringify(entry)}.`,
|
|
7095
|
+
);
|
|
7096
|
+
}
|
|
7097
|
+
const rowVersionStamp =
|
|
7098
|
+
row.rowVersionStamp == null ? null : Number(row.rowVersionStamp);
|
|
7099
|
+
return {
|
|
7100
|
+
key: row.key,
|
|
7101
|
+
reason: row.reason,
|
|
7102
|
+
rowVersionStamp:
|
|
7103
|
+
rowVersionStamp !== null && Number.isFinite(rowVersionStamp)
|
|
7104
|
+
? rowVersionStamp
|
|
7105
|
+
: null,
|
|
7106
|
+
};
|
|
7107
|
+
});
|
|
7108
|
+
}
|
|
7109
|
+
|
|
6881
7110
|
function classifyRuntimeMapRowsWrite(input: {
|
|
6882
7111
|
submittedKeys: Iterable<string>;
|
|
6883
7112
|
updated: number;
|
|
6884
|
-
fencedKeys
|
|
7113
|
+
fencedKeys?: Iterable<string>;
|
|
7114
|
+
fencedRows?: Iterable<RuntimeMapRowsStaleRow>;
|
|
6885
7115
|
}): RuntimeMapRowsWriteResult {
|
|
6886
|
-
const
|
|
7116
|
+
const staleRowsByKey = new Map<string, RuntimeMapRowsStaleRow>();
|
|
7117
|
+
for (const row of input.fencedRows ?? []) {
|
|
7118
|
+
if (!staleRowsByKey.has(row.key)) staleRowsByKey.set(row.key, row);
|
|
7119
|
+
}
|
|
7120
|
+
const staleKeys = [
|
|
7121
|
+
...new Set([...staleRowsByKey.keys(), ...(input.fencedKeys ?? [])]),
|
|
7122
|
+
];
|
|
6887
7123
|
const staleSet = new Set(staleKeys);
|
|
6888
7124
|
const committedKeys = [...new Set(input.submittedKeys)].filter(
|
|
6889
7125
|
(key) => !staleSet.has(key),
|
|
@@ -6891,6 +7127,9 @@ function classifyRuntimeMapRowsWrite(input: {
|
|
|
6891
7127
|
return {
|
|
6892
7128
|
committedKeys,
|
|
6893
7129
|
staleKeys,
|
|
7130
|
+
...(staleRowsByKey.size > 0
|
|
7131
|
+
? { staleRows: [...staleRowsByKey.values()] }
|
|
7132
|
+
: {}),
|
|
6894
7133
|
updated: input.updated,
|
|
6895
7134
|
fencedKeys: staleKeys,
|
|
6896
7135
|
staleDropped: staleKeys.length,
|
|
@@ -6898,6 +7137,99 @@ function classifyRuntimeMapRowsWrite(input: {
|
|
|
6898
7137
|
};
|
|
6899
7138
|
}
|
|
6900
7139
|
|
|
7140
|
+
/**
|
|
7141
|
+
* The stale rows of a terminal write that prove this writer no longer owns the
|
|
7142
|
+
* sheet: every stale key except the ones the caller verified as idempotent
|
|
7143
|
+
* same-run settlements and the benign unversioned `already_terminal` case. A
|
|
7144
|
+
* result without `staleRows` (a gateway that predates reasons) reports its
|
|
7145
|
+
* stale keys as `unreported`, which counts as superseded: a versioned write can
|
|
7146
|
+
* only be stale when the row carries a different admission version.
|
|
7147
|
+
*/
|
|
7148
|
+
export function supersededRuntimeMapRows(
|
|
7149
|
+
result: Pick<RuntimeMapRowsWriteResult, 'staleKeys' | 'staleRows'>,
|
|
7150
|
+
options: { exceptKeys?: Iterable<string> } = {},
|
|
7151
|
+
): RuntimeMapRowsStaleRow[] {
|
|
7152
|
+
const except = new Set(options.exceptKeys ?? []);
|
|
7153
|
+
const reported = new Map(
|
|
7154
|
+
(result.staleRows ?? []).map((row) => [row.key, row] as const),
|
|
7155
|
+
);
|
|
7156
|
+
const superseded: RuntimeMapRowsStaleRow[] = [];
|
|
7157
|
+
for (const key of new Set(result.staleKeys)) {
|
|
7158
|
+
if (except.has(key)) continue;
|
|
7159
|
+
const row = reported.get(key) ?? {
|
|
7160
|
+
key,
|
|
7161
|
+
reason: 'unreported' as const,
|
|
7162
|
+
rowVersionStamp: null,
|
|
7163
|
+
};
|
|
7164
|
+
if (row.reason === 'already_terminal') continue;
|
|
7165
|
+
superseded.push(row);
|
|
7166
|
+
}
|
|
7167
|
+
return superseded;
|
|
7168
|
+
}
|
|
7169
|
+
|
|
7170
|
+
/**
|
|
7171
|
+
* A terminal write batch was rejected because the Runtime Sheet rows belong to
|
|
7172
|
+
* a different write version than this attempt carries. The attempt has been
|
|
7173
|
+
* superseded: nothing it executes from here on can be persisted, so it must
|
|
7174
|
+
* stop instead of spending provider calls on rows that will be dropped. Thrown
|
|
7175
|
+
* by the runner's terminal-write handler and by the Context when a handler
|
|
7176
|
+
* reports `supersededKeys`; both trip the persistence latch.
|
|
7177
|
+
*/
|
|
7178
|
+
export class RuntimeSheetAttemptSupersededError extends Error {
|
|
7179
|
+
readonly tableNamespace: string;
|
|
7180
|
+
readonly runId: string;
|
|
7181
|
+
readonly writeVersion: number | null;
|
|
7182
|
+
readonly submittedRows: number;
|
|
7183
|
+
readonly supersededRows: RuntimeMapRowsStaleRow[];
|
|
7184
|
+
|
|
7185
|
+
constructor(input: {
|
|
7186
|
+
tableNamespace: string;
|
|
7187
|
+
runId: string;
|
|
7188
|
+
writeVersionValue: number | null;
|
|
7189
|
+
submittedRows: number;
|
|
7190
|
+
supersededRows: RuntimeMapRowsStaleRow[];
|
|
7191
|
+
}) {
|
|
7192
|
+
const reasonCounts = new Map<RuntimeMapRowsStaleReason, number>();
|
|
7193
|
+
for (const row of input.supersededRows) {
|
|
7194
|
+
reasonCounts.set(row.reason, (reasonCounts.get(row.reason) ?? 0) + 1);
|
|
7195
|
+
}
|
|
7196
|
+
const reasons = [...reasonCounts.entries()]
|
|
7197
|
+
.map(([reason, count]) => `${reason}=${count}`)
|
|
7198
|
+
.join(', ');
|
|
7199
|
+
const samples = input.supersededRows
|
|
7200
|
+
.slice(0, 5)
|
|
7201
|
+
.map(
|
|
7202
|
+
(row) =>
|
|
7203
|
+
`key=${row.key} row_write_version=${row.rowVersionStamp ?? 'unknown'}`,
|
|
7204
|
+
)
|
|
7205
|
+
.join('; ');
|
|
7206
|
+
super(
|
|
7207
|
+
`RUNTIME_SHEET_ATTEMPT_SUPERSEDED: Runtime Sheet table "${input.tableNamespace}" ` +
|
|
7208
|
+
`rejected ${input.supersededRows.length} of ${input.submittedRows} terminal row write(s) ` +
|
|
7209
|
+
`from run ${input.runId} at write version ${input.writeVersionValue ?? 'none'} ` +
|
|
7210
|
+
`because the rows are owned by a different write version (${reasons || 'unreported'}). ` +
|
|
7211
|
+
`This attempt has been superseded by a newer admission of these rows and must stop ` +
|
|
7212
|
+
`executing: nothing it persists from here on can be accepted. Sample: ${samples}.`,
|
|
7213
|
+
);
|
|
7214
|
+
this.name = 'RuntimeSheetAttemptSupersededError';
|
|
7215
|
+
this.tableNamespace = input.tableNamespace;
|
|
7216
|
+
this.runId = input.runId;
|
|
7217
|
+
this.writeVersion = input.writeVersionValue;
|
|
7218
|
+
this.submittedRows = input.submittedRows;
|
|
7219
|
+
this.supersededRows = input.supersededRows;
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
|
|
7223
|
+
export function isRuntimeSheetAttemptSupersededError(
|
|
7224
|
+
error: unknown,
|
|
7225
|
+
): error is RuntimeSheetAttemptSupersededError {
|
|
7226
|
+
if (error instanceof RuntimeSheetAttemptSupersededError) return true;
|
|
7227
|
+
return (
|
|
7228
|
+
error instanceof Error &&
|
|
7229
|
+
error.name === 'RuntimeSheetAttemptSupersededError'
|
|
7230
|
+
);
|
|
7231
|
+
}
|
|
7232
|
+
|
|
6901
7233
|
function writableEnrichedRuntimeSheetAttemptSql(
|
|
6902
7234
|
tableAlias: string,
|
|
6903
7235
|
attemptOwnerRunIdExpression: string,
|
|
@@ -6918,7 +7250,7 @@ async function completeRuntimeMapRowChunks(
|
|
|
6918
7250
|
input: CompleteRuntimeMapRowChunksInput,
|
|
6919
7251
|
): Promise<RuntimeMapRowsMutationResult> {
|
|
6920
7252
|
let updated = 0;
|
|
6921
|
-
const
|
|
7253
|
+
const fencedRows: RuntimeMapRowsStaleRow[] = [];
|
|
6922
7254
|
const conflictKeys: string[] = [];
|
|
6923
7255
|
for (const chunk of input.chunks) {
|
|
6924
7256
|
const chunkKeys = chunk.map((row) => row.key);
|
|
@@ -6946,7 +7278,7 @@ async function completeRuntimeMapRowChunks(
|
|
|
6946
7278
|
const { rows } = await client.query<{
|
|
6947
7279
|
updated: number;
|
|
6948
7280
|
matched_keys: string[];
|
|
6949
|
-
|
|
7281
|
+
fenced_rows: unknown;
|
|
6950
7282
|
conflict_keys: string[];
|
|
6951
7283
|
}>(
|
|
6952
7284
|
`WITH updates AS (
|
|
@@ -7062,7 +7394,13 @@ async function completeRuntimeMapRowChunks(
|
|
|
7062
7394
|
FROM applied_rows
|
|
7063
7395
|
),
|
|
7064
7396
|
fenced_rows AS (
|
|
7065
|
-
SELECT updates.matched_key AS _key
|
|
7397
|
+
SELECT updates.matched_key AS _key,
|
|
7398
|
+
CASE
|
|
7399
|
+
WHEN $13::bigint IS NOT NULL AND target._write_version > $13::bigint THEN 'newer_write_version'
|
|
7400
|
+
WHEN $13::bigint IS NOT NULL AND target._write_version < $13::bigint THEN 'older_write_version'
|
|
7401
|
+
ELSE 'already_terminal'
|
|
7402
|
+
END AS reason,
|
|
7403
|
+
target._write_version AS row_write_version
|
|
7066
7404
|
FROM matched_updates AS updates
|
|
7067
7405
|
JOIN ${sheetTable(session)} AS target
|
|
7068
7406
|
ON target._key = updates.matched_key
|
|
@@ -7166,7 +7504,7 @@ async function completeRuntimeMapRowChunks(
|
|
|
7166
7504
|
SELECT
|
|
7167
7505
|
(SELECT count(*)::int FROM applied_rows) AS updated,
|
|
7168
7506
|
coalesce((SELECT array_agg(matched_key) FROM matched_updates), '{}'::text[]) AS matched_keys,
|
|
7169
|
-
coalesce((SELECT
|
|
7507
|
+
coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows,
|
|
7170
7508
|
coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys,
|
|
7171
7509
|
(SELECT count(*)::int FROM summary_delta) AS summary_delta_count,
|
|
7172
7510
|
(SELECT count(*)::int FROM column_delta) AS column_delta_count`,
|
|
@@ -7187,14 +7525,14 @@ async function completeRuntimeMapRowChunks(
|
|
|
7187
7525
|
],
|
|
7188
7526
|
);
|
|
7189
7527
|
const matchedKeys = new Set(rows[0]?.matched_keys ?? []);
|
|
7190
|
-
const
|
|
7528
|
+
const chunkFencedRows = parseRuntimeMapRowsFencedRows(rows[0]?.fenced_rows);
|
|
7191
7529
|
const chunkConflictKeys = rows[0]?.conflict_keys ?? [];
|
|
7192
7530
|
// An exact-version replay that already produced the same terminal value is
|
|
7193
7531
|
// accepted idempotently even when SQL has no physical mutation to apply.
|
|
7194
|
-
// Stale versions remain excluded through
|
|
7532
|
+
// Stale versions remain excluded through fenced_rows.
|
|
7195
7533
|
const appliedCount = Number(rows[0]?.updated ?? 0);
|
|
7196
7534
|
updated += appliedCount;
|
|
7197
|
-
|
|
7535
|
+
fencedRows.push(...chunkFencedRows);
|
|
7198
7536
|
conflictKeys.push(...chunkConflictKeys);
|
|
7199
7537
|
|
|
7200
7538
|
if (chunkConflictKeys.length > 0) {
|
|
@@ -7217,10 +7555,10 @@ async function completeRuntimeMapRowChunks(
|
|
|
7217
7555
|
{ ...input, chunks: [repairChunk] },
|
|
7218
7556
|
);
|
|
7219
7557
|
updated += repaired.updated;
|
|
7220
|
-
|
|
7558
|
+
fencedRows.push(...repaired.fencedRows);
|
|
7221
7559
|
conflictKeys.push(...repaired.conflictKeys);
|
|
7222
7560
|
}
|
|
7223
|
-
return { updated,
|
|
7561
|
+
return { updated, fencedRows, conflictKeys };
|
|
7224
7562
|
}
|
|
7225
7563
|
|
|
7226
7564
|
async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
@@ -7229,7 +7567,7 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
|
7229
7567
|
input: CompleteRuntimeMapRowChunksInput,
|
|
7230
7568
|
): Promise<RuntimeMapRowsMutationResult> {
|
|
7231
7569
|
let updated = 0;
|
|
7232
|
-
const
|
|
7570
|
+
const fencedRows: RuntimeMapRowsStaleRow[] = [];
|
|
7233
7571
|
const conflictKeys: string[] = [];
|
|
7234
7572
|
for (const chunk of input.chunks) {
|
|
7235
7573
|
const chunkKeys = chunk.map((row) => row.key);
|
|
@@ -7249,7 +7587,7 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
|
7249
7587
|
);
|
|
7250
7588
|
const { rows } = await client.query<{
|
|
7251
7589
|
_key?: string;
|
|
7252
|
-
|
|
7590
|
+
fenced_rows?: unknown;
|
|
7253
7591
|
conflict_keys?: string[];
|
|
7254
7592
|
updated?: number;
|
|
7255
7593
|
}>(
|
|
@@ -7362,7 +7700,13 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
|
7362
7700
|
FROM applied_rows
|
|
7363
7701
|
),
|
|
7364
7702
|
fenced_rows AS (
|
|
7365
|
-
SELECT updates.submitted_key AS _key
|
|
7703
|
+
SELECT updates.submitted_key AS _key,
|
|
7704
|
+
CASE
|
|
7705
|
+
WHEN $13::bigint IS NOT NULL AND target._write_version > $13::bigint THEN 'newer_write_version'
|
|
7706
|
+
WHEN $13::bigint IS NOT NULL AND target._write_version < $13::bigint THEN 'older_write_version'
|
|
7707
|
+
ELSE 'already_terminal'
|
|
7708
|
+
END AS reason,
|
|
7709
|
+
target._write_version AS row_write_version
|
|
7366
7710
|
FROM matched_updates AS updates
|
|
7367
7711
|
JOIN ${sheetTable(session)} AS target
|
|
7368
7712
|
ON target._key = updates.matched_key
|
|
@@ -7463,7 +7807,7 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
|
7463
7807
|
)
|
|
7464
7808
|
SELECT
|
|
7465
7809
|
(SELECT count(*)::int FROM applied_rows) AS updated,
|
|
7466
|
-
coalesce((SELECT
|
|
7810
|
+
coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows,
|
|
7467
7811
|
coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys`,
|
|
7468
7812
|
[
|
|
7469
7813
|
chunkKeys,
|
|
@@ -7482,10 +7826,10 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
|
7482
7826
|
],
|
|
7483
7827
|
);
|
|
7484
7828
|
updated += Number(rows[0]?.updated ?? 0);
|
|
7485
|
-
|
|
7829
|
+
fencedRows.push(...parseRuntimeMapRowsFencedRows(rows[0]?.fenced_rows));
|
|
7486
7830
|
conflictKeys.push(...(rows[0]?.conflict_keys ?? []));
|
|
7487
7831
|
}
|
|
7488
|
-
return { updated,
|
|
7832
|
+
return { updated, fencedRows, conflictKeys };
|
|
7489
7833
|
}
|
|
7490
7834
|
|
|
7491
7835
|
async function insertMissingCompletedMapRowChunks(
|
|
@@ -7680,7 +8024,7 @@ async function failRuntimeMapRowChunks(
|
|
|
7680
8024
|
},
|
|
7681
8025
|
): Promise<RuntimeMapRowsMutationResult> {
|
|
7682
8026
|
let updated = 0;
|
|
7683
|
-
const
|
|
8027
|
+
const fencedRows: RuntimeMapRowsStaleRow[] = [];
|
|
7684
8028
|
const conflictKeys: string[] = [];
|
|
7685
8029
|
for (const chunk of input.chunks) {
|
|
7686
8030
|
const chunkKeys = chunk.map((row) => row.key);
|
|
@@ -7713,7 +8057,7 @@ async function failRuntimeMapRowChunks(
|
|
|
7713
8057
|
);
|
|
7714
8058
|
const { rows } = await client.query<{
|
|
7715
8059
|
updated?: number;
|
|
7716
|
-
|
|
8060
|
+
fenced_rows?: unknown;
|
|
7717
8061
|
conflict_keys?: string[];
|
|
7718
8062
|
}>(
|
|
7719
8063
|
`WITH updates AS (
|
|
@@ -7829,7 +8173,13 @@ async function failRuntimeMapRowChunks(
|
|
|
7829
8173
|
FROM applied_rows
|
|
7830
8174
|
),
|
|
7831
8175
|
fenced_rows AS (
|
|
7832
|
-
SELECT updates.matched_key AS _key
|
|
8176
|
+
SELECT updates.matched_key AS _key,
|
|
8177
|
+
CASE
|
|
8178
|
+
WHEN $16::bigint IS NOT NULL AND target._write_version > $16::bigint THEN 'newer_write_version'
|
|
8179
|
+
WHEN $16::bigint IS NOT NULL AND target._write_version < $16::bigint THEN 'older_write_version'
|
|
8180
|
+
ELSE 'already_terminal'
|
|
8181
|
+
END AS reason,
|
|
8182
|
+
target._write_version AS row_write_version
|
|
7833
8183
|
FROM matched_updates AS updates
|
|
7834
8184
|
JOIN ${sheetTable(session)} AS target
|
|
7835
8185
|
ON target._key = updates.matched_key
|
|
@@ -7908,7 +8258,7 @@ async function failRuntimeMapRowChunks(
|
|
|
7908
8258
|
)
|
|
7909
8259
|
SELECT
|
|
7910
8260
|
(SELECT count(*)::int FROM applied_rows) AS updated,
|
|
7911
|
-
coalesce((SELECT
|
|
8261
|
+
coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows,
|
|
7912
8262
|
coalesce((SELECT array_agg(_key) FROM same_version_conflicts), '{}'::text[]) AS conflict_keys`,
|
|
7913
8263
|
[
|
|
7914
8264
|
chunkKeys,
|
|
@@ -7930,13 +8280,13 @@ async function failRuntimeMapRowChunks(
|
|
|
7930
8280
|
],
|
|
7931
8281
|
);
|
|
7932
8282
|
const appliedCount = Number(rows[0]?.updated ?? 0);
|
|
7933
|
-
const
|
|
8283
|
+
const chunkFencedRows = parseRuntimeMapRowsFencedRows(rows[0]?.fenced_rows);
|
|
7934
8284
|
const chunkConflictKeys = rows[0]?.conflict_keys ?? [];
|
|
7935
8285
|
updated += appliedCount;
|
|
7936
|
-
|
|
8286
|
+
fencedRows.push(...chunkFencedRows);
|
|
7937
8287
|
conflictKeys.push(...chunkConflictKeys);
|
|
7938
8288
|
}
|
|
7939
|
-
return { updated,
|
|
8289
|
+
return { updated, fencedRows, conflictKeys };
|
|
7940
8290
|
}
|
|
7941
8291
|
|
|
7942
8292
|
/**
|
|
@@ -8006,6 +8356,11 @@ export async function completeRuntimeMapRows(
|
|
|
8006
8356
|
submittedKeys,
|
|
8007
8357
|
updated: result.updated,
|
|
8008
8358
|
fencedKeys: staleKeys,
|
|
8359
|
+
// A gateway that predates stale reasons omits staleRows; keep it
|
|
8360
|
+
// absent so the caller classifies those keys as unreported.
|
|
8361
|
+
fencedRows: Array.isArray(result.staleRows)
|
|
8362
|
+
? parseRuntimeMapRowsFencedRows(result.staleRows)
|
|
8363
|
+
: undefined,
|
|
8009
8364
|
}),
|
|
8010
8365
|
// Preserve the gateway's compatibility classification during rollout.
|
|
8011
8366
|
fencedKeys: result.fencedKeys,
|
|
@@ -8141,11 +8496,11 @@ export async function completeRuntimeMapRows(
|
|
|
8141
8496
|
);
|
|
8142
8497
|
return {
|
|
8143
8498
|
updated: updated.updated + inserted.inserted,
|
|
8144
|
-
|
|
8499
|
+
fencedRows: updated.fencedRows,
|
|
8145
8500
|
conflictKeys: updated.conflictKeys,
|
|
8146
8501
|
};
|
|
8147
8502
|
})()
|
|
8148
|
-
: { updated: 0,
|
|
8503
|
+
: { updated: 0, fencedRows: [], conflictKeys: [] };
|
|
8149
8504
|
const failed =
|
|
8150
8505
|
failedRows.length > 0
|
|
8151
8506
|
? await failRuntimeMapRowChunks(client, session, {
|
|
@@ -8164,7 +8519,7 @@ export async function completeRuntimeMapRows(
|
|
|
8164
8519
|
input.tableNamespace,
|
|
8165
8520
|
),
|
|
8166
8521
|
})
|
|
8167
|
-
: { updated: 0,
|
|
8522
|
+
: { updated: 0, fencedRows: [], conflictKeys: [] };
|
|
8168
8523
|
const conflictKeys = [...completed.conflictKeys, ...failed.conflictKeys];
|
|
8169
8524
|
if (conflictKeys.length > 0) {
|
|
8170
8525
|
throw new Error(
|
|
@@ -8178,7 +8533,7 @@ export async function completeRuntimeMapRows(
|
|
|
8178
8533
|
return classifyRuntimeMapRowsWrite({
|
|
8179
8534
|
submittedKeys: uniqueRows.keys(),
|
|
8180
8535
|
updated: completed.updated + failed.updated,
|
|
8181
|
-
|
|
8536
|
+
fencedRows: [...completed.fencedRows, ...failed.fencedRows],
|
|
8182
8537
|
});
|
|
8183
8538
|
},
|
|
8184
8539
|
);
|
|
@@ -8207,27 +8562,160 @@ export function createCompleteRuntimeMapRowsOperation(
|
|
|
8207
8562
|
return () => completeRuntimeMapRows(context, frozenInput);
|
|
8208
8563
|
}
|
|
8209
8564
|
|
|
8210
|
-
export
|
|
8565
|
+
export type RuntimeSheetDatasetReadInput = {
|
|
8566
|
+
tableNamespace: string;
|
|
8567
|
+
runId?: string | null;
|
|
8568
|
+
rowMode?: 'output' | 'all';
|
|
8569
|
+
limit: number;
|
|
8570
|
+
offset: number;
|
|
8571
|
+
};
|
|
8572
|
+
|
|
8573
|
+
export type RuntimeSheetDatasetReadBatchInput = Omit<
|
|
8574
|
+
RuntimeSheetDatasetReadInput,
|
|
8575
|
+
'limit' | 'offset'
|
|
8576
|
+
> & {
|
|
8577
|
+
pages: readonly RuntimeSheetDatasetPageInput[];
|
|
8578
|
+
};
|
|
8579
|
+
|
|
8580
|
+
export type RuntimeSheetDatasetReadBatchResult = {
|
|
8581
|
+
pages: RuntimeSheetDatasetPage[];
|
|
8582
|
+
};
|
|
8583
|
+
|
|
8584
|
+
function normalizeRuntimeSheetDatasetPage(
|
|
8585
|
+
input: RuntimeSheetDatasetPageInput,
|
|
8586
|
+
): RuntimeSheetDatasetPageInput {
|
|
8587
|
+
if (!input || typeof input !== 'object') {
|
|
8588
|
+
throw new Error('Runtime dataset page input must be an object.');
|
|
8589
|
+
}
|
|
8590
|
+
if (!Number.isFinite(input.limit) || !Number.isFinite(input.offset)) {
|
|
8591
|
+
throw new Error('Runtime dataset page limit and offset must be finite.');
|
|
8592
|
+
}
|
|
8593
|
+
return {
|
|
8594
|
+
limit: Math.max(
|
|
8595
|
+
1,
|
|
8596
|
+
Math.min(DIRECT_POSTGRES_BATCH_SIZE, Math.floor(input.limit)),
|
|
8597
|
+
),
|
|
8598
|
+
offset: Math.max(0, Math.floor(input.offset)),
|
|
8599
|
+
};
|
|
8600
|
+
}
|
|
8601
|
+
|
|
8602
|
+
function assertRuntimeSheetDatasetReadBatchResponse(
|
|
8603
|
+
value: unknown,
|
|
8604
|
+
requestedPages: readonly RuntimeSheetDatasetPageInput[],
|
|
8605
|
+
): asserts value is RuntimeSheetDatasetReadBatchResult {
|
|
8606
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
8607
|
+
throw new Error('Runtime dataset batch response must be an object.');
|
|
8608
|
+
}
|
|
8609
|
+
const pages = (value as { pages?: unknown }).pages;
|
|
8610
|
+
if (!Array.isArray(pages) || pages.length !== requestedPages.length) {
|
|
8611
|
+
throw new Error(
|
|
8612
|
+
`Runtime dataset batch response must contain ${requestedPages.length} correlated page result(s).`,
|
|
8613
|
+
);
|
|
8614
|
+
}
|
|
8615
|
+
for (const [index, requested] of requestedPages.entries()) {
|
|
8616
|
+
const page = pages[index];
|
|
8617
|
+
if (!page || typeof page !== 'object' || Array.isArray(page)) {
|
|
8618
|
+
throw new Error(
|
|
8619
|
+
`Runtime dataset batch response page ${index} must be an object.`,
|
|
8620
|
+
);
|
|
8621
|
+
}
|
|
8622
|
+
const result = page as Partial<RuntimeSheetDatasetPage>;
|
|
8623
|
+
if (
|
|
8624
|
+
result.limit !== requested.limit ||
|
|
8625
|
+
result.offset !== requested.offset ||
|
|
8626
|
+
!Array.isArray(result.rows) ||
|
|
8627
|
+
result.rows.length > requested.limit ||
|
|
8628
|
+
result.rows.some(
|
|
8629
|
+
(row) => !row || typeof row !== 'object' || Array.isArray(row),
|
|
8630
|
+
)
|
|
8631
|
+
) {
|
|
8632
|
+
throw new Error(
|
|
8633
|
+
`Runtime dataset batch response page ${index} is not correlated to its requested offset and limit.`,
|
|
8634
|
+
);
|
|
8635
|
+
}
|
|
8636
|
+
}
|
|
8637
|
+
}
|
|
8638
|
+
|
|
8639
|
+
function assertRuntimeSheetDatasetReadBatchWithinBudget(
|
|
8640
|
+
value: RuntimeSheetDatasetReadBatchResult,
|
|
8641
|
+
): void {
|
|
8642
|
+
const measurement = jsonByteLengthUpTo(
|
|
8643
|
+
value,
|
|
8644
|
+
RUNTIME_SHEET_READ_BATCH_MAX_RESPONSE_BYTES,
|
|
8645
|
+
);
|
|
8646
|
+
if (measurement.exceeded) {
|
|
8647
|
+
throw new RuntimeSheetDatasetReadBatchTooLargeError(measurement.bytes);
|
|
8648
|
+
}
|
|
8649
|
+
}
|
|
8650
|
+
|
|
8651
|
+
function isRuntimeSheetDatasetReadBatchUnsupportedError(
|
|
8652
|
+
error: unknown,
|
|
8653
|
+
): boolean {
|
|
8654
|
+
return (
|
|
8655
|
+
error instanceof Error &&
|
|
8656
|
+
/Unsupported runtime data-plane action/i.test(error.message)
|
|
8657
|
+
);
|
|
8658
|
+
}
|
|
8659
|
+
|
|
8660
|
+
/**
|
|
8661
|
+
* Redeem several ordered dataset pages under one scoped DB session or gateway
|
|
8662
|
+
* request. The response deliberately stays positional: a caller can only
|
|
8663
|
+
* receive the page it asked for, in the order it asked for it.
|
|
8664
|
+
*/
|
|
8665
|
+
export async function readRuntimeSheetDatasetRowsBatch(
|
|
8211
8666
|
context: RuntimeApiContext & { playName: string },
|
|
8212
|
-
input:
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8667
|
+
input: RuntimeSheetDatasetReadBatchInput,
|
|
8668
|
+
): Promise<RuntimeSheetDatasetReadBatchResult> {
|
|
8669
|
+
if (!Array.isArray(input.pages)) {
|
|
8670
|
+
throw new Error('Runtime dataset read batch pages must be an array.');
|
|
8671
|
+
}
|
|
8672
|
+
if (
|
|
8673
|
+
input.pages.length === 0 ||
|
|
8674
|
+
input.pages.length > RUNTIME_SHEET_READ_BATCH_MAX_PAGES
|
|
8675
|
+
) {
|
|
8676
|
+
throw new Error(
|
|
8677
|
+
`Runtime dataset read batch requires 1-${RUNTIME_SHEET_READ_BATCH_MAX_PAGES} pages.`,
|
|
8678
|
+
);
|
|
8679
|
+
}
|
|
8680
|
+
const pages = input.pages.map(normalizeRuntimeSheetDatasetPage);
|
|
8220
8681
|
if (context.dbSessionStrategy === 'gateway_only') {
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8682
|
+
try {
|
|
8683
|
+
const result = await postRuntimeApi<RuntimeSheetDatasetReadBatchResult>(
|
|
8684
|
+
context,
|
|
8685
|
+
{
|
|
8686
|
+
action: 'runtime_sheet_read_rows_batch',
|
|
8687
|
+
input: { ...input, pages },
|
|
8688
|
+
},
|
|
8689
|
+
);
|
|
8690
|
+
assertRuntimeSheetDatasetReadBatchResponse(result, pages);
|
|
8691
|
+
return result;
|
|
8692
|
+
} catch (error) {
|
|
8693
|
+
// New runners can cross a rolling gateway fleet. Keep the old
|
|
8694
|
+
// single-page action readable until the batch action is everywhere;
|
|
8695
|
+
// this is a compatibility fallback, never a second read of an accepted
|
|
8696
|
+
// batch response.
|
|
8697
|
+
if (!isRuntimeSheetDatasetReadBatchUnsupportedError(error)) {
|
|
8698
|
+
throw error;
|
|
8699
|
+
}
|
|
8700
|
+
const legacyPages: RuntimeSheetDatasetPage[] = [];
|
|
8701
|
+
for (const page of pages) {
|
|
8702
|
+
legacyPages.push(
|
|
8703
|
+
await postRuntimeApi(context, {
|
|
8704
|
+
action: 'runtime_sheet_read_rows',
|
|
8705
|
+
input: {
|
|
8706
|
+
tableNamespace: input.tableNamespace,
|
|
8707
|
+
runId: input.runId,
|
|
8708
|
+
rowMode: input.rowMode,
|
|
8709
|
+
...page,
|
|
8710
|
+
},
|
|
8711
|
+
}),
|
|
8712
|
+
);
|
|
8713
|
+
}
|
|
8714
|
+
const result = { pages: legacyPages };
|
|
8715
|
+
assertRuntimeSheetDatasetReadBatchResponse(result, pages);
|
|
8716
|
+
return result;
|
|
8717
|
+
}
|
|
8225
8718
|
}
|
|
8226
|
-
const limit = Math.max(
|
|
8227
|
-
1,
|
|
8228
|
-
Math.min(DIRECT_POSTGRES_BATCH_SIZE, Math.floor(input.limit)),
|
|
8229
|
-
);
|
|
8230
|
-
const offset = Math.max(0, Math.floor(input.offset));
|
|
8231
8719
|
const session = requireRuntimePostgresSession(
|
|
8232
8720
|
await getRuntimeDbSession(
|
|
8233
8721
|
{
|
|
@@ -8239,20 +8727,176 @@ export async function readRuntimeSheetDatasetRows(
|
|
|
8239
8727
|
tableNamespace: input.tableNamespace,
|
|
8240
8728
|
logicalTable: 'sheet_rows',
|
|
8241
8729
|
operations: ['rows.read'],
|
|
8242
|
-
limits: {
|
|
8730
|
+
limits: {
|
|
8731
|
+
maxRows: runtimeDbSessionRowLimit(
|
|
8732
|
+
pages.reduce((total, page) => total + page.limit, 0),
|
|
8733
|
+
),
|
|
8734
|
+
},
|
|
8243
8735
|
},
|
|
8244
8736
|
),
|
|
8245
8737
|
);
|
|
8246
|
-
const
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8738
|
+
const results: RuntimeSheetDatasetPage[] = [];
|
|
8739
|
+
for (const page of pages) {
|
|
8740
|
+
const rows = await readRuntimeRows(session, {
|
|
8741
|
+
limit: page.limit,
|
|
8742
|
+
offset: page.offset,
|
|
8743
|
+
runId: input.runId ?? null,
|
|
8744
|
+
rowMode: input.rowMode,
|
|
8745
|
+
});
|
|
8746
|
+
results.push({
|
|
8747
|
+
rows: rows.map((row) => row.data),
|
|
8748
|
+
limit: page.limit,
|
|
8749
|
+
offset: page.offset,
|
|
8750
|
+
});
|
|
8751
|
+
// Stop before a later page turns a bounded response into several retained
|
|
8752
|
+
// page bodies. The caller will split this read in offset order.
|
|
8753
|
+
assertRuntimeSheetDatasetReadBatchWithinBudget({ pages: results });
|
|
8754
|
+
}
|
|
8755
|
+
const result = { pages: results };
|
|
8756
|
+
assertRuntimeSheetDatasetReadBatchResponse(result, pages);
|
|
8757
|
+
assertRuntimeSheetDatasetReadBatchWithinBudget(result);
|
|
8758
|
+
return result;
|
|
8759
|
+
}
|
|
8760
|
+
|
|
8761
|
+
export async function readRuntimeSheetDatasetRows(
|
|
8762
|
+
context: RuntimeApiContext & { playName: string },
|
|
8763
|
+
input: RuntimeSheetDatasetReadInput,
|
|
8764
|
+
): Promise<RuntimeSheetDatasetPage> {
|
|
8765
|
+
const result = await readRuntimeSheetDatasetRowsBatch(context, {
|
|
8766
|
+
tableNamespace: input.tableNamespace,
|
|
8767
|
+
runId: input.runId,
|
|
8250
8768
|
rowMode: input.rowMode,
|
|
8769
|
+
pages: [{ limit: input.limit, offset: input.offset }],
|
|
8770
|
+
});
|
|
8771
|
+
return result.pages[0]!;
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
async function readRuntimeSheetDatasetPageWithAdaptiveLimit(
|
|
8775
|
+
context: RuntimeApiContext & { playName: string },
|
|
8776
|
+
input: RuntimeSheetDatasetReadInput,
|
|
8777
|
+
knownTooLarge = false,
|
|
8778
|
+
): Promise<RuntimeSheetDatasetPage> {
|
|
8779
|
+
if (!knownTooLarge) {
|
|
8780
|
+
try {
|
|
8781
|
+
return await readRuntimeSheetDatasetRows(context, input);
|
|
8782
|
+
} catch (error) {
|
|
8783
|
+
if (
|
|
8784
|
+
!(error instanceof RuntimeSheetDatasetReadBatchTooLargeError) ||
|
|
8785
|
+
input.limit <= 1
|
|
8786
|
+
) {
|
|
8787
|
+
throw error;
|
|
8788
|
+
}
|
|
8789
|
+
}
|
|
8790
|
+
}
|
|
8791
|
+
if (input.limit <= 1) {
|
|
8792
|
+
throw new RuntimeSheetDatasetReadBatchTooLargeError(
|
|
8793
|
+
RUNTIME_SHEET_READ_BATCH_MAX_RESPONSE_BYTES + 1,
|
|
8794
|
+
);
|
|
8795
|
+
}
|
|
8796
|
+
const firstLimit = Math.ceil(input.limit / 2);
|
|
8797
|
+
const first = await readRuntimeSheetDatasetPageWithAdaptiveLimit(context, {
|
|
8798
|
+
...input,
|
|
8799
|
+
limit: firstLimit,
|
|
8800
|
+
});
|
|
8801
|
+
if (first.rows.length < firstLimit) {
|
|
8802
|
+
return { ...first, limit: input.limit };
|
|
8803
|
+
}
|
|
8804
|
+
const second = await readRuntimeSheetDatasetPageWithAdaptiveLimit(context, {
|
|
8805
|
+
...input,
|
|
8806
|
+
limit: input.limit - firstLimit,
|
|
8807
|
+
offset: input.offset + first.rows.length,
|
|
8251
8808
|
});
|
|
8252
8809
|
return {
|
|
8253
|
-
rows: rows
|
|
8254
|
-
limit,
|
|
8255
|
-
offset,
|
|
8810
|
+
rows: [...first.rows, ...second.rows],
|
|
8811
|
+
limit: input.limit,
|
|
8812
|
+
offset: input.offset,
|
|
8813
|
+
};
|
|
8814
|
+
}
|
|
8815
|
+
|
|
8816
|
+
export type RuntimeSheetDatasetPageReader = {
|
|
8817
|
+
read(input: RuntimeSheetDatasetPageInput): Promise<RuntimeSheetDatasetPage>;
|
|
8818
|
+
};
|
|
8819
|
+
|
|
8820
|
+
/**
|
|
8821
|
+
* The common lazy-handle reader. It batches only pages that share every
|
|
8822
|
+
* authority fact and has bounded request retention, active requests, and
|
|
8823
|
+
* decoded response size. The rare oversized response is split in order rather
|
|
8824
|
+
* than silently truncating the dataset or increasing the memory ceiling.
|
|
8825
|
+
*/
|
|
8826
|
+
export function createRuntimeSheetDatasetPageReader(input: {
|
|
8827
|
+
context: RuntimeApiContext & { playName: string };
|
|
8828
|
+
tableNamespace: string;
|
|
8829
|
+
runId?: string | null;
|
|
8830
|
+
rowMode?: 'output' | 'all';
|
|
8831
|
+
}): RuntimeSheetDatasetPageReader {
|
|
8832
|
+
const tokenDigest = createHash('sha256')
|
|
8833
|
+
.update(input.context.executorToken?.trim() ?? '')
|
|
8834
|
+
.digest('hex')
|
|
8835
|
+
.slice(0, 24);
|
|
8836
|
+
const scope = [
|
|
8837
|
+
input.context.baseUrl?.trim() ?? '',
|
|
8838
|
+
tokenDigest,
|
|
8839
|
+
input.context.orgId?.trim() ?? '',
|
|
8840
|
+
input.context.playName,
|
|
8841
|
+
input.runId?.trim() ?? input.context.runId?.trim() ?? '',
|
|
8842
|
+
input.tableNamespace,
|
|
8843
|
+
input.rowMode ?? 'output',
|
|
8844
|
+
].join('::');
|
|
8845
|
+
const reads = new BoundedBatchFunction<
|
|
8846
|
+
RuntimeSheetDatasetPageInput,
|
|
8847
|
+
RuntimeSheetDatasetPage,
|
|
8848
|
+
string
|
|
8849
|
+
>({
|
|
8850
|
+
keyId: (key) => key,
|
|
8851
|
+
estimateBytes: (page) =>
|
|
8852
|
+
new TextEncoder().encode(JSON.stringify(page)).byteLength,
|
|
8853
|
+
execute: async ({ values }) => {
|
|
8854
|
+
const read = (pages: readonly RuntimeSheetDatasetPageInput[]) =>
|
|
8855
|
+
readRuntimeSheetDatasetRowsBatch(input.context, {
|
|
8856
|
+
tableNamespace: input.tableNamespace,
|
|
8857
|
+
runId: input.runId,
|
|
8858
|
+
rowMode: input.rowMode,
|
|
8859
|
+
pages,
|
|
8860
|
+
});
|
|
8861
|
+
try {
|
|
8862
|
+
return (await read(values)).pages;
|
|
8863
|
+
} catch (error) {
|
|
8864
|
+
if (!(error instanceof RuntimeSheetDatasetReadBatchTooLargeError)) {
|
|
8865
|
+
throw error;
|
|
8866
|
+
}
|
|
8867
|
+
const results: RuntimeSheetDatasetPage[] = [];
|
|
8868
|
+
for (const [index, page] of values.entries()) {
|
|
8869
|
+
results.push(
|
|
8870
|
+
await readRuntimeSheetDatasetPageWithAdaptiveLimit(
|
|
8871
|
+
input.context,
|
|
8872
|
+
{
|
|
8873
|
+
tableNamespace: input.tableNamespace,
|
|
8874
|
+
runId: input.runId,
|
|
8875
|
+
rowMode: input.rowMode,
|
|
8876
|
+
...page,
|
|
8877
|
+
},
|
|
8878
|
+
values.length === 1 && index === 0,
|
|
8879
|
+
),
|
|
8880
|
+
);
|
|
8881
|
+
}
|
|
8882
|
+
return results;
|
|
8883
|
+
}
|
|
8884
|
+
},
|
|
8885
|
+
maxBatchItems: RUNTIME_SHEET_READ_BATCH_MAX_PAGES,
|
|
8886
|
+
maxBatchBytes: 16 * 1024,
|
|
8887
|
+
maxRetainedItems: RUNTIME_SHEET_READ_BATCH_MAX_RETAINED_PAGES,
|
|
8888
|
+
maxRetainedBytes: RUNTIME_SHEET_READ_BATCH_MAX_RETAINED_REQUEST_BYTES,
|
|
8889
|
+
maxInFlightBatches: RUNTIME_SHEET_READ_BATCH_MAX_IN_FLIGHT,
|
|
8890
|
+
flushMs: RUNTIME_SHEET_READ_BATCH_FLUSH_MS,
|
|
8891
|
+
});
|
|
8892
|
+
return {
|
|
8893
|
+
read: (page) =>
|
|
8894
|
+
reads.submit({
|
|
8895
|
+
key: scope,
|
|
8896
|
+
value: normalizeRuntimeSheetDatasetPage(page),
|
|
8897
|
+
signal: input.context.abortSignal ?? undefined,
|
|
8898
|
+
maxWaitMs: RUNTIME_SHEET_READ_BATCH_MAX_QUEUE_WAIT_MS,
|
|
8899
|
+
}),
|
|
8256
8900
|
};
|
|
8257
8901
|
}
|
|
8258
8902
|
|
|
@@ -8622,6 +9266,11 @@ export function createRuntimeBackedPlayDataset(input: {
|
|
|
8622
9266
|
...input.context,
|
|
8623
9267
|
runId: input.context.runId ?? input.runId,
|
|
8624
9268
|
};
|
|
9269
|
+
const pageReader = createRuntimeSheetDatasetPageReader({
|
|
9270
|
+
context: runtimeContext,
|
|
9271
|
+
tableNamespace: input.tableNamespace,
|
|
9272
|
+
runId: input.runId ?? null,
|
|
9273
|
+
});
|
|
8625
9274
|
return createDeferredPlayDataset({
|
|
8626
9275
|
datasetKind: input.datasetKind,
|
|
8627
9276
|
datasetId: createRuntimeDatasetId(
|
|
@@ -8656,62 +9305,30 @@ export function createRuntimeBackedPlayDataset(input: {
|
|
|
8656
9305
|
return Number(summary.stats.total ?? 0);
|
|
8657
9306
|
},
|
|
8658
9307
|
peek: async (limit) => {
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
logicalTable: 'sheet_rows',
|
|
8662
|
-
operations: ['rows.read'],
|
|
8663
|
-
});
|
|
8664
|
-
const rows = await readRuntimeRows(
|
|
8665
|
-
requireRuntimePostgresSession(session),
|
|
8666
|
-
{
|
|
8667
|
-
limit,
|
|
8668
|
-
offset: 0,
|
|
8669
|
-
runId: input.runId ?? null,
|
|
8670
|
-
},
|
|
8671
|
-
);
|
|
8672
|
-
return rows.map((row) => row.data);
|
|
9308
|
+
if (limit <= 0) return [];
|
|
9309
|
+
return (await pageReader.read({ limit, offset: 0 })).rows;
|
|
8673
9310
|
},
|
|
8674
9311
|
at: async (index) => {
|
|
8675
|
-
|
|
8676
|
-
tableNamespace: input.tableNamespace,
|
|
8677
|
-
logicalTable: 'sheet_rows',
|
|
8678
|
-
operations: ['rows.read'],
|
|
8679
|
-
});
|
|
8680
|
-
const rows = await readRuntimeRows(
|
|
8681
|
-
requireRuntimePostgresSession(session),
|
|
8682
|
-
{
|
|
8683
|
-
limit: 1,
|
|
8684
|
-
offset: index,
|
|
8685
|
-
runId: input.runId ?? null,
|
|
8686
|
-
},
|
|
8687
|
-
);
|
|
8688
|
-
return rows[0]?.data;
|
|
9312
|
+
return (await pageReader.read({ limit: 1, offset: index })).rows[0];
|
|
8689
9313
|
},
|
|
8690
9314
|
materialize: async (limit) => {
|
|
8691
9315
|
const pageSize = 1000;
|
|
8692
9316
|
const materialized: Record<string, unknown>[] = [];
|
|
8693
9317
|
let offset = 0;
|
|
8694
9318
|
while (true) {
|
|
8695
|
-
const
|
|
8696
|
-
|
|
8697
|
-
logicalTable: 'sheet_rows',
|
|
8698
|
-
operations: ['rows.read'],
|
|
8699
|
-
});
|
|
8700
|
-
const rows = await readRuntimeRows(
|
|
8701
|
-
requireRuntimePostgresSession(session),
|
|
8702
|
-
{
|
|
9319
|
+
const rows = (
|
|
9320
|
+
await pageReader.read({
|
|
8703
9321
|
limit:
|
|
8704
9322
|
limit !== undefined
|
|
8705
9323
|
? Math.min(pageSize, Math.max(0, limit - materialized.length))
|
|
8706
9324
|
: pageSize,
|
|
8707
9325
|
offset,
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
);
|
|
9326
|
+
})
|
|
9327
|
+
).rows;
|
|
8711
9328
|
if (rows.length === 0) {
|
|
8712
9329
|
break;
|
|
8713
9330
|
}
|
|
8714
|
-
materialized.push(...rows
|
|
9331
|
+
materialized.push(...rows);
|
|
8715
9332
|
if (limit !== undefined && materialized.length >= limit) {
|
|
8716
9333
|
return materialized.slice(0, limit);
|
|
8717
9334
|
}
|
|
@@ -8725,26 +9342,17 @@ export function createRuntimeBackedPlayDataset(input: {
|
|
|
8725
9342
|
const pageSize = 1000;
|
|
8726
9343
|
let offset = 0;
|
|
8727
9344
|
while (true) {
|
|
8728
|
-
const
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
operations: ['rows.read'],
|
|
9345
|
+
const page = await pageReader.read({
|
|
9346
|
+
limit: pageSize,
|
|
9347
|
+
offset,
|
|
8732
9348
|
});
|
|
8733
|
-
|
|
8734
|
-
requireRuntimePostgresSession(session),
|
|
8735
|
-
{
|
|
8736
|
-
limit: pageSize,
|
|
8737
|
-
offset,
|
|
8738
|
-
runId: input.runId ?? null,
|
|
8739
|
-
},
|
|
8740
|
-
);
|
|
8741
|
-
if (page.length === 0) {
|
|
9349
|
+
if (page.rows.length === 0) {
|
|
8742
9350
|
return;
|
|
8743
9351
|
}
|
|
8744
|
-
for (const row of page) {
|
|
8745
|
-
yield row
|
|
9352
|
+
for (const row of page.rows) {
|
|
9353
|
+
yield row;
|
|
8746
9354
|
}
|
|
8747
|
-
offset += page.length;
|
|
9355
|
+
offset += page.rows.length;
|
|
8748
9356
|
}
|
|
8749
9357
|
},
|
|
8750
9358
|
}) as AsyncIterable<Record<string, unknown>>,
|