@superbee/core 0.2.0-pre.6 → 0.2.0-pre.8
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/README.md +89 -0
- package/dist/bundle-ops.d.ts +1 -1
- package/dist/bundle-ops.d.ts.map +1 -1
- package/dist/bundle-ops.js +2 -2
- package/dist/bundle-ops.js.map +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +4 -3
- package/dist/engine.js.map +1 -1
- package/dist/file-journaled-backend-internal.d.ts +19 -0
- package/dist/file-journaled-backend-internal.d.ts.map +1 -0
- package/dist/file-journaled-backend-internal.js +10 -0
- package/dist/file-journaled-backend-internal.js.map +1 -0
- package/dist/file-journaled-backend.d.ts +162 -0
- package/dist/file-journaled-backend.d.ts.map +1 -0
- package/dist/file-journaled-backend.js +1113 -0
- package/dist/file-journaled-backend.js.map +1 -0
- package/dist/filesystem-lock.d.ts +10 -0
- package/dist/filesystem-lock.d.ts.map +1 -1
- package/dist/filesystem-lock.js +133 -8
- package/dist/filesystem-lock.js.map +1 -1
- package/dist/filesystem-push-role.d.ts +77 -0
- package/dist/filesystem-push-role.d.ts.map +1 -0
- package/dist/filesystem-push-role.js +165 -0
- package/dist/filesystem-push-role.js.map +1 -0
- package/dist/governed-body-write.d.ts +8 -1
- package/dist/governed-body-write.d.ts.map +1 -1
- package/dist/governed-body-write.js +9 -2
- package/dist/governed-body-write.js.map +1 -1
- package/dist/host-text.d.ts +31 -0
- package/dist/host-text.d.ts.map +1 -0
- package/dist/host-text.js +77 -0
- package/dist/host-text.js.map +1 -0
- package/dist/hosted-transport/answer-rows.d.ts +216 -0
- package/dist/hosted-transport/answer-rows.d.ts.map +1 -0
- package/dist/hosted-transport/answer-rows.js +396 -0
- package/dist/hosted-transport/answer-rows.js.map +1 -0
- package/dist/hosted-transport/carrier.d.ts +142 -0
- package/dist/hosted-transport/carrier.d.ts.map +1 -0
- package/dist/hosted-transport/carrier.js +241 -0
- package/dist/hosted-transport/carrier.js.map +1 -0
- package/dist/hosted-transport/history-pages.d.ts +55 -0
- package/dist/hosted-transport/history-pages.d.ts.map +1 -0
- package/dist/hosted-transport/history-pages.js +69 -0
- package/dist/hosted-transport/history-pages.js.map +1 -0
- package/dist/hosted-transport/index.d.ts +18 -0
- package/dist/hosted-transport/index.d.ts.map +1 -0
- package/dist/hosted-transport/index.js +18 -0
- package/dist/hosted-transport/index.js.map +1 -0
- package/dist/hosted-transport/operations.d.ts +85 -0
- package/dist/hosted-transport/operations.d.ts.map +1 -0
- package/dist/hosted-transport/operations.js +128 -0
- package/dist/hosted-transport/operations.js.map +1 -0
- package/dist/hosted-transport/paged-reads.d.ts +87 -0
- package/dist/hosted-transport/paged-reads.d.ts.map +1 -0
- package/dist/hosted-transport/paged-reads.js +238 -0
- package/dist/hosted-transport/paged-reads.js.map +1 -0
- package/dist/hosted-transport/read-adapter.d.ts +250 -0
- package/dist/hosted-transport/read-adapter.d.ts.map +1 -0
- package/dist/hosted-transport/read-adapter.js +557 -0
- package/dist/hosted-transport/read-adapter.js.map +1 -0
- package/dist/hosted-transport/root-write.d.ts +114 -0
- package/dist/hosted-transport/root-write.d.ts.map +1 -0
- package/dist/hosted-transport/root-write.js +171 -0
- package/dist/hosted-transport/root-write.js.map +1 -0
- package/dist/hosted-transport/whole-document-transport.d.ts +174 -0
- package/dist/hosted-transport/whole-document-transport.d.ts.map +1 -0
- package/dist/hosted-transport/whole-document-transport.js +411 -0
- package/dist/hosted-transport/whole-document-transport.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-backend.d.ts +6 -1
- package/dist/indexeddb-backend.d.ts.map +1 -1
- package/dist/indexeddb-backend.js +77 -6
- package/dist/indexeddb-backend.js.map +1 -1
- package/dist/journaled-backend.d.ts +73 -5
- package/dist/journaled-backend.d.ts.map +1 -1
- package/dist/journaled-backend.js +55 -1
- package/dist/journaled-backend.js.map +1 -1
- package/dist/kinds.d.ts +25 -1
- package/dist/kinds.d.ts.map +1 -1
- package/dist/kinds.js +52 -4
- package/dist/kinds.js.map +1 -1
- package/dist/links.d.ts +1 -1
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +5 -10
- package/dist/links.js.map +1 -1
- package/dist/platform.d.ts +9 -2
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js.map +1 -1
- package/dist/query-order.d.ts +22 -0
- package/dist/query-order.d.ts.map +1 -0
- package/dist/query-order.js +42 -0
- package/dist/query-order.js.map +1 -0
- package/dist/remote-backend.d.ts +56 -11
- package/dist/remote-backend.d.ts.map +1 -1
- package/dist/remote-backend.js +154 -27
- package/dist/remote-backend.js.map +1 -1
- package/dist/remote-error.d.ts +17 -1
- package/dist/remote-error.d.ts.map +1 -1
- package/dist/remote-error.js +32 -2
- package/dist/remote-error.js.map +1 -1
- package/dist/remote-operations.d.ts +5 -4
- package/dist/remote-operations.d.ts.map +1 -1
- package/dist/remote-operations.js +16 -6
- package/dist/remote-operations.js.map +1 -1
- package/dist/remote-parsers.d.ts +1 -1
- package/dist/remote-parsers.js +1 -1
- package/dist/types.d.ts +9 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/uncertain-write.d.ts +45 -11
- package/dist/uncertain-write.d.ts.map +1 -1
- package/dist/uncertain-write.js +30 -9
- package/dist/uncertain-write.js.map +1 -1
- package/package.json +17 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted operations by id: the host lists the reads it runs for one bundle
|
|
3
|
+
* (`POST <sync prefix>/operations {bundleId}`) and runs one of them
|
|
4
|
+
* (`POST <sync prefix>/run {bundleId, operationId, input}`), answering the kernel's operation
|
|
5
|
+
* result. A client reaches a read the host adds with no change of its own.
|
|
6
|
+
*
|
|
7
|
+
* Everything the host says here is untrusted data. A descriptor's title and description go
|
|
8
|
+
* through {@link stripHostText}; its JSON Schemas are carried for rendering only, never compiled
|
|
9
|
+
* and never followed (`$ref`, `$id`); a result is data, never a path, URL or command. A listing
|
|
10
|
+
* is bounded: at most {@link HOSTED_READ_BOUNDS.operations} descriptors, and a descriptor that is
|
|
11
|
+
* malformed or out of bounds is dropped with a note while the rest still list.
|
|
12
|
+
*/
|
|
13
|
+
import { type HostedOperationRefusal } from "./read-adapter.js";
|
|
14
|
+
/** True for an id in the operation grammar (`documents.history.v1`), at most 128 characters. */
|
|
15
|
+
export declare function isOperationId(value: unknown): value is string;
|
|
16
|
+
/** A JSON object, as parsed from an answer. */
|
|
17
|
+
export type JsonObject = {
|
|
18
|
+
readonly [key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
/** One operation the host runs by id, as its listing describes it. */
|
|
21
|
+
export interface HostedOperation {
|
|
22
|
+
readonly operationId: string;
|
|
23
|
+
/** Host text, stripped of control and format characters, at most {@link HOSTED_READ_BOUNDS.operationTitleChars}. */
|
|
24
|
+
readonly title: string;
|
|
25
|
+
/** Host text, stripped of control and format characters, at most {@link HOSTED_READ_BOUNDS.operationDescriptionChars}. */
|
|
26
|
+
readonly description: string;
|
|
27
|
+
/** The most bytes the host answers a run of this operation with. */
|
|
28
|
+
readonly maximumOutputBytes: number;
|
|
29
|
+
/** For rendering only: never compiled, never followed. */
|
|
30
|
+
readonly inputJsonSchema: JsonObject;
|
|
31
|
+
/** For rendering only: never compiled, never followed. */
|
|
32
|
+
readonly resultJsonSchema: JsonObject;
|
|
33
|
+
/** The boolean hints the host states (`readOnlyHint`, `destructiveHint`, ...); any other value is left out. */
|
|
34
|
+
readonly annotations: Readonly<Record<string, boolean>>;
|
|
35
|
+
}
|
|
36
|
+
/** A decoded listing: the operations admitted, in the host's order, and a note for each one dropped. */
|
|
37
|
+
export interface HostedOperationListing {
|
|
38
|
+
readonly operations: readonly HostedOperation[];
|
|
39
|
+
readonly notes: readonly string[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The listing route's `200` answer (`{ operations: [descriptor, ...] }`). A body that is not an
|
|
43
|
+
* object with an `operations` array is malformed, naming `route`; fields the answer may gain are
|
|
44
|
+
* ignored. Each descriptor is admitted on its own: one that is malformed, whose id is outside the
|
|
45
|
+
* operation grammar or repeats an earlier one, whose `maximumOutputBytes` is not positive, or
|
|
46
|
+
* whose schema nests deeper than {@link HOSTED_READ_BOUNDS.operationSchemaDepth} is dropped with a note. At most
|
|
47
|
+
* {@link HOSTED_READ_BOUNDS.operations} are read.
|
|
48
|
+
*/
|
|
49
|
+
export declare function decodeOperationListing(body: unknown, route?: string): HostedOperationListing;
|
|
50
|
+
/** True when an operation input names a bundle other than `bundleId` (an input without one names none). */
|
|
51
|
+
export declare function namesOtherBundle(input: JsonObject, bundleId: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The body a run sends: the bundle the request selects its tenant by, the operation, and the
|
|
54
|
+
* operation's input with its `bundleId` set to that same bare bundle id (the host refuses an
|
|
55
|
+
* input naming another bundle). An input that already names another bundle is refused here.
|
|
56
|
+
*/
|
|
57
|
+
export declare function operationRunBody(bundleId: string, operationId: string, input: JsonObject): {
|
|
58
|
+
bundleId: string;
|
|
59
|
+
operationId: string;
|
|
60
|
+
input: Record<string, unknown>;
|
|
61
|
+
};
|
|
62
|
+
/** A listing, or the refusal of the bundle it was asked for. */
|
|
63
|
+
export type HostedOperationListingAnswer = {
|
|
64
|
+
readonly ok: true;
|
|
65
|
+
readonly listing: HostedOperationListing;
|
|
66
|
+
} | {
|
|
67
|
+
readonly ok: false;
|
|
68
|
+
readonly refusal: HostedOperationRefusal;
|
|
69
|
+
};
|
|
70
|
+
/** A run's `200` answer: the operation's data, or its refusal (the kernel's code, message and retry advice). */
|
|
71
|
+
export type HostedOperationRun = {
|
|
72
|
+
readonly ok: true;
|
|
73
|
+
readonly data: unknown;
|
|
74
|
+
} | {
|
|
75
|
+
readonly ok: false;
|
|
76
|
+
readonly refusal: HostedOperationRefusal;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* A run's `200` answer, the kernel's operation result: `{ ok: true, operationId, data }` for the
|
|
80
|
+
* operation asked for, or its refusal through {@link operationRefusal}. Anything else, including
|
|
81
|
+
* a result for another operation, or data nested deeper than `HOSTED_READ_BOUNDS.runDepth`
|
|
82
|
+
* levels (which no renderer could walk), is malformed, naming `route`. The data is not interpreted.
|
|
83
|
+
*/
|
|
84
|
+
export declare function decodeOperationRun(operationId: string, body: unknown, route?: string): HostedOperationRun;
|
|
85
|
+
//# sourceMappingURL=operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/hosted-transport/operations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAwC,KAAK,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAOtG,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAE7D;AAED,+CAA+C;AAC/C,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE7D,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oHAAoH;IACpH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,0HAA0H;IAC1H,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,0DAA0D;IAC1D,QAAQ,CAAC,eAAe,EAAE,UAAU,CAAC;IACrC,0DAA0D;IAC1D,QAAQ,CAAC,gBAAgB,EAAE,UAAU,CAAC;IACtC,+GAA+G;IAC/G,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACzD;AAED,wGAAwG;AACxG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAmCD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,sBAAsB,CAmB5F;AAED,2GAA2G;AAC3G,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAKpK;AAED,gEAAgE;AAChE,MAAM,MAAM,4BAA4B,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAA;CAAE,CAAC;AAE9K,gHAAgH;AAChH,MAAM,MAAM,kBAAkB,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,sBAAsB,CAAA;CAAE,CAAC;AAElJ;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAOzG"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted operations by id: the host lists the reads it runs for one bundle
|
|
3
|
+
* (`POST <sync prefix>/operations {bundleId}`) and runs one of them
|
|
4
|
+
* (`POST <sync prefix>/run {bundleId, operationId, input}`), answering the kernel's operation
|
|
5
|
+
* result. A client reaches a read the host adds with no change of its own.
|
|
6
|
+
*
|
|
7
|
+
* Everything the host says here is untrusted data. A descriptor's title and description go
|
|
8
|
+
* through {@link stripHostText}; its JSON Schemas are carried for rendering only, never compiled
|
|
9
|
+
* and never followed (`$ref`, `$id`); a result is data, never a path, URL or command. A listing
|
|
10
|
+
* is bounded: at most {@link HOSTED_READ_BOUNDS.operations} descriptors, and a descriptor that is
|
|
11
|
+
* malformed or out of bounds is dropped with a note while the rest still list.
|
|
12
|
+
*/
|
|
13
|
+
import { stripHostText } from "../host-text.js";
|
|
14
|
+
import { malformed } from "../remote-error.js";
|
|
15
|
+
import { operationRefusal, HOSTED_READ_BOUNDS } from "./read-adapter.js";
|
|
16
|
+
/** The longest operation id admitted. */
|
|
17
|
+
const OPERATION_ID_MAX = 128;
|
|
18
|
+
const OPERATION_ID = /^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)*\.v[1-9][0-9]*$/;
|
|
19
|
+
/** True for an id in the operation grammar (`documents.history.v1`), at most 128 characters. */
|
|
20
|
+
export function isOperationId(value) {
|
|
21
|
+
return typeof value === "string" && value.length <= OPERATION_ID_MAX && OPERATION_ID.test(value);
|
|
22
|
+
}
|
|
23
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
/** Whether `value` nests objects and arrays deeper than `limit` levels (the value itself is level 1). */
|
|
25
|
+
function deeperThan(value, limit, level = 1) {
|
|
26
|
+
if (typeof value !== "object" || value === null)
|
|
27
|
+
return false;
|
|
28
|
+
if (level > limit)
|
|
29
|
+
return true;
|
|
30
|
+
const children = Array.isArray(value) ? value : Object.values(value);
|
|
31
|
+
return children.some((child) => deeperThan(child, limit, level + 1));
|
|
32
|
+
}
|
|
33
|
+
/** One descriptor, admitted, or the reason it is dropped. */
|
|
34
|
+
function admitDescriptor(raw) {
|
|
35
|
+
if (!isRecord(raw))
|
|
36
|
+
return "not an object";
|
|
37
|
+
const { operationId, title, description, maximumOutputBytes, inputJsonSchema, resultJsonSchema, annotations } = raw;
|
|
38
|
+
if (!isOperationId(operationId))
|
|
39
|
+
return "its id is not an operation id";
|
|
40
|
+
if (typeof title !== "string" || typeof description !== "string")
|
|
41
|
+
return "it has no title or description";
|
|
42
|
+
if (typeof maximumOutputBytes !== "number" || !Number.isSafeInteger(maximumOutputBytes) || maximumOutputBytes <= 0)
|
|
43
|
+
return "its maximumOutputBytes is not a positive integer";
|
|
44
|
+
if (!isRecord(inputJsonSchema) || !isRecord(resultJsonSchema))
|
|
45
|
+
return "its schemas are not objects";
|
|
46
|
+
if (deeperThan(inputJsonSchema, HOSTED_READ_BOUNDS.operationSchemaDepth) || deeperThan(resultJsonSchema, HOSTED_READ_BOUNDS.operationSchemaDepth))
|
|
47
|
+
return `a schema is nested deeper than ${HOSTED_READ_BOUNDS.operationSchemaDepth} levels`;
|
|
48
|
+
if (annotations !== undefined && !isRecord(annotations))
|
|
49
|
+
return "its annotations are not an object";
|
|
50
|
+
const hints = {};
|
|
51
|
+
for (const [key, value] of Object.entries(annotations ?? {}))
|
|
52
|
+
if (typeof value === "boolean")
|
|
53
|
+
hints[key] = value;
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
operationId,
|
|
56
|
+
title: stripHostText(title, HOSTED_READ_BOUNDS.operationTitleChars),
|
|
57
|
+
description: stripHostText(description, HOSTED_READ_BOUNDS.operationDescriptionChars),
|
|
58
|
+
maximumOutputBytes,
|
|
59
|
+
inputJsonSchema,
|
|
60
|
+
resultJsonSchema,
|
|
61
|
+
annotations: Object.freeze(hints),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The listing route's `200` answer (`{ operations: [descriptor, ...] }`). A body that is not an
|
|
66
|
+
* object with an `operations` array is malformed, naming `route`; fields the answer may gain are
|
|
67
|
+
* ignored. Each descriptor is admitted on its own: one that is malformed, whose id is outside the
|
|
68
|
+
* operation grammar or repeats an earlier one, whose `maximumOutputBytes` is not positive, or
|
|
69
|
+
* whose schema nests deeper than {@link HOSTED_READ_BOUNDS.operationSchemaDepth} is dropped with a note. At most
|
|
70
|
+
* {@link HOSTED_READ_BOUNDS.operations} are read.
|
|
71
|
+
*/
|
|
72
|
+
export function decodeOperationListing(body, route) {
|
|
73
|
+
if (!isRecord(body) || !Array.isArray(body.operations))
|
|
74
|
+
throw malformed("operations answered a body that is not an operation listing", route);
|
|
75
|
+
const rows = body.operations;
|
|
76
|
+
const operations = [];
|
|
77
|
+
const notes = [];
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
rows.slice(0, HOSTED_READ_BOUNDS.operations).forEach((raw, index) => {
|
|
80
|
+
const admitted = admitDescriptor(raw);
|
|
81
|
+
// Only an id that passed the grammar is named: anything else the host sent stays out of the note.
|
|
82
|
+
const named = isRecord(raw) && isOperationId(raw.operationId) ? ` (${raw.operationId})` : "";
|
|
83
|
+
if (typeof admitted === "string")
|
|
84
|
+
notes.push(`dropped the host's operation #${index + 1}${named}: ${admitted}`);
|
|
85
|
+
else if (seen.has(admitted.operationId))
|
|
86
|
+
notes.push(`dropped the host's operation #${index + 1}${named}: its id repeats an earlier one`);
|
|
87
|
+
else {
|
|
88
|
+
seen.add(admitted.operationId);
|
|
89
|
+
operations.push(admitted);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
if (rows.length > HOSTED_READ_BOUNDS.operations)
|
|
93
|
+
notes.push(`dropped ${rows.length - HOSTED_READ_BOUNDS.operations} operations past the first ${HOSTED_READ_BOUNDS.operations} the host listed`);
|
|
94
|
+
return Object.freeze({ operations: Object.freeze(operations), notes: Object.freeze(notes) });
|
|
95
|
+
}
|
|
96
|
+
/** True when an operation input names a bundle other than `bundleId` (an input without one names none). */
|
|
97
|
+
export function namesOtherBundle(input, bundleId) {
|
|
98
|
+
return input.bundleId !== undefined && input.bundleId !== bundleId;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The body a run sends: the bundle the request selects its tenant by, the operation, and the
|
|
102
|
+
* operation's input with its `bundleId` set to that same bare bundle id (the host refuses an
|
|
103
|
+
* input naming another bundle). An input that already names another bundle is refused here.
|
|
104
|
+
*/
|
|
105
|
+
export function operationRunBody(bundleId, operationId, input) {
|
|
106
|
+
if (namesOtherBundle(input, bundleId))
|
|
107
|
+
throw new RangeError("the operation input names another bundle than the one the run selects");
|
|
108
|
+
const rest = { ...input };
|
|
109
|
+
delete rest.bundleId;
|
|
110
|
+
return { bundleId, operationId, input: { bundleId, ...rest } };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A run's `200` answer, the kernel's operation result: `{ ok: true, operationId, data }` for the
|
|
114
|
+
* operation asked for, or its refusal through {@link operationRefusal}. Anything else, including
|
|
115
|
+
* a result for another operation, or data nested deeper than `HOSTED_READ_BOUNDS.runDepth`
|
|
116
|
+
* levels (which no renderer could walk), is malformed, naming `route`. The data is not interpreted.
|
|
117
|
+
*/
|
|
118
|
+
export function decodeOperationRun(operationId, body, route) {
|
|
119
|
+
const refusal = operationRefusal(body, operationId, route);
|
|
120
|
+
if (refusal)
|
|
121
|
+
return Object.freeze({ ok: false, refusal });
|
|
122
|
+
if (!isRecord(body) || body.ok !== true || body.operationId !== operationId || !("data" in body) || body.data === undefined)
|
|
123
|
+
throw malformed(`run answered an envelope that is not ${operationId}'s result`, route);
|
|
124
|
+
if (deeperThan(body.data, HOSTED_READ_BOUNDS.runDepth))
|
|
125
|
+
throw malformed(`run answered data nested deeper than ${HOSTED_READ_BOUNDS.runDepth} levels`, route);
|
|
126
|
+
return Object.freeze({ ok: true, data: body.data });
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/hosted-transport/operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Following the host's page cursor for heads and snapshot. The host serves both a page at a
|
|
3
|
+
* time: a request without `cursor` is the first page, and each page that is not the last names
|
|
4
|
+
* the `next` cursor. Every page is served under the one bundle revision the first page pinned
|
|
5
|
+
* and states the whole listing's `count` and `digest`; a write between pages makes the next page
|
|
6
|
+
* refuse with `409 concurrent_change` rather than mix two states, and the reader starts again.
|
|
7
|
+
*
|
|
8
|
+
* A bundle that fits in one page gets the answer it got before paging, so these helpers only
|
|
9
|
+
* stitch pages together: the assembled heads go through {@link parseHeadsAnswer} and the
|
|
10
|
+
* stitched snapshot body through {@link readSnapshotStream}, which keep deciding what a working
|
|
11
|
+
* copy may trust (the count, the terminator, the digest).
|
|
12
|
+
*/
|
|
13
|
+
import { type DocumentHead } from "../heads-digest.js";
|
|
14
|
+
/** The refusal a page answers when the bundle moved since the first page pinned it. */
|
|
15
|
+
export declare const PAGE_RESTART_CODE = "concurrent_change";
|
|
16
|
+
/** How many times a heads listing starts again from the first page before the refusal stands. */
|
|
17
|
+
export declare const HEADS_PAGE_ATTEMPTS = 3;
|
|
18
|
+
/**
|
|
19
|
+
* The pause before each restart, by restart: about a quarter second, then about a second. A burst
|
|
20
|
+
* of writes (an organizer apply, an agent's batch) usually settles within that, where restarts
|
|
21
|
+
* back to back would spend every attempt inside the same burst.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PAGE_RESTART_DELAYS_MS: readonly number[];
|
|
24
|
+
/** The pause before restart `restart` (1-based): half the base plus up to half again, at random. */
|
|
25
|
+
export declare function pageRestartDelay(restart: number, random?: () => number): number;
|
|
26
|
+
/** Waits `ms`, or rejects with the signal's reason once it aborts. */
|
|
27
|
+
export declare function pause(ms: number, signal: AbortSignal): Promise<void>;
|
|
28
|
+
/** The longest cursor a reader passes back; the host's are far shorter. */
|
|
29
|
+
export declare const MAXIMUM_CURSOR_LENGTH = 4096;
|
|
30
|
+
/** True when `error` is the host's refusal of a page because the bundle moved since the first. */
|
|
31
|
+
export declare function isPageRestart(error: unknown): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* True when a snapshot ended as truncation because a later page was refused as a restart: the
|
|
34
|
+
* documents changed while the pages streamed. The snapshot cannot start again by itself, since
|
|
35
|
+
* its header and first documents already reached the consumer; the caller that consumes it
|
|
36
|
+
* (a bootstrap or a checkout) runs again, after {@link pageRestartDelay}, a bounded number of
|
|
37
|
+
* times ({@link SNAPSHOT_RESTART_ATTEMPTS}).
|
|
38
|
+
*/
|
|
39
|
+
export declare function isSnapshotRestart(error: unknown): boolean;
|
|
40
|
+
/** How many times a snapshot consumer runs before a restart truncation stands. */
|
|
41
|
+
export declare const SNAPSHOT_RESTART_ATTEMPTS = 3;
|
|
42
|
+
/**
|
|
43
|
+
* Runs `consume` (a bootstrap or a checkout over one snapshot) again when it fails with a
|
|
44
|
+
* snapshot restart, pausing before each run, at most `attempts` times in all. Any other failure,
|
|
45
|
+
* and the last restart, stand.
|
|
46
|
+
*/
|
|
47
|
+
export declare function retrySnapshotRestarts<T>(consume: () => Promise<T>, options: {
|
|
48
|
+
signal: AbortSignal;
|
|
49
|
+
attempts?: number;
|
|
50
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
51
|
+
}): Promise<T>;
|
|
52
|
+
/** One page of a paged heads answer, shape-checked; the whole listing is checked once assembled. */
|
|
53
|
+
export interface HeadsPage {
|
|
54
|
+
readonly count: number;
|
|
55
|
+
readonly digest: string;
|
|
56
|
+
readonly heads: readonly DocumentHead[];
|
|
57
|
+
readonly next?: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function decodeHeadsPage(value: unknown): HeadsPage;
|
|
60
|
+
/**
|
|
61
|
+
* Collects paged heads into the one listing they page. Every page must state the first page's
|
|
62
|
+
* count and digest, rows must strictly ascend by id across pages (so no row repeats), a page that
|
|
63
|
+
* names a next cursor must carry rows, and the rows may never outnumber the count. The caller
|
|
64
|
+
* hands the assembled `{ count, digest, heads }` to the shared heads parser.
|
|
65
|
+
*/
|
|
66
|
+
export declare class HeadsPages {
|
|
67
|
+
#private;
|
|
68
|
+
/** Adds a page; answers the cursor of the next page, or undefined once the listing is whole. */
|
|
69
|
+
add(page: HeadsPage): string | undefined;
|
|
70
|
+
/** The assembled answer, in the unpaged answer's shape. */
|
|
71
|
+
whole(): {
|
|
72
|
+
count: number;
|
|
73
|
+
digest: string;
|
|
74
|
+
heads: DocumentHead[];
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* One snapshot body stitched from its pages, in the unpaged grammar: the first page's header,
|
|
79
|
+
* every page's document lines in order, and the last page's `end` terminator. A later page's
|
|
80
|
+
* header must state the first page's count and digest and is dropped; a `page` terminator must
|
|
81
|
+
* count the document lines its page carried and names the cursor `open` fetches next. Anything
|
|
82
|
+
* else passes through verbatim for the snapshot parser to judge, and a page that fails to open
|
|
83
|
+
* or ends early ends the stitched body without its terminator, which the parser reports as
|
|
84
|
+
* truncation. Cancelling the stitched body cancels the page being read.
|
|
85
|
+
*/
|
|
86
|
+
export declare function stitchSnapshotPages(first: ReadableStream<Uint8Array>, open: (cursor: string) => Promise<ReadableStream<Uint8Array>>): ReadableStream<Uint8Array>;
|
|
87
|
+
//# sourceMappingURL=paged-reads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paged-reads.d.ts","sourceRoot":"","sources":["../../src/hosted-transport/paged-reads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAItE,uFAAuF;AACvF,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AACrD,iGAAiG;AACjG,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAA+B,CAAC;AAEpF,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,MAAoB,GAAG,MAAM,CAG5F;AAED,sEAAsE;AACtE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAapE;AACD,2EAA2E;AAC3E,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAG1C,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD;AAED,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,OAAO,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAC9G,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,oGAAoG;AACpG,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAczD;AAED;;;;;GAKG;AACH,qBAAa,UAAU;;IAIrB,gGAAgG;IAChG,GAAG,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAaxC,2DAA2D;IAC3D,KAAK,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE;CAIlE;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,CAAC,UAAU,CAAC,EACjC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,GAC5D,cAAc,CAAC,UAAU,CAAC,CAkD5B"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Following the host's page cursor for heads and snapshot. The host serves both a page at a
|
|
3
|
+
* time: a request without `cursor` is the first page, and each page that is not the last names
|
|
4
|
+
* the `next` cursor. Every page is served under the one bundle revision the first page pinned
|
|
5
|
+
* and states the whole listing's `count` and `digest`; a write between pages makes the next page
|
|
6
|
+
* refuse with `409 concurrent_change` rather than mix two states, and the reader starts again.
|
|
7
|
+
*
|
|
8
|
+
* A bundle that fits in one page gets the answer it got before paging, so these helpers only
|
|
9
|
+
* stitch pages together: the assembled heads go through {@link parseHeadsAnswer} and the
|
|
10
|
+
* stitched snapshot body through {@link readSnapshotStream}, which keep deciding what a working
|
|
11
|
+
* copy may trust (the count, the terminator, the digest).
|
|
12
|
+
*/
|
|
13
|
+
import { isHeadsDigest } from "../heads-digest.js";
|
|
14
|
+
import { malformed, RemoteError } from "../remote-error.js";
|
|
15
|
+
import { SNAPSHOT_TRUNCATED } from "../remote-parsers.js";
|
|
16
|
+
/** The refusal a page answers when the bundle moved since the first page pinned it. */
|
|
17
|
+
export const PAGE_RESTART_CODE = "concurrent_change";
|
|
18
|
+
/** How many times a heads listing starts again from the first page before the refusal stands. */
|
|
19
|
+
export const HEADS_PAGE_ATTEMPTS = 3;
|
|
20
|
+
/**
|
|
21
|
+
* The pause before each restart, by restart: about a quarter second, then about a second. A burst
|
|
22
|
+
* of writes (an organizer apply, an agent's batch) usually settles within that, where restarts
|
|
23
|
+
* back to back would spend every attempt inside the same burst.
|
|
24
|
+
*/
|
|
25
|
+
export const PAGE_RESTART_DELAYS_MS = Object.freeze([250, 1000]);
|
|
26
|
+
/** The pause before restart `restart` (1-based): half the base plus up to half again, at random. */
|
|
27
|
+
export function pageRestartDelay(restart, random = Math.random) {
|
|
28
|
+
const base = PAGE_RESTART_DELAYS_MS[Math.min(restart, PAGE_RESTART_DELAYS_MS.length) - 1];
|
|
29
|
+
return Math.round(base / 2 + (random() * base) / 2);
|
|
30
|
+
}
|
|
31
|
+
/** Waits `ms`, or rejects with the signal's reason once it aborts. */
|
|
32
|
+
export function pause(ms, signal) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
if (signal.aborted)
|
|
35
|
+
return reject(signal.reason);
|
|
36
|
+
const timer = setTimeout(() => {
|
|
37
|
+
signal.removeEventListener("abort", aborted);
|
|
38
|
+
resolve();
|
|
39
|
+
}, ms);
|
|
40
|
+
const aborted = () => {
|
|
41
|
+
clearTimeout(timer);
|
|
42
|
+
reject(signal.reason);
|
|
43
|
+
};
|
|
44
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** The longest cursor a reader passes back; the host's are far shorter. */
|
|
48
|
+
export const MAXIMUM_CURSOR_LENGTH = 4096;
|
|
49
|
+
/** True when `error` is the host's refusal of a page because the bundle moved since the first. */
|
|
50
|
+
export function isPageRestart(error) {
|
|
51
|
+
return error instanceof RemoteError && error.status === 409 && error.code === PAGE_RESTART_CODE;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* True when a snapshot ended as truncation because a later page was refused as a restart: the
|
|
55
|
+
* documents changed while the pages streamed. The snapshot cannot start again by itself, since
|
|
56
|
+
* its header and first documents already reached the consumer; the caller that consumes it
|
|
57
|
+
* (a bootstrap or a checkout) runs again, after {@link pageRestartDelay}, a bounded number of
|
|
58
|
+
* times ({@link SNAPSHOT_RESTART_ATTEMPTS}).
|
|
59
|
+
*/
|
|
60
|
+
export function isSnapshotRestart(error) {
|
|
61
|
+
return error instanceof RemoteError && error.code === SNAPSHOT_TRUNCATED && isPageRestart(error.cause);
|
|
62
|
+
}
|
|
63
|
+
/** How many times a snapshot consumer runs before a restart truncation stands. */
|
|
64
|
+
export const SNAPSHOT_RESTART_ATTEMPTS = 3;
|
|
65
|
+
/**
|
|
66
|
+
* Runs `consume` (a bootstrap or a checkout over one snapshot) again when it fails with a
|
|
67
|
+
* snapshot restart, pausing before each run, at most `attempts` times in all. Any other failure,
|
|
68
|
+
* and the last restart, stand.
|
|
69
|
+
*/
|
|
70
|
+
export async function retrySnapshotRestarts(consume, options) {
|
|
71
|
+
const attempts = options.attempts ?? SNAPSHOT_RESTART_ATTEMPTS;
|
|
72
|
+
for (let attempt = 1;; attempt += 1) {
|
|
73
|
+
try {
|
|
74
|
+
return await consume();
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (attempt >= attempts || !isSnapshotRestart(error))
|
|
78
|
+
throw error;
|
|
79
|
+
await (options.sleep ?? pause)(pageRestartDelay(attempt), options.signal);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isCursor(value) {
|
|
84
|
+
return typeof value === "string" && value.length > 0 && value.length <= MAXIMUM_CURSOR_LENGTH;
|
|
85
|
+
}
|
|
86
|
+
export function decodeHeadsPage(value) {
|
|
87
|
+
const body = value;
|
|
88
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
89
|
+
throw malformed("heads page is not a JSON object");
|
|
90
|
+
if (typeof body.count !== "number" || !Number.isSafeInteger(body.count) || body.count < 0)
|
|
91
|
+
throw malformed("heads page carries no listing count");
|
|
92
|
+
if (!isHeadsDigest(body.digest))
|
|
93
|
+
throw malformed("heads page carries no well-formed digest");
|
|
94
|
+
if (!Array.isArray(body.heads))
|
|
95
|
+
throw malformed("heads page carries no heads array");
|
|
96
|
+
const heads = body.heads.map((row) => {
|
|
97
|
+
const head = row;
|
|
98
|
+
if (typeof head !== "object" || head === null || typeof head.id !== "string" || typeof head.version !== "string")
|
|
99
|
+
throw malformed("heads page carries a row that is not { id, version }");
|
|
100
|
+
return { id: head.id, version: head.version };
|
|
101
|
+
});
|
|
102
|
+
if (body.next !== undefined && !isCursor(body.next))
|
|
103
|
+
throw malformed("heads page carries a malformed next cursor");
|
|
104
|
+
return { count: body.count, digest: body.digest, heads, ...(body.next === undefined ? {} : { next: body.next }) };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Collects paged heads into the one listing they page. Every page must state the first page's
|
|
108
|
+
* count and digest, rows must strictly ascend by id across pages (so no row repeats), a page that
|
|
109
|
+
* names a next cursor must carry rows, and the rows may never outnumber the count. The caller
|
|
110
|
+
* hands the assembled `{ count, digest, heads }` to the shared heads parser.
|
|
111
|
+
*/
|
|
112
|
+
export class HeadsPages {
|
|
113
|
+
#first;
|
|
114
|
+
#rows = [];
|
|
115
|
+
/** Adds a page; answers the cursor of the next page, or undefined once the listing is whole. */
|
|
116
|
+
add(page) {
|
|
117
|
+
const first = (this.#first ??= page);
|
|
118
|
+
if (page.count !== first.count || page.digest !== first.digest)
|
|
119
|
+
throw malformed("heads pages disagree on the listing they page");
|
|
120
|
+
for (const head of page.heads) {
|
|
121
|
+
const last = this.#rows.at(-1);
|
|
122
|
+
if (last !== undefined && !(last.id < head.id))
|
|
123
|
+
throw malformed("heads pages repeat or reorder a row");
|
|
124
|
+
this.#rows.push(head);
|
|
125
|
+
}
|
|
126
|
+
if (this.#rows.length > first.count)
|
|
127
|
+
throw malformed(`heads pages carry more than the ${first.count} row(s) they announce`);
|
|
128
|
+
if (page.next !== undefined && page.heads.length === 0)
|
|
129
|
+
throw malformed("a heads page names a next page but carries no rows");
|
|
130
|
+
return page.next;
|
|
131
|
+
}
|
|
132
|
+
/** The assembled answer, in the unpaged answer's shape. */
|
|
133
|
+
whole() {
|
|
134
|
+
if (!this.#first)
|
|
135
|
+
throw malformed("no heads page arrived");
|
|
136
|
+
return { count: this.#first.count, digest: this.#first.digest, heads: [...this.#rows] };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Complete lines of one NDJSON body as they arrive; a partial trailing line is not yielded. */
|
|
140
|
+
async function* bodyLines(body) {
|
|
141
|
+
const reader = body.getReader();
|
|
142
|
+
const decoder = new TextDecoder();
|
|
143
|
+
let buffered = "";
|
|
144
|
+
try {
|
|
145
|
+
for (;;) {
|
|
146
|
+
const chunk = await reader.read();
|
|
147
|
+
buffered += decoder.decode(chunk.value, { stream: !chunk.done });
|
|
148
|
+
let start = 0;
|
|
149
|
+
for (let newline = buffered.indexOf("\n"); newline !== -1; newline = buffered.indexOf("\n", start)) {
|
|
150
|
+
yield buffered.slice(start, newline);
|
|
151
|
+
start = newline + 1;
|
|
152
|
+
}
|
|
153
|
+
buffered = buffered.slice(start);
|
|
154
|
+
if (chunk.done)
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
await reader.cancel().catch(() => undefined);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function lineRecord(line) {
|
|
163
|
+
try {
|
|
164
|
+
const record = JSON.parse(line);
|
|
165
|
+
return typeof record === "object" && record !== null && !Array.isArray(record) ? record : undefined;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** The prefix every document line the host writes begins with; any other line is parsed here. */
|
|
172
|
+
const DOC_LINE = '{"kind":"doc",';
|
|
173
|
+
/**
|
|
174
|
+
* One snapshot body stitched from its pages, in the unpaged grammar: the first page's header,
|
|
175
|
+
* every page's document lines in order, and the last page's `end` terminator. A later page's
|
|
176
|
+
* header must state the first page's count and digest and is dropped; a `page` terminator must
|
|
177
|
+
* count the document lines its page carried and names the cursor `open` fetches next. Anything
|
|
178
|
+
* else passes through verbatim for the snapshot parser to judge, and a page that fails to open
|
|
179
|
+
* or ends early ends the stitched body without its terminator, which the parser reports as
|
|
180
|
+
* truncation. Cancelling the stitched body cancels the page being read.
|
|
181
|
+
*/
|
|
182
|
+
export function stitchSnapshotPages(first, open) {
|
|
183
|
+
async function* lines() {
|
|
184
|
+
let body = first;
|
|
185
|
+
let header;
|
|
186
|
+
for (;;) {
|
|
187
|
+
let index = 0;
|
|
188
|
+
let documents = 0;
|
|
189
|
+
let next;
|
|
190
|
+
for await (const line of bodyLines(body)) {
|
|
191
|
+
if (index++ === 0 && header !== undefined) {
|
|
192
|
+
const record = lineRecord(line);
|
|
193
|
+
if (record?.kind !== "snapshot" || record.count !== header.count || record.digest !== header.digest)
|
|
194
|
+
throw malformed("snapshot pages disagree on the header");
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (line.startsWith(DOC_LINE)) {
|
|
198
|
+
documents += 1;
|
|
199
|
+
yield `${line}\n`;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const record = lineRecord(line);
|
|
203
|
+
if (header === undefined && record?.kind === "snapshot")
|
|
204
|
+
header = { count: record.count, digest: record.digest };
|
|
205
|
+
if (record?.kind === "page") {
|
|
206
|
+
if (record.count !== documents || documents === 0 || !isCursor(record.next))
|
|
207
|
+
throw malformed("snapshot page terminator disagrees with its page");
|
|
208
|
+
next = record.next;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
yield `${line}\n`;
|
|
212
|
+
}
|
|
213
|
+
if (next === undefined)
|
|
214
|
+
return;
|
|
215
|
+
body = await open(next);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const produced = lines();
|
|
219
|
+
const encoder = new TextEncoder();
|
|
220
|
+
return new ReadableStream({
|
|
221
|
+
async pull(controller) {
|
|
222
|
+
try {
|
|
223
|
+
const step = await produced.next();
|
|
224
|
+
if (step.done)
|
|
225
|
+
controller.close();
|
|
226
|
+
else
|
|
227
|
+
controller.enqueue(encoder.encode(step.value));
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
controller.error(error);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
async cancel() {
|
|
234
|
+
await produced.return(undefined);
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=paged-reads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paged-reads.js","sourceRoot":"","sources":["../../src/hosted-transport/paged-reads.ts"],"names":[],"mappings":"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"}
|