deepline 0.2.53 → 0.2.55
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 +17 -1
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +43 -1
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +30 -1
- package/dist/bundling-sources/shared_libs/play-runtime/cell-provenance.ts +231 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1094 -128
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +178 -9
- package/dist/bundling-sources/shared_libs/play-runtime/docflow-node-io.ts +634 -0
- package/dist/bundling-sources/shared_libs/play-runtime/docflow-observation.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/dynamic-worker-version.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +18 -0
- package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/log-provenance.ts +251 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-node-scope.ts +160 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +43 -5
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +26 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +83 -0
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +3 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +49 -1
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +375 -29
- package/dist/bundling-sources/shared_libs/plays/docflow-binding-owner.ts +636 -0
- package/dist/bundling-sources/shared_libs/plays/docflow-binding.ts +598 -0
- package/dist/bundling-sources/shared_libs/plays/docflow.ts +1645 -0
- package/dist/bundling-sources/shared_libs/plays/play-exports.ts +202 -0
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +16 -1
- package/dist/bundling-sources/shared_libs/plays/ts-ast.ts +48 -0
- package/dist/cli/index.js +994 -312
- package/dist/cli/index.mjs +994 -312
- package/dist/{compiler-manifest-Cj3--4ZJ.d.mts → compiler-manifest-Bl8kmLx9.d.mts} +118 -0
- package/dist/{compiler-manifest-Cj3--4ZJ.d.ts → compiler-manifest-Bl8kmLx9.d.ts} +118 -0
- package/dist/index.d.mts +47 -2
- package/dist/index.d.ts +47 -2
- package/dist/index.js +419 -59
- package/dist/index.mjs +419 -59
- package/dist/install-integrity.json +12 -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 +1361 -45
- package/package.json +1 -1
|
@@ -205,8 +205,21 @@ function resolvePlayRunRuntimeSelection(
|
|
|
205
205
|
process.env.DEEPLINE_RUNTIME_ENVIRONMENT_TOKEN?.trim();
|
|
206
206
|
if (!configured) {
|
|
207
207
|
if (configuredNamespace || configuredToken) {
|
|
208
|
+
// Name the variables that are actually set. This branch is almost always
|
|
209
|
+
// hit by a stale value auto-loaded from a .env/.env.local rather than by
|
|
210
|
+
// a deliberate export, so the message has to say where to look and how
|
|
211
|
+
// to run the command anyway.
|
|
212
|
+
const present: string[] = [];
|
|
213
|
+
if (configuredNamespace) present.push('DEEPLINE_RUNTIME_NAMESPACE');
|
|
214
|
+
if (configuredToken) present.push('DEEPLINE_RUNTIME_ENVIRONMENT_TOKEN');
|
|
215
|
+
const isPlural = present.length > 1;
|
|
208
216
|
throw new DeeplineError(
|
|
209
|
-
|
|
217
|
+
[
|
|
218
|
+
`Incomplete play runtime selection: ${present.join(' and ')} ${isPlural ? 'are' : 'is'} set but DEEPLINE_RUNTIME_ENVIRONMENT is not.`,
|
|
219
|
+
'Preview routing requires DEEPLINE_RUNTIME_ENVIRONMENT=preview together with DEEPLINE_RUNTIME_NAMESPACE; the token alone authorizes nothing.',
|
|
220
|
+
`If you did not export ${isPlural ? 'these' : 'this'} yourself, ${isPlural ? 'they were' : 'it was'} most likely auto-loaded from a .env or .env.local in the current directory (bun loads those automatically, and in a git worktree .env.local is usually a symlink to the main checkout).`,
|
|
221
|
+
`To run against the app-native runtime, clear ${isPlural ? 'them' : 'it'} for this command: ${present.map((name) => `${name}=`).join(' ')} <your deepline command>`,
|
|
222
|
+
].join('\n'),
|
|
210
223
|
undefined,
|
|
211
224
|
'INVALID_RUNTIME_ENVIRONMENT',
|
|
212
225
|
);
|
|
@@ -2448,6 +2461,9 @@ export class DeeplineClient {
|
|
|
2448
2461
|
sourceFiles?: Record<string, string>;
|
|
2449
2462
|
description?: string;
|
|
2450
2463
|
artifact: Record<string, unknown>;
|
|
2464
|
+
/** Which exported play in `sourceCode` this artifact is, when the file
|
|
2465
|
+
* exports more than one. Omit for the default export. */
|
|
2466
|
+
exportName?: string | null;
|
|
2451
2467
|
integrationMode?: 'live' | 'eval_stub' | 'fixture';
|
|
2452
2468
|
/**
|
|
2453
2469
|
* Sibling plays from the same local bundle graph. Lets the server splice
|
|
@@ -39,7 +39,7 @@ export type {
|
|
|
39
39
|
|
|
40
40
|
export { extractDefinedPlayName } from '../../../shared_libs/plays/bundling/index.js';
|
|
41
41
|
|
|
42
|
-
const PLAY_BUNDLE_CACHE_VERSION =
|
|
42
|
+
const PLAY_BUNDLE_CACHE_VERSION = 33;
|
|
43
43
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
44
44
|
const SDK_PACKAGE_ROOT = resolve(MODULE_DIR, '..', '..');
|
|
45
45
|
const SOURCE_REPO_ROOT = resolve(SDK_PACKAGE_ROOT, '..');
|
|
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
|
|
|
160
160
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
161
161
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
162
162
|
// release keeps lazy paging semantics independent of row residency.
|
|
163
|
-
version: '0.2.
|
|
163
|
+
version: '0.2.55',
|
|
164
164
|
contracts: {
|
|
165
165
|
api: {
|
|
166
166
|
name: 'sdk-http-api',
|
|
@@ -1270,6 +1270,14 @@ export interface PlayCheckResult {
|
|
|
1270
1270
|
graphHash?: string | null;
|
|
1271
1271
|
/** SHA-256 of the exact source bytes checked by Deepline. */
|
|
1272
1272
|
sourceHash?: string | null;
|
|
1273
|
+
/**
|
|
1274
|
+
* Per-export results, present ONLY when the checked file exports more than
|
|
1275
|
+
* one play. Single-play files keep the exact flat shape they always had.
|
|
1276
|
+
* Entry 0 is the default export and mirrors the top-level fields; the
|
|
1277
|
+
* top-level `valid` is the AND over every entry, so an agent that reads only
|
|
1278
|
+
* `valid` can never ship a file whose second play fails.
|
|
1279
|
+
*/
|
|
1280
|
+
exports?: PlayCheckExportResult[];
|
|
1273
1281
|
/** Enforceable byte budgets measured during cloud preflight. */
|
|
1274
1282
|
limits?: {
|
|
1275
1283
|
revisionStorage: {
|
|
@@ -1291,6 +1299,29 @@ export interface PlayCheckResult {
|
|
|
1291
1299
|
};
|
|
1292
1300
|
}
|
|
1293
1301
|
|
|
1302
|
+
/**
|
|
1303
|
+
* One exported play's check result inside a multi-play file. Carries the same
|
|
1304
|
+
* per-play fields as {@link PlayCheckResult}, unprefixed and unaggregated, so a
|
|
1305
|
+
* consumer can attribute an error to the export that produced it.
|
|
1306
|
+
*/
|
|
1307
|
+
export interface PlayCheckExportResult {
|
|
1308
|
+
/** Canonical export name: `default`, or the named export (`batch`). */
|
|
1309
|
+
exportName: string;
|
|
1310
|
+
/** The `definePlay` name this export declares. */
|
|
1311
|
+
name?: string | null;
|
|
1312
|
+
valid: boolean;
|
|
1313
|
+
errors: string[];
|
|
1314
|
+
warnings?: string[];
|
|
1315
|
+
issues?: PlayCheckIssue[];
|
|
1316
|
+
staticPipeline?: Record<string, unknown> | null;
|
|
1317
|
+
artifactHash?: string | null;
|
|
1318
|
+
graphHash?: string | null;
|
|
1319
|
+
sourceHash?: string | null;
|
|
1320
|
+
summary?: string;
|
|
1321
|
+
recognized?: PlayCheckRecognizedSummary;
|
|
1322
|
+
triggers?: PlayCheckTriggersSummary | null;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1294
1325
|
/** Severity of a {@link PlayCheckIssue}. `error` fails the check; `warning` does not. */
|
|
1295
1326
|
export type PlayCheckIssueSeverity = 'error' | 'warning';
|
|
1296
1327
|
|
|
@@ -1305,6 +1336,12 @@ export interface PlayCheckIssue {
|
|
|
1305
1336
|
code: string;
|
|
1306
1337
|
severity: PlayCheckIssueSeverity;
|
|
1307
1338
|
message: string;
|
|
1339
|
+
/**
|
|
1340
|
+
* Which exported play raised this issue, when the checked file exports more
|
|
1341
|
+
* than one and this is not the default export. Absent everywhere else, so
|
|
1342
|
+
* the structured channel stays byte-identical for single-play files.
|
|
1343
|
+
*/
|
|
1344
|
+
exportName?: string;
|
|
1308
1345
|
path?: string;
|
|
1309
1346
|
hint?: string;
|
|
1310
1347
|
validOptions?: string[];
|
|
@@ -1318,7 +1355,12 @@ export interface PlayCheckIssue {
|
|
|
1318
1355
|
export interface PlayCheckRecognizedSummary {
|
|
1319
1356
|
triggers?: PlayCheckTriggersSummary;
|
|
1320
1357
|
tools?: string[];
|
|
1321
|
-
|
|
1358
|
+
/**
|
|
1359
|
+
* Durable datasets the play produces. `undrawnColumns` echoes the columns the
|
|
1360
|
+
* author declared out of an authored `@mermaid` diagram with
|
|
1361
|
+
* `.run({ undrawnColumns: [...] })`, so an opt-out is visible in check output.
|
|
1362
|
+
*/
|
|
1363
|
+
datasets?: { name: string; columns?: string[]; undrawnColumns?: string[] }[];
|
|
1322
1364
|
inputs?: string[];
|
|
1323
1365
|
outputs?: string[];
|
|
1324
1366
|
}
|
|
@@ -5,6 +5,7 @@ import { pipeline } from 'node:stream/promises';
|
|
|
5
5
|
import type { PlaySheetContract } from '@shared_libs/plays/static-pipeline';
|
|
6
6
|
import type { PlayRowUpdate } from '@shared_libs/play-runtime/ctx-types';
|
|
7
7
|
import type { PlayRunTimelineEntry } from '@shared_libs/play-runtime/live-events';
|
|
8
|
+
import type { PlayDatasetBornFrom } from '@shared_libs/play-runtime/cell-provenance';
|
|
8
9
|
import {
|
|
9
10
|
routePlayActivityObservation,
|
|
10
11
|
type PlayActivityObservation,
|
|
@@ -84,6 +85,8 @@ export type RuntimeStatusUpdate = {
|
|
|
84
85
|
succeededRows?: number;
|
|
85
86
|
failedRows?: number;
|
|
86
87
|
complete?: boolean;
|
|
88
|
+
/** Dataset-grain row birth, stated once per dataset (ADR 0019). */
|
|
89
|
+
bornFrom?: PlayDatasetBornFrom;
|
|
87
90
|
at: number;
|
|
88
91
|
}>;
|
|
89
92
|
/** Sparse typed activity facts. Routing/deduplication happens server-side. */
|
|
@@ -1432,7 +1435,13 @@ async function updateRunStatusViaAppRuntimeUnlocked(
|
|
|
1432
1435
|
(event.persistedRows ?? 0) > current.persistedRows ||
|
|
1433
1436
|
(event.succeededRows ?? 0) > current.succeededRows ||
|
|
1434
1437
|
(event.failedRows ?? 0) > current.failedRows ||
|
|
1435
|
-
(event.complete === true && current.complete !== true)
|
|
1438
|
+
(event.complete === true && current.complete !== true) ||
|
|
1439
|
+
// A birth record the snapshot has not seen — or a larger admitted-row
|
|
1440
|
+
// count from a later page — is itself a change, or the dataset would keep
|
|
1441
|
+
// an unattributed or stale birth for the whole run.
|
|
1442
|
+
(event.bornFrom !== undefined &&
|
|
1443
|
+
(!current.bornFrom ||
|
|
1444
|
+
event.bornFrom.rowCountIn > current.bornFrom.rowCountIn));
|
|
1436
1445
|
if (!changesSnapshot) continue;
|
|
1437
1446
|
events.push({
|
|
1438
1447
|
type: 'dataset.lifecycle',
|
|
@@ -1453,6 +1462,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
|
|
|
1453
1462
|
? {}
|
|
1454
1463
|
: { failedRows: event.failedRows }),
|
|
1455
1464
|
...(event.complete === undefined ? {} : { complete: event.complete }),
|
|
1465
|
+
...(event.bornFrom === undefined ? {} : { bornFrom: event.bornFrom }),
|
|
1456
1466
|
});
|
|
1457
1467
|
}
|
|
1458
1468
|
const latestActivities = new Map(
|
|
@@ -1908,3 +1918,22 @@ export async function skipRuntimeStepReceiptViaAppRuntime(
|
|
|
1908
1918
|
...input,
|
|
1909
1919
|
});
|
|
1910
1920
|
}
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* Post one durable docflow observation to the receipt gateway (ADR 0016 rule
|
|
1924
|
+
* 2). Callers invoke this fire-and-forget; it resolves on the gateway ack and
|
|
1925
|
+
* surfaces transport failures to its caller (the runner's observation sink),
|
|
1926
|
+
* which swallows-and-logs so the play body is never affected.
|
|
1927
|
+
*/
|
|
1928
|
+
export async function observeDocflowNodeViaAppRuntime(
|
|
1929
|
+
context: WorkerRuntimeApiContext,
|
|
1930
|
+
input: Omit<
|
|
1931
|
+
Extract<RuntimeApiRequest, { action: 'observe_docflow_node' }>,
|
|
1932
|
+
'action'
|
|
1933
|
+
>,
|
|
1934
|
+
): Promise<void> {
|
|
1935
|
+
await postAppRuntimeApi<{ ok?: boolean }>(context, {
|
|
1936
|
+
action: 'observe_docflow_node',
|
|
1937
|
+
...input,
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cell-level provenance capture (ADR 0019).
|
|
3
|
+
*
|
|
4
|
+
* ADR 0018 answered *which producer* filled a cell. This module carries the
|
|
5
|
+
* other half — *what that producer was given*, *which branch was taken*, and
|
|
6
|
+
* *where the row came from* — as additive fields on writes the runtime already
|
|
7
|
+
* makes. Nothing here performs a round trip, and every value is a fact the
|
|
8
|
+
* runtime held at the write site rather than a declaration it trusted or a
|
|
9
|
+
* mapping it reconstructed afterwards.
|
|
10
|
+
*
|
|
11
|
+
* Shared by the runtime (`ctx-types`/`context`), the run ledger, and the
|
|
12
|
+
* durable sheet parse path, so all three agree on one shape and one bound.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One cell version a cell was computed from.
|
|
17
|
+
*
|
|
18
|
+
* Both addressing components are omitted when they match the cell that carries
|
|
19
|
+
* the ref: no `table` means the same table, no `rowKey` means the same row.
|
|
20
|
+
* That is not shorthand for the reader's convenience — it is what keeps the
|
|
21
|
+
* trace inside the retained-row memory budget. Runtime sheet row keys are
|
|
22
|
+
* namespace-salted hashes, and repeating one per ref per cell inflated a
|
|
23
|
+
* mapped row by roughly forty percent, which the streaming-dataset budget test
|
|
24
|
+
* rejects. Readers fill both in from the cell's own address; the durable sheet
|
|
25
|
+
* read model does this so every consumer sees a complete ref.
|
|
26
|
+
*
|
|
27
|
+
* `version` is the attempt/fence identity of the value read and is set only
|
|
28
|
+
* where the runtime already held one: absent means "not witnessed", never
|
|
29
|
+
* "version 0".
|
|
30
|
+
*/
|
|
31
|
+
export interface PlayCellReadRef {
|
|
32
|
+
table?: string;
|
|
33
|
+
rowKey?: string;
|
|
34
|
+
column: string;
|
|
35
|
+
version?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The branch a per-row control-flow evaluation selected, with the read-set the
|
|
40
|
+
* evaluator was handed. `branch` is the runtime's own vocabulary, never
|
|
41
|
+
* authored free text.
|
|
42
|
+
*/
|
|
43
|
+
export interface PlayCellDecision {
|
|
44
|
+
branch: string;
|
|
45
|
+
reads: PlayCellReadRef[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Row-grain birth: the source row the runtime held while building this row. */
|
|
49
|
+
export type PlayRowBornFromRow = { table: string; rowKey: string };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Dataset-grain birth: rows arrived from free-form play-body code and no
|
|
53
|
+
* per-row mapping was witnessed. It names the destination table and how many
|
|
54
|
+
* rows entered, and deliberately says nothing about any single row.
|
|
55
|
+
*
|
|
56
|
+
* There is no `step` field. The only step name available at the write site is
|
|
57
|
+
* `map:<table>`, which restates `table` — a derived join key, not an observed
|
|
58
|
+
* fact — and the record already hangs off the dataset's own lifecycle entry.
|
|
59
|
+
*/
|
|
60
|
+
export type PlayDatasetBornFrom = {
|
|
61
|
+
table: string;
|
|
62
|
+
rowCountIn: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type PlayRowBornFrom = PlayRowBornFromRow | PlayDatasetBornFrom;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Per-cell cap on the durable read trace, matching the producer trace bound.
|
|
69
|
+
* The read-set is the row's column list, so an unbounded trace would put a wide
|
|
70
|
+
* row's whole schema into `_cell_meta` once per cell. Truncation keeps the
|
|
71
|
+
* first columns in row order, which is stable across runs.
|
|
72
|
+
*/
|
|
73
|
+
export const MAX_CELL_READ_REFS = 12;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Cap on how many cells one row's read order describes. A dataset has a handful
|
|
77
|
+
* of columns; this only bounds pathological graphs, and it reports what it
|
|
78
|
+
* dropped rather than trimming silently.
|
|
79
|
+
*/
|
|
80
|
+
export const MAX_ROW_READ_CELLS = 24;
|
|
81
|
+
|
|
82
|
+
/** Reserved `_cell_meta` key holding row-grain facts rather than a cell. */
|
|
83
|
+
export const ROW_META_CELL_KEY = '_row';
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Row-grain read order (ADR 0019).
|
|
87
|
+
*
|
|
88
|
+
* Every cell in a row reads a prefix of the same list: the row's input columns,
|
|
89
|
+
* then each column this run wrote, in the order they became available. Storing
|
|
90
|
+
* the list once per row and a cut point per cell is the same information as a
|
|
91
|
+
* per-cell array at O(columns) instead of O(columns x cells) — which matters,
|
|
92
|
+
* because this rides the retained-row memory budget on every mapped row.
|
|
93
|
+
*
|
|
94
|
+
* A written column is appended to `columns` before its own cell runs, so its
|
|
95
|
+
* position *is* its cut point: everything before it was available, itself and
|
|
96
|
+
* everything after it was not. `upto` therefore only carries cells the list
|
|
97
|
+
* cannot place — non-persisted fields, and cells past the column cap.
|
|
98
|
+
* `droppedCells` counts cells no cut describes at all, so an incomplete order
|
|
99
|
+
* is visible instead of looking like a cell that read nothing.
|
|
100
|
+
*/
|
|
101
|
+
export interface PlayRowReadOrder {
|
|
102
|
+
columns: string[];
|
|
103
|
+
upto?: Record<string, number>;
|
|
104
|
+
droppedCells?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* How many leading `columns` entries a cell read, or null when the order
|
|
109
|
+
* describes no read for it. Null is not zero: a cell that read nothing and a
|
|
110
|
+
* cell nobody recorded must not render the same.
|
|
111
|
+
*/
|
|
112
|
+
export function cellReadCut(
|
|
113
|
+
order: PlayRowReadOrder | null | undefined,
|
|
114
|
+
column: string,
|
|
115
|
+
): number | null {
|
|
116
|
+
if (!order) return null;
|
|
117
|
+
const explicit = order.upto?.[column];
|
|
118
|
+
if (typeof explicit === 'number' && Number.isFinite(explicit)) {
|
|
119
|
+
return Math.max(0, Math.min(Math.trunc(explicit), order.columns.length));
|
|
120
|
+
}
|
|
121
|
+
const index = order.columns.indexOf(column);
|
|
122
|
+
return index >= 0 ? index : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Row-grain `_cell_meta._row` record. */
|
|
126
|
+
export interface PlayRowMeta {
|
|
127
|
+
reads?: PlayRowReadOrder;
|
|
128
|
+
bornFrom?: PlayRowBornFromRow;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Expand one cell's read-set out of the row order. Returns undefined when the
|
|
133
|
+
* order describes no read for that cell, which is not the same as an empty
|
|
134
|
+
* read-set and must not be rendered as one.
|
|
135
|
+
*/
|
|
136
|
+
export function hydrateCellReadRefs(
|
|
137
|
+
order: PlayRowReadOrder | null | undefined,
|
|
138
|
+
column: string,
|
|
139
|
+
rowKey: string,
|
|
140
|
+
): Array<PlayCellReadRef & { rowKey: string }> | undefined {
|
|
141
|
+
const cut = cellReadCut(order, column);
|
|
142
|
+
if (cut === null || cut <= 0 || !order) return undefined;
|
|
143
|
+
const refs = order.columns
|
|
144
|
+
.slice(0, Math.min(cut, MAX_CELL_READ_REFS))
|
|
145
|
+
.map((readColumn) => ({ rowKey, column: readColumn }));
|
|
146
|
+
return refs.length > 0 ? refs : undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Parse a stored row read order off untrusted transport. */
|
|
150
|
+
export function normalizeRowReadOrder(value: unknown): PlayRowReadOrder | null {
|
|
151
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
152
|
+
const record = value as Record<string, unknown>;
|
|
153
|
+
const columns = Array.isArray(record.columns)
|
|
154
|
+
? record.columns
|
|
155
|
+
.filter((column): column is string => typeof column === 'string')
|
|
156
|
+
.slice(0, MAX_CELL_READ_REFS)
|
|
157
|
+
: [];
|
|
158
|
+
if (columns.length === 0) return null;
|
|
159
|
+
const rawUpto =
|
|
160
|
+
record.upto &&
|
|
161
|
+
typeof record.upto === 'object' &&
|
|
162
|
+
!Array.isArray(record.upto)
|
|
163
|
+
? (record.upto as Record<string, unknown>)
|
|
164
|
+
: {};
|
|
165
|
+
const upto: Record<string, number> = {};
|
|
166
|
+
let entries = 0;
|
|
167
|
+
for (const [column, cut] of Object.entries(rawUpto)) {
|
|
168
|
+
if (entries >= MAX_ROW_READ_CELLS) break;
|
|
169
|
+
if (typeof cut !== 'number' || !Number.isFinite(cut) || cut < 0) continue;
|
|
170
|
+
upto[column] = Math.min(Math.trunc(cut), columns.length);
|
|
171
|
+
entries += 1;
|
|
172
|
+
}
|
|
173
|
+
const droppedCells = finiteNonNegativeInteger(record.droppedCells);
|
|
174
|
+
return {
|
|
175
|
+
columns,
|
|
176
|
+
...(entries > 0 ? { upto } : {}),
|
|
177
|
+
...(droppedCells ? { droppedCells } : {}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Enforce {@link MAX_CELL_READ_REFS} without reordering. */
|
|
182
|
+
export function boundCellReadRefs<T>(refs: readonly T[]): T[] {
|
|
183
|
+
return refs.length > MAX_CELL_READ_REFS
|
|
184
|
+
? refs.slice(0, MAX_CELL_READ_REFS)
|
|
185
|
+
: [...refs];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Fold a newly stated dataset birth into the one already on the snapshot.
|
|
190
|
+
*
|
|
191
|
+
* A paged dataset registers more than once, each registration stating the rows
|
|
192
|
+
* admitted so far, so the larger `rowCountIn` is the later truth. A phase event
|
|
193
|
+
* that states no birth never erases one that was witnessed.
|
|
194
|
+
*/
|
|
195
|
+
export function mergeDatasetBornFrom(
|
|
196
|
+
current: PlayDatasetBornFrom | null | undefined,
|
|
197
|
+
next: PlayDatasetBornFrom | null | undefined,
|
|
198
|
+
): PlayDatasetBornFrom | null {
|
|
199
|
+
if (!next) return current ?? null;
|
|
200
|
+
if (!current) return next;
|
|
201
|
+
return next.rowCountIn > current.rowCountIn ? next : current;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function finiteNonNegativeInteger(value: unknown): number | null {
|
|
205
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return Math.trunc(value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function nonEmptyString(value: unknown): string | null {
|
|
212
|
+
if (typeof value !== 'string') return null;
|
|
213
|
+
const trimmed = value.trim();
|
|
214
|
+
return trimmed ? trimmed : null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Parse a dataset-grain birth record off untrusted transport. Returns null for
|
|
219
|
+
* anything that is not a complete record: a partial birth record would claim a
|
|
220
|
+
* lineage the writer never stated.
|
|
221
|
+
*/
|
|
222
|
+
export function normalizeDatasetBornFrom(
|
|
223
|
+
value: unknown,
|
|
224
|
+
): PlayDatasetBornFrom | null {
|
|
225
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
226
|
+
const record = value as Record<string, unknown>;
|
|
227
|
+
const table = nonEmptyString(record.table);
|
|
228
|
+
const rowCountIn = finiteNonNegativeInteger(record.rowCountIn);
|
|
229
|
+
if (!table || rowCountIn === null) return null;
|
|
230
|
+
return { table, rowCountIn };
|
|
231
|
+
}
|