@markless/serializer 0.1.0
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/LICENSE +21 -0
- package/README.md +6 -0
- package/dist/decode-client.d.ts +10 -0
- package/dist/decode-client.js +64 -0
- package/dist/decode.d.ts +2 -0
- package/dist/decode.js +60 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +402 -0
- package/dist/protocol-validation.d.ts +55 -0
- package/dist/protocol-validation.js +666 -0
- package/dist/protocol.d.ts +251 -0
- package/dist/protocol.js +4 -0
- package/dist/value-CcROymhu.d.ts +102 -0
- package/dist/value-decode-extensions-BhNCyKre.js +33 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jack Shelton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { a as SerializedRecord, o as SerializedSlot, r as SerializedGraphPayload } from "./value-CcROymhu.js";
|
|
2
|
+
|
|
3
|
+
//#region src/value-decode-client.d.ts
|
|
4
|
+
type ValueDecodeExtensions = {
|
|
5
|
+
readonly recordShell: (record: SerializedRecord, shells: ReadonlyMap<number, unknown>) => unknown;
|
|
6
|
+
readonly slot: (slot: SerializedSlot) => unknown;
|
|
7
|
+
};
|
|
8
|
+
declare function deserializeGraphValueForClient(payload: SerializedGraphPayload): Promise<unknown>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { type SerializedGraphPayload, type SerializedRecord, type SerializedSlot, ValueDecodeExtensions, deserializeGraphValueForClient };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/value-decode-client.ts
|
|
2
|
+
async function deserializeGraphValueForClient(payload) {
|
|
3
|
+
return deserializeGraphValueWithExtensions(payload, payloadNeedsExtendedDecoders(payload) ? (await import("./value-decode-extensions-BhNCyKre.js")).valueDecodeExtensions : void 0);
|
|
4
|
+
}
|
|
5
|
+
function deserializeGraphValueWithExtensions(payload, extensions) {
|
|
6
|
+
const shells = /* @__PURE__ */ new Map();
|
|
7
|
+
for (const record of payload.records) {
|
|
8
|
+
if (record.type === "object") shells.set(record.id, {});
|
|
9
|
+
if (record.type === "array") shells.set(record.id, []);
|
|
10
|
+
if (record.type === "map") shells.set(record.id, /* @__PURE__ */ new Map());
|
|
11
|
+
if (record.type === "set") shells.set(record.id, /* @__PURE__ */ new Set());
|
|
12
|
+
if (record.type === "date") shells.set(record.id, new Date(record.value));
|
|
13
|
+
const shell = extensions?.recordShell(record, shells);
|
|
14
|
+
if (shell !== void 0) shells.set(record.id, shell);
|
|
15
|
+
}
|
|
16
|
+
for (const record of payload.records) if (record.type === "typed-array" || record.type === "data-view") {
|
|
17
|
+
const shell = extensions?.recordShell(record, shells);
|
|
18
|
+
if (shell !== void 0) shells.set(record.id, shell);
|
|
19
|
+
}
|
|
20
|
+
for (const record of payload.records) {
|
|
21
|
+
const shell = shells.get(record.id);
|
|
22
|
+
if (record.type === "object") {
|
|
23
|
+
const object = shell;
|
|
24
|
+
for (const [key, slot] of record.fields) object[key] = decodeSlot(slot, shells, extensions);
|
|
25
|
+
}
|
|
26
|
+
if (record.type === "array") {
|
|
27
|
+
const array = shell;
|
|
28
|
+
for (const item of record.items) array.push(decodeSlot(item, shells, extensions));
|
|
29
|
+
}
|
|
30
|
+
if (record.type === "map") {
|
|
31
|
+
const map = shell;
|
|
32
|
+
for (const [key, value] of record.entries) map.set(decodeSlot(key, shells, extensions), decodeSlot(value, shells, extensions));
|
|
33
|
+
}
|
|
34
|
+
if (record.type === "set") {
|
|
35
|
+
const set = shell;
|
|
36
|
+
for (const value of record.values) set.add(decodeSlot(value, shells, extensions));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return decodeSlot(payload.root, shells, extensions);
|
|
40
|
+
}
|
|
41
|
+
function decodeSlot(slot, shells, extensions) {
|
|
42
|
+
if (slot === null || typeof slot === "string" || typeof slot === "number" || typeof slot === "boolean") return slot;
|
|
43
|
+
if ("$ref" in slot) return shells.get(slot.$ref);
|
|
44
|
+
if (slot.$type === "undefined") return void 0;
|
|
45
|
+
if (slot.$type === "bigint") return BigInt(slot.value);
|
|
46
|
+
if (slot.$type === "date") return new Date(slot.value);
|
|
47
|
+
return extensions?.slot(slot);
|
|
48
|
+
}
|
|
49
|
+
function payloadNeedsExtendedDecoders(payload) {
|
|
50
|
+
if (slotNeedsExtendedDecoder(payload.root)) return true;
|
|
51
|
+
return payload.records.some((record) => {
|
|
52
|
+
if (record.type === "regexp" || record.type === "url" || record.type === "array-buffer" || record.type === "typed-array" || record.type === "data-view") return true;
|
|
53
|
+
if (record.type === "object") return record.fields.some(([, slot]) => slotNeedsExtendedDecoder(slot));
|
|
54
|
+
if (record.type === "array") return record.items.some(slotNeedsExtendedDecoder);
|
|
55
|
+
if (record.type === "map") return record.entries.some(([key, value]) => slotNeedsExtendedDecoder(key) || slotNeedsExtendedDecoder(value));
|
|
56
|
+
if (record.type === "set") return record.values.some(slotNeedsExtendedDecoder);
|
|
57
|
+
return false;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function slotNeedsExtendedDecoder(slot) {
|
|
61
|
+
return typeof slot === "object" && slot !== null && "$type" in slot && (slot.$type === "regexp" || slot.$type === "url");
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { deserializeGraphValueForClient };
|
package/dist/decode.d.ts
ADDED
package/dist/decode.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/value-decode.ts
|
|
2
|
+
function deserializeGraphValue(payload) {
|
|
3
|
+
const shells = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const record of payload.records) {
|
|
5
|
+
if (record.type === "object") shells.set(record.id, {});
|
|
6
|
+
if (record.type === "array") shells.set(record.id, []);
|
|
7
|
+
if (record.type === "map") shells.set(record.id, /* @__PURE__ */ new Map());
|
|
8
|
+
if (record.type === "set") shells.set(record.id, /* @__PURE__ */ new Set());
|
|
9
|
+
if (record.type === "date") shells.set(record.id, new Date(record.value));
|
|
10
|
+
if (record.type === "regexp") shells.set(record.id, new RegExp(record.source, record.flags));
|
|
11
|
+
if (record.type === "url") shells.set(record.id, new URL(record.value));
|
|
12
|
+
if (record.type === "array-buffer") shells.set(record.id, new Uint8Array(record.bytes).buffer);
|
|
13
|
+
}
|
|
14
|
+
for (const record of payload.records) {
|
|
15
|
+
if (record.type === "typed-array") shells.set(record.id, createTypedArray(record, shells));
|
|
16
|
+
if (record.type === "data-view") shells.set(record.id, createDataView(record, shells));
|
|
17
|
+
}
|
|
18
|
+
for (const record of payload.records) {
|
|
19
|
+
const shell = shells.get(record.id);
|
|
20
|
+
if (record.type === "object") {
|
|
21
|
+
const object = shell;
|
|
22
|
+
for (const [key, slot] of record.fields) object[key] = decodeSlot(slot, shells);
|
|
23
|
+
}
|
|
24
|
+
if (record.type === "array") {
|
|
25
|
+
const array = shell;
|
|
26
|
+
for (const item of record.items) array.push(decodeSlot(item, shells));
|
|
27
|
+
}
|
|
28
|
+
if (record.type === "map") {
|
|
29
|
+
const map = shell;
|
|
30
|
+
for (const [key, value] of record.entries) map.set(decodeSlot(key, shells), decodeSlot(value, shells));
|
|
31
|
+
}
|
|
32
|
+
if (record.type === "set") {
|
|
33
|
+
const set = shell;
|
|
34
|
+
for (const value of record.values) set.add(decodeSlot(value, shells));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return decodeSlot(payload.root, shells);
|
|
38
|
+
}
|
|
39
|
+
function decodeSlot(slot, shells) {
|
|
40
|
+
if (slot === null || typeof slot === "string" || typeof slot === "number" || typeof slot === "boolean") return slot;
|
|
41
|
+
if ("$ref" in slot) return shells.get(slot.$ref);
|
|
42
|
+
if (slot.$type === "undefined") return void 0;
|
|
43
|
+
if (slot.$type === "bigint") return BigInt(slot.value);
|
|
44
|
+
if (slot.$type === "date") return new Date(slot.value);
|
|
45
|
+
if (slot.$type === "regexp") return new RegExp(slot.source, slot.flags);
|
|
46
|
+
if (slot.$type === "url") return new URL(slot.value);
|
|
47
|
+
}
|
|
48
|
+
function createTypedArray(record, shells) {
|
|
49
|
+
const buffer = decodeSlot(record.buffer, shells);
|
|
50
|
+
if (!(buffer instanceof ArrayBuffer)) return void 0;
|
|
51
|
+
const constructor = globalThis[record.arrayType];
|
|
52
|
+
return typeof constructor === "function" ? new constructor(buffer, record.byteOffset, record.length) : void 0;
|
|
53
|
+
}
|
|
54
|
+
function createDataView(record, shells) {
|
|
55
|
+
const buffer = decodeSlot(record.buffer, shells);
|
|
56
|
+
if (!(buffer instanceof ArrayBuffer)) return void 0;
|
|
57
|
+
return new DataView(buffer, record.byteOffset, record.byteLength);
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { deserializeGraphValue };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ASYNC_PROTOCOL_VERSION, ProtocolArmBranchRecord, ProtocolArmRecordSet, ProtocolPayloadScripts, ProtocolStatePayload, ProtocolSyncPolicy, ProtocolSyncPolicyBranch, ProtocolSyncPolicyCondition, ProtocolViewPayload } from "./protocol.js";
|
|
2
|
+
import { a as SerializedRecord, c as serializeGraphValue, i as SerializedPrimitive, l as deserializeGraphValue, n as SerializationResult, o as SerializedSlot, r as SerializedGraphPayload, s as TypedArrayName, t as SerializationDiagnostic } from "./value-CcROymhu.js";
|
|
3
|
+
import { DecodedPayloadScripts, EncodedPayloadScripts, RuntimePayloadDiagnostic, RuntimePayloadError, RuntimePayloadErrorCode, RuntimePayloadType, assertProtocolStateCellPayload, assertProtocolStatePayload, assertProtocolViewPayload, decodePayloadScripts, payloadInvalidError, payloadScriptSelector } from "./protocol-validation.js";
|
|
4
|
+
|
|
5
|
+
//#region src/payload-scripts.d.ts
|
|
6
|
+
type RenderPayloadScriptsInput = {
|
|
7
|
+
readonly state: ProtocolStatePayload;
|
|
8
|
+
readonly view: ProtocolViewPayload;
|
|
9
|
+
};
|
|
10
|
+
type RenderedPayloadScripts = {
|
|
11
|
+
readonly state: ProtocolStatePayload;
|
|
12
|
+
readonly view: ProtocolViewPayload;
|
|
13
|
+
readonly stateScript: string;
|
|
14
|
+
readonly viewScript: string;
|
|
15
|
+
};
|
|
16
|
+
declare function renderPayloadScripts(input: RenderPayloadScriptsInput): RenderedPayloadScripts;
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/protocol-state.d.ts
|
|
19
|
+
type ProtocolAsyncComputedSnapshotInput = {
|
|
20
|
+
readonly status: 'idle';
|
|
21
|
+
readonly version: 0;
|
|
22
|
+
} | {
|
|
23
|
+
readonly status: 'pending';
|
|
24
|
+
readonly version: number;
|
|
25
|
+
readonly key: unknown;
|
|
26
|
+
} | {
|
|
27
|
+
readonly status: 'fulfilled';
|
|
28
|
+
readonly version: number;
|
|
29
|
+
readonly key: unknown;
|
|
30
|
+
readonly value: unknown;
|
|
31
|
+
} | {
|
|
32
|
+
readonly status: 'rejected';
|
|
33
|
+
readonly version: number;
|
|
34
|
+
readonly key: unknown;
|
|
35
|
+
readonly error: unknown;
|
|
36
|
+
};
|
|
37
|
+
type ProtocolStatePayloadInput = {
|
|
38
|
+
readonly cells: ReadonlyArray<{
|
|
39
|
+
readonly graphNodeId: string;
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly valueKind: 'scalar' | 'object' | 'array' | 'unknown';
|
|
42
|
+
readonly value: unknown;
|
|
43
|
+
}>;
|
|
44
|
+
readonly computed?: ReadonlyArray<Omit<ProtocolStatePayload['computed'][number], 'snapshot'> & {
|
|
45
|
+
readonly snapshot?: ProtocolAsyncComputedSnapshotInput;
|
|
46
|
+
}>;
|
|
47
|
+
readonly sharedDefinitions?: ProtocolStatePayload['sharedDefinitions'];
|
|
48
|
+
};
|
|
49
|
+
type ProtocolStateSerializationDiagnostic = SerializationDiagnostic & {
|
|
50
|
+
readonly graphNodeId: string;
|
|
51
|
+
readonly cellName: string;
|
|
52
|
+
};
|
|
53
|
+
declare class ProtocolStateSerializationError extends Error implements ProtocolStateSerializationDiagnostic {
|
|
54
|
+
readonly code: SerializationDiagnostic['code'];
|
|
55
|
+
readonly severity: SerializationDiagnostic['severity'];
|
|
56
|
+
readonly phase: SerializationDiagnostic['phase'];
|
|
57
|
+
readonly title: SerializationDiagnostic['title'];
|
|
58
|
+
readonly path: SerializationDiagnostic['path'];
|
|
59
|
+
readonly statePath: SerializationDiagnostic['statePath'];
|
|
60
|
+
readonly valueKind: SerializationDiagnostic['valueKind'];
|
|
61
|
+
readonly why: SerializationDiagnostic['why'];
|
|
62
|
+
readonly suggestions: SerializationDiagnostic['suggestions'];
|
|
63
|
+
readonly docsUrl: SerializationDiagnostic['docsUrl'];
|
|
64
|
+
readonly graphNodeId: string;
|
|
65
|
+
readonly cellName: string;
|
|
66
|
+
constructor(diagnostic: ProtocolStateSerializationDiagnostic);
|
|
67
|
+
}
|
|
68
|
+
declare function createProtocolStatePayload(input: ProtocolStatePayloadInput): ProtocolStatePayload;
|
|
69
|
+
declare function serializeRuntimeStateCells(cells: ProtocolStatePayload['cells']): ProtocolStatePayload['cells'];
|
|
70
|
+
declare function serializeRuntimeAsyncSnapshots(computed: ProtocolStatePayload['computed']): ProtocolStatePayload['computed'];
|
|
71
|
+
//#endregion
|
|
72
|
+
export { ASYNC_PROTOCOL_VERSION, DecodedPayloadScripts, EncodedPayloadScripts, ProtocolArmBranchRecord, ProtocolArmRecordSet, ProtocolAsyncComputedSnapshotInput, ProtocolPayloadScripts, ProtocolStatePayload, ProtocolStatePayloadInput, ProtocolStateSerializationDiagnostic, ProtocolStateSerializationError, ProtocolSyncPolicy, ProtocolSyncPolicyBranch, ProtocolSyncPolicyCondition, ProtocolViewPayload, RenderPayloadScriptsInput, RenderedPayloadScripts, RuntimePayloadDiagnostic, RuntimePayloadError, RuntimePayloadErrorCode, RuntimePayloadType, SerializationDiagnostic, SerializationResult, SerializedGraphPayload, SerializedPrimitive, SerializedRecord, SerializedSlot, TypedArrayName, assertProtocolStateCellPayload, assertProtocolStatePayload, assertProtocolViewPayload, createProtocolStatePayload, decodePayloadScripts, deserializeGraphValue, payloadInvalidError, payloadScriptSelector, renderPayloadScripts, serializeGraphValue, serializeRuntimeAsyncSnapshots, serializeRuntimeStateCells };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { ASYNC_PROTOCOL_VERSION } from "./protocol.js";
|
|
2
|
+
import { deserializeGraphValue } from "./decode.js";
|
|
3
|
+
import { RuntimePayloadError, assertProtocolStateCellPayload, assertProtocolStatePayload, assertProtocolViewPayload, decodePayloadScripts, payloadInvalidError, payloadScriptSelector } from "./protocol-validation.js";
|
|
4
|
+
//#region src/payload-scripts.ts
|
|
5
|
+
function renderPayloadScripts(input) {
|
|
6
|
+
return {
|
|
7
|
+
state: input.state,
|
|
8
|
+
view: input.view,
|
|
9
|
+
stateScript: renderDataScript("markless/state", input.state),
|
|
10
|
+
viewScript: renderDataScript("markless/view", input.view)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function renderDataScript(type, payload) {
|
|
14
|
+
return `<script type="${type}">${escapeScriptJson(JSON.stringify(payload))}<\/script>`;
|
|
15
|
+
}
|
|
16
|
+
function escapeScriptJson(value) {
|
|
17
|
+
return value.replace(/</g, "\\u003C");
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/value.ts
|
|
21
|
+
function serializeGraphValue(value) {
|
|
22
|
+
const diagnostics = [];
|
|
23
|
+
const seen = /* @__PURE__ */ new WeakMap();
|
|
24
|
+
const records = [];
|
|
25
|
+
const root = encodeSlot(value, [], seen, records, diagnostics);
|
|
26
|
+
if (root === void 0 || diagnostics.length > 0) return {
|
|
27
|
+
ok: false,
|
|
28
|
+
diagnostics
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
ok: true,
|
|
32
|
+
payload: {
|
|
33
|
+
version: 1,
|
|
34
|
+
root,
|
|
35
|
+
records
|
|
36
|
+
},
|
|
37
|
+
diagnostics: []
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function encodeSlot(value, path, seen, records, diagnostics) {
|
|
41
|
+
if (value === null) return null;
|
|
42
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
43
|
+
if (typeof value === "undefined") return { $type: "undefined" };
|
|
44
|
+
if (typeof value === "bigint") return {
|
|
45
|
+
$type: "bigint",
|
|
46
|
+
value: String(value)
|
|
47
|
+
};
|
|
48
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
49
|
+
diagnostics.push(unsupportedDiagnostic(value, path));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!isObject(value)) {
|
|
53
|
+
diagnostics.push(unsupportedDiagnostic(value, path));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (value instanceof Date) {
|
|
57
|
+
const existingId = seen.get(value);
|
|
58
|
+
if (existingId !== void 0) return { $ref: existingId };
|
|
59
|
+
const id = records.length;
|
|
60
|
+
seen.set(value, id);
|
|
61
|
+
records.push({
|
|
62
|
+
id,
|
|
63
|
+
type: "date",
|
|
64
|
+
value: value.toISOString()
|
|
65
|
+
});
|
|
66
|
+
return { $ref: id };
|
|
67
|
+
}
|
|
68
|
+
if (value instanceof RegExp) {
|
|
69
|
+
const existingId = seen.get(value);
|
|
70
|
+
if (existingId !== void 0) return { $ref: existingId };
|
|
71
|
+
const id = records.length;
|
|
72
|
+
seen.set(value, id);
|
|
73
|
+
records.push({
|
|
74
|
+
id,
|
|
75
|
+
type: "regexp",
|
|
76
|
+
source: value.source,
|
|
77
|
+
flags: value.flags
|
|
78
|
+
});
|
|
79
|
+
return { $ref: id };
|
|
80
|
+
}
|
|
81
|
+
if (value instanceof URL) {
|
|
82
|
+
const existingId = seen.get(value);
|
|
83
|
+
if (existingId !== void 0) return { $ref: existingId };
|
|
84
|
+
const id = records.length;
|
|
85
|
+
seen.set(value, id);
|
|
86
|
+
records.push({
|
|
87
|
+
id,
|
|
88
|
+
type: "url",
|
|
89
|
+
value: value.toString()
|
|
90
|
+
});
|
|
91
|
+
return { $ref: id };
|
|
92
|
+
}
|
|
93
|
+
if (value instanceof ArrayBuffer) {
|
|
94
|
+
const existingId = seen.get(value);
|
|
95
|
+
if (existingId !== void 0) return { $ref: existingId };
|
|
96
|
+
const id = records.length;
|
|
97
|
+
seen.set(value, id);
|
|
98
|
+
records.push({
|
|
99
|
+
id,
|
|
100
|
+
type: "array-buffer",
|
|
101
|
+
bytes: [...new Uint8Array(value)]
|
|
102
|
+
});
|
|
103
|
+
return { $ref: id };
|
|
104
|
+
}
|
|
105
|
+
const arrayType = typedArrayName(value);
|
|
106
|
+
if (arrayType) {
|
|
107
|
+
const existingId = seen.get(value);
|
|
108
|
+
if (existingId !== void 0) return { $ref: existingId };
|
|
109
|
+
const id = records.length;
|
|
110
|
+
seen.set(value, id);
|
|
111
|
+
records.push({
|
|
112
|
+
id,
|
|
113
|
+
type: "typed-array",
|
|
114
|
+
arrayType,
|
|
115
|
+
buffer: encodeArrayBufferViewBuffer(value, path, seen, records, diagnostics),
|
|
116
|
+
byteOffset: typedArrayByteOffset(value),
|
|
117
|
+
length: typedArrayLength(value)
|
|
118
|
+
});
|
|
119
|
+
return { $ref: id };
|
|
120
|
+
}
|
|
121
|
+
if (value instanceof DataView) {
|
|
122
|
+
const existingId = seen.get(value);
|
|
123
|
+
if (existingId !== void 0) return { $ref: existingId };
|
|
124
|
+
const id = records.length;
|
|
125
|
+
seen.set(value, id);
|
|
126
|
+
records.push({
|
|
127
|
+
id,
|
|
128
|
+
type: "data-view",
|
|
129
|
+
buffer: encodeArrayBufferViewBuffer(value, path, seen, records, diagnostics),
|
|
130
|
+
byteOffset: value.byteOffset,
|
|
131
|
+
byteLength: value.byteLength
|
|
132
|
+
});
|
|
133
|
+
return { $ref: id };
|
|
134
|
+
}
|
|
135
|
+
const existingId = seen.get(value);
|
|
136
|
+
if (existingId !== void 0) return { $ref: existingId };
|
|
137
|
+
const id = records.length;
|
|
138
|
+
seen.set(value, id);
|
|
139
|
+
if (Array.isArray(value)) {
|
|
140
|
+
const record = {
|
|
141
|
+
id,
|
|
142
|
+
type: "array",
|
|
143
|
+
items: []
|
|
144
|
+
};
|
|
145
|
+
records.push(record);
|
|
146
|
+
record.items.push(...value.map((item, index) => encodeSlot(item, [...path, String(index)], seen, records, diagnostics)).filter(isSerializedSlot));
|
|
147
|
+
return { $ref: id };
|
|
148
|
+
}
|
|
149
|
+
if (value instanceof Map) {
|
|
150
|
+
const record = {
|
|
151
|
+
id,
|
|
152
|
+
type: "map",
|
|
153
|
+
entries: []
|
|
154
|
+
};
|
|
155
|
+
records.push(record);
|
|
156
|
+
let index = 0;
|
|
157
|
+
for (const [key, item] of value) {
|
|
158
|
+
const keySlot = encodeSlot(key, [...path, `mapKey:${index}`], seen, records, diagnostics);
|
|
159
|
+
const valueSlot = encodeSlot(item, [...path, `mapValue:${index}`], seen, records, diagnostics);
|
|
160
|
+
if (keySlot !== void 0 && valueSlot !== void 0) record.entries.push([keySlot, valueSlot]);
|
|
161
|
+
index++;
|
|
162
|
+
}
|
|
163
|
+
return { $ref: id };
|
|
164
|
+
}
|
|
165
|
+
if (value instanceof Set) {
|
|
166
|
+
const record = {
|
|
167
|
+
id,
|
|
168
|
+
type: "set",
|
|
169
|
+
values: []
|
|
170
|
+
};
|
|
171
|
+
records.push(record);
|
|
172
|
+
let index = 0;
|
|
173
|
+
for (const item of value) {
|
|
174
|
+
const slot = encodeSlot(item, [...path, `set:${index}`], seen, records, diagnostics);
|
|
175
|
+
if (slot !== void 0) record.values.push(slot);
|
|
176
|
+
index++;
|
|
177
|
+
}
|
|
178
|
+
return { $ref: id };
|
|
179
|
+
}
|
|
180
|
+
if (!isPlainObject(value)) {
|
|
181
|
+
diagnostics.push(unsupportedDiagnostic(value, path));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const record = {
|
|
185
|
+
id,
|
|
186
|
+
type: "object",
|
|
187
|
+
fields: []
|
|
188
|
+
};
|
|
189
|
+
records.push(record);
|
|
190
|
+
for (const [key, item] of Object.entries(value)) {
|
|
191
|
+
const slot = encodeSlot(item, [...path, key], seen, records, diagnostics);
|
|
192
|
+
if (slot !== void 0) record.fields.push([key, slot]);
|
|
193
|
+
}
|
|
194
|
+
return { $ref: id };
|
|
195
|
+
}
|
|
196
|
+
function typedArrayName(value) {
|
|
197
|
+
if (value instanceof Int8Array) return "Int8Array";
|
|
198
|
+
if (value instanceof Uint8Array) return "Uint8Array";
|
|
199
|
+
if (value instanceof Uint8ClampedArray) return "Uint8ClampedArray";
|
|
200
|
+
if (value instanceof Int16Array) return "Int16Array";
|
|
201
|
+
if (value instanceof Uint16Array) return "Uint16Array";
|
|
202
|
+
if (value instanceof Int32Array) return "Int32Array";
|
|
203
|
+
if (value instanceof Uint32Array) return "Uint32Array";
|
|
204
|
+
if (value instanceof Float32Array) return "Float32Array";
|
|
205
|
+
if (value instanceof Float64Array) return "Float64Array";
|
|
206
|
+
if (typeof BigInt64Array !== "undefined" && value instanceof BigInt64Array) return "BigInt64Array";
|
|
207
|
+
if (typeof BigUint64Array !== "undefined" && value instanceof BigUint64Array) return "BigUint64Array";
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
function encodeArrayBufferViewBuffer(value, path, seen, records, diagnostics) {
|
|
211
|
+
const buffer = value.buffer;
|
|
212
|
+
if (!(buffer instanceof ArrayBuffer)) {
|
|
213
|
+
diagnostics.push(unsupportedDiagnostic(buffer, [...path, "buffer"]));
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
return encodeSlot(buffer, [...path, "buffer"], seen, records, diagnostics) ?? null;
|
|
217
|
+
}
|
|
218
|
+
function typedArrayByteOffset(value) {
|
|
219
|
+
return value.byteOffset;
|
|
220
|
+
}
|
|
221
|
+
function typedArrayLength(value) {
|
|
222
|
+
return value.length;
|
|
223
|
+
}
|
|
224
|
+
function unsupportedDiagnostic(value, path) {
|
|
225
|
+
const valueKind = describeUnsupportedValue(value);
|
|
226
|
+
const formattedPath = formatPath(path);
|
|
227
|
+
const isClassOrHost = isObject(value) && !isPlainObject(value);
|
|
228
|
+
return {
|
|
229
|
+
code: "MARKLESS_SERIALIZE_UNSUPPORTED_VALUE",
|
|
230
|
+
severity: "error",
|
|
231
|
+
phase: "serialization",
|
|
232
|
+
title: "Cannot serialize graph state value",
|
|
233
|
+
path,
|
|
234
|
+
statePath: formattedPath,
|
|
235
|
+
valueKind,
|
|
236
|
+
message: isClassOrHost ? `Cannot serialize value at ${formattedPath} because ${valueKind} is a live host/class resource that is not durable graph state and would resume as an empty/plain object.` : `Cannot serialize value at ${formattedPath} because ${valueKind} values are not durable graph state.`,
|
|
237
|
+
why: isClassOrHost ? "Serialization is for durable graph state. Host and runtime resources cannot be reconstructed from a payload during resume." : "Serialization is for durable graph state. Functions and host/runtime resources cannot be restored during resume.",
|
|
238
|
+
suggestions: [{ message: isClassOrHost ? "Keep live resources in attach={...} behaviors and store serializable connection data such as URLs and status flags in state." : "Move runtime resources into attach={...}, make the value serializable state, or derive it with computed()." }],
|
|
239
|
+
docsUrl: "https://markless.dev/errors/MARKLESS_SERIALIZE_UNSUPPORTED_VALUE"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function formatPath(path) {
|
|
243
|
+
return path.length === 0 ? "<root>" : path.join(".");
|
|
244
|
+
}
|
|
245
|
+
function isObject(value) {
|
|
246
|
+
return typeof value === "object" && value !== null;
|
|
247
|
+
}
|
|
248
|
+
function isPlainObject(value) {
|
|
249
|
+
const prototype = Object.getPrototypeOf(value);
|
|
250
|
+
return prototype === Object.prototype || prototype === null;
|
|
251
|
+
}
|
|
252
|
+
function describeUnsupportedValue(value) {
|
|
253
|
+
if (isObject(value)) return value.constructor?.name ?? "object";
|
|
254
|
+
return typeof value;
|
|
255
|
+
}
|
|
256
|
+
function isSerializedSlot(value) {
|
|
257
|
+
return value !== void 0;
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/protocol-state.ts
|
|
261
|
+
var ProtocolStateSerializationError = class extends Error {
|
|
262
|
+
code;
|
|
263
|
+
severity;
|
|
264
|
+
phase;
|
|
265
|
+
title;
|
|
266
|
+
path;
|
|
267
|
+
statePath;
|
|
268
|
+
valueKind;
|
|
269
|
+
why;
|
|
270
|
+
suggestions;
|
|
271
|
+
docsUrl;
|
|
272
|
+
graphNodeId;
|
|
273
|
+
cellName;
|
|
274
|
+
constructor(diagnostic) {
|
|
275
|
+
super(diagnostic.message);
|
|
276
|
+
this.name = "ProtocolStateSerializationError";
|
|
277
|
+
this.code = diagnostic.code;
|
|
278
|
+
this.severity = diagnostic.severity;
|
|
279
|
+
this.phase = diagnostic.phase;
|
|
280
|
+
this.title = diagnostic.title;
|
|
281
|
+
this.path = diagnostic.path;
|
|
282
|
+
this.statePath = diagnostic.statePath;
|
|
283
|
+
this.valueKind = diagnostic.valueKind;
|
|
284
|
+
this.why = diagnostic.why;
|
|
285
|
+
this.suggestions = diagnostic.suggestions;
|
|
286
|
+
this.docsUrl = diagnostic.docsUrl;
|
|
287
|
+
this.graphNodeId = diagnostic.graphNodeId;
|
|
288
|
+
this.cellName = diagnostic.cellName;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
function createProtocolStatePayload(input) {
|
|
292
|
+
return {
|
|
293
|
+
version: 1,
|
|
294
|
+
cells: input.cells.map((cell) => {
|
|
295
|
+
const result = serializeGraphValue(cell.value);
|
|
296
|
+
if (!result.ok) throw protocolStateSerializationError(cell, result.diagnostics[0]);
|
|
297
|
+
return {
|
|
298
|
+
graphNodeId: cell.graphNodeId,
|
|
299
|
+
name: cell.name,
|
|
300
|
+
valueKind: cell.valueKind,
|
|
301
|
+
value: result.payload
|
|
302
|
+
};
|
|
303
|
+
}),
|
|
304
|
+
computed: (input.computed ?? []).map(serializeComputedSnapshot),
|
|
305
|
+
sharedDefinitions: input.sharedDefinitions
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function serializeRuntimeStateCells(cells) {
|
|
309
|
+
return cells.map((cell) => {
|
|
310
|
+
if (cell.directValue === void 0) return cell;
|
|
311
|
+
const durable = {
|
|
312
|
+
graphNodeId: cell.graphNodeId,
|
|
313
|
+
name: cell.name ?? "",
|
|
314
|
+
valueKind: cell.valueKind ?? "unknown",
|
|
315
|
+
value: cell.directValue
|
|
316
|
+
};
|
|
317
|
+
const result = serializeGraphValue(cell.directValue);
|
|
318
|
+
if (!result.ok) throw protocolStateSerializationError(durable, result.diagnostics[0]);
|
|
319
|
+
return {
|
|
320
|
+
...durable,
|
|
321
|
+
value: result.payload
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function serializeRuntimeAsyncSnapshots(computed) {
|
|
326
|
+
return computed.map((record) => serializeComputedSnapshot(record));
|
|
327
|
+
}
|
|
328
|
+
function serializeComputedSnapshot(computed) {
|
|
329
|
+
if (!computed.snapshot) return computed;
|
|
330
|
+
if (computed.snapshot.status === "idle") return computed;
|
|
331
|
+
const key = serializeProtocolStateField(computed, "key", computed.snapshot.key);
|
|
332
|
+
if (computed.snapshot.status === "pending") return {
|
|
333
|
+
...computed,
|
|
334
|
+
snapshot: {
|
|
335
|
+
status: computed.snapshot.status,
|
|
336
|
+
version: computed.snapshot.version,
|
|
337
|
+
key
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
if (computed.snapshot.status === "fulfilled") return {
|
|
341
|
+
...computed,
|
|
342
|
+
snapshot: {
|
|
343
|
+
status: computed.snapshot.status,
|
|
344
|
+
version: computed.snapshot.version,
|
|
345
|
+
key,
|
|
346
|
+
value: serializeProtocolStateField(computed, "value", computed.snapshot.value)
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
return {
|
|
350
|
+
...computed,
|
|
351
|
+
snapshot: {
|
|
352
|
+
status: computed.snapshot.status,
|
|
353
|
+
version: computed.snapshot.version,
|
|
354
|
+
key,
|
|
355
|
+
error: serializeProtocolStateField(computed, "error", durableSnapshotError(computed.snapshot.error))
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function durableSnapshotError(error) {
|
|
360
|
+
if (error instanceof Error) return {
|
|
361
|
+
$type: "error",
|
|
362
|
+
name: error.name,
|
|
363
|
+
message: error.message
|
|
364
|
+
};
|
|
365
|
+
return error;
|
|
366
|
+
}
|
|
367
|
+
function serializeProtocolStateField(computed, field, value) {
|
|
368
|
+
const result = serializeGraphValue(value);
|
|
369
|
+
if (!result.ok) throw protocolStateSerializationError({
|
|
370
|
+
graphNodeId: computed.graphNodeId,
|
|
371
|
+
name: `${computed.name}.snapshot.${field}`,
|
|
372
|
+
valueKind: "unknown",
|
|
373
|
+
value
|
|
374
|
+
}, result.diagnostics[0]);
|
|
375
|
+
return result.payload;
|
|
376
|
+
}
|
|
377
|
+
function protocolStateSerializationError(cell, diagnostic) {
|
|
378
|
+
const cellPrefix = cell.name === "" ? cell.graphNodeId : cell.name;
|
|
379
|
+
const base = diagnostic ?? {
|
|
380
|
+
code: "MARKLESS_SERIALIZE_UNSUPPORTED_VALUE",
|
|
381
|
+
severity: "error",
|
|
382
|
+
phase: "serialization",
|
|
383
|
+
title: "Cannot serialize graph state value",
|
|
384
|
+
path: [],
|
|
385
|
+
statePath: cellPrefix,
|
|
386
|
+
valueKind: "unknown",
|
|
387
|
+
message: `Cannot serialize value at ${cellPrefix} because unknown values are not durable graph state.`,
|
|
388
|
+
why: "Serialization is for durable graph state. Functions and host/runtime resources cannot be restored during resume.",
|
|
389
|
+
suggestions: [{ message: "Move runtime resources into attach={...}, make the value serializable state, or derive it with computed()." }],
|
|
390
|
+
docsUrl: "https://markless.dev/errors/MARKLESS_SERIALIZE_UNSUPPORTED_VALUE"
|
|
391
|
+
};
|
|
392
|
+
const statePath = base.statePath === "<root>" ? cellPrefix : `${cellPrefix}.${base.statePath}`;
|
|
393
|
+
return new ProtocolStateSerializationError({
|
|
394
|
+
...base,
|
|
395
|
+
graphNodeId: cell.graphNodeId,
|
|
396
|
+
cellName: cell.name,
|
|
397
|
+
statePath,
|
|
398
|
+
message: `Cannot serialize value at ${statePath} because ${base.valueKind} values are not durable graph state.`
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
export { ASYNC_PROTOCOL_VERSION, ProtocolStateSerializationError, RuntimePayloadError, assertProtocolStateCellPayload, assertProtocolStatePayload, assertProtocolViewPayload, createProtocolStatePayload, decodePayloadScripts, deserializeGraphValue, payloadInvalidError, payloadScriptSelector, renderPayloadScripts, serializeGraphValue, serializeRuntimeAsyncSnapshots, serializeRuntimeStateCells };
|