@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.
@@ -0,0 +1,666 @@
1
+ import "./protocol.js";
2
+ //#region src/protocol-validation.ts
3
+ var RuntimePayloadError = class extends Error {
4
+ code;
5
+ severity;
6
+ phase;
7
+ title;
8
+ why;
9
+ payloadType;
10
+ payloadScript;
11
+ expectedVersion;
12
+ actualVersion;
13
+ suggestions;
14
+ docsUrl;
15
+ constructor(diagnostic) {
16
+ super(diagnostic.message);
17
+ this.name = "RuntimePayloadError";
18
+ this.code = diagnostic.code;
19
+ this.severity = diagnostic.severity;
20
+ this.phase = diagnostic.phase;
21
+ this.title = diagnostic.title;
22
+ this.why = diagnostic.why;
23
+ this.payloadType = diagnostic.payloadType;
24
+ this.payloadScript = diagnostic.payloadScript;
25
+ this.expectedVersion = diagnostic.expectedVersion;
26
+ this.actualVersion = diagnostic.actualVersion;
27
+ this.suggestions = diagnostic.suggestions;
28
+ this.docsUrl = diagnostic.docsUrl;
29
+ }
30
+ };
31
+ function decodePayloadScripts(input) {
32
+ const state = parseDataScript(input.stateScript, "markless/state");
33
+ const view = parseDataScript(input.viewScript, "markless/view");
34
+ assertProtocolStatePayload(state);
35
+ assertProtocolViewPayload(view);
36
+ assertProtocolVersion(state.version, "markless/state");
37
+ assertProtocolVersion(view.version, "markless/view");
38
+ return {
39
+ state,
40
+ view
41
+ };
42
+ }
43
+ function parseDataScript(script, type) {
44
+ const prefix = `<script type="${type}">`;
45
+ if (!script.startsWith(prefix) || !script.endsWith("<\/script>")) throw payloadInvalidError(type, `Expected ${type} payload script.`, `Browser resume expects the ${type} data to arrive in a canonical ${payloadScriptSelector(type)} script wrapper before decoding the resumability protocol.`, [{ message: `Emit the ${type} payload with renderPayloadScripts or an equivalent canonical script wrapper.` }]);
46
+ try {
47
+ return JSON.parse(script.slice(prefix.length, -9));
48
+ } catch {
49
+ throw payloadInvalidError(type, `Invalid ${type} payload JSON.`, `The ${type} payload script must contain valid JSON before the runtime can validate the resumability protocol fields.`, [{ message: `Emit valid JSON inside the ${payloadScriptSelector(type)} script content.` }]);
50
+ }
51
+ }
52
+ function assertProtocolStatePayload(payload) {
53
+ if (!isRecord(payload)) throw invalidPayloadShapeError("markless/state", "Invalid markless/state payload: expected object.");
54
+ if (!("version" in payload)) throw invalidPayloadShapeError("markless/state", "Invalid markless/state payload: expected version.");
55
+ const cells = requiredPayloadArrayField(payload, "cells", "markless/state");
56
+ for (const [index, cell] of cells.entries()) assertProtocolStateCellPayload(cell, `markless/state cell[${index}]`);
57
+ const computedEntries = requiredPayloadArrayField(payload, "computed", "markless/state");
58
+ for (const [index, computed] of computedEntries.entries()) {
59
+ const context = `markless/state computed[${index}]`;
60
+ assertRecordShape(computed, context);
61
+ assertStringField(computed, "graphNodeId", context);
62
+ assertStringField(computed, "name", context);
63
+ assertBooleanField(computed, "async", context);
64
+ assertOptionalComputedDependencies(computed, context);
65
+ assertOptionalAsyncComputedSnapshot(computed, context);
66
+ }
67
+ assertOptionalSharedDefinitions(payload);
68
+ }
69
+ function assertProtocolStateCellPayload(cell, context) {
70
+ assertRecordShape(cell, context);
71
+ assertStringField(cell, "graphNodeId", context);
72
+ assertStringField(cell, "name", context);
73
+ assertStateValueKind(cell, context);
74
+ if ("value" in cell) assertSerializedGraphPayload(cell.value, `${context}.value`);
75
+ if ("directValue" in cell) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: live directValue cells must be serialized before serving.`);
76
+ }
77
+ function assertProtocolViewPayload(payload) {
78
+ if (!isRecord(payload)) throw invalidPayloadShapeError("markless/view", "Invalid markless/view payload: expected object.");
79
+ if (!("version" in payload)) throw invalidPayloadShapeError("markless/view", "Invalid markless/view payload: expected version.");
80
+ const locators = requiredPayloadArrayField(payload, "locators", "markless/view");
81
+ const events = requiredPayloadArrayField(payload, "events", "markless/view");
82
+ const domUpdates = requiredPayloadArrayField(payload, "domUpdates", "markless/view");
83
+ const behaviors = requiredPayloadArrayField(payload, "behaviors", "markless/view");
84
+ const elementHandles = requiredPayloadArrayField(payload, "elementHandles", "markless/view");
85
+ const asyncBoundaries = requiredPayloadArrayField(payload, "asyncBoundaries", "markless/view");
86
+ for (const [index, locator] of locators.entries()) {
87
+ const context = `markless/view locator[${index}]`;
88
+ assertRecordShape(locator, context);
89
+ assertStringField(locator, "hostNodeId", context);
90
+ assertLiteralField(locator, "strategy", "dom-order", context);
91
+ assertNonNegativeIntegerField(locator, "index", context);
92
+ assertStringField(locator, "tagName", context);
93
+ }
94
+ for (const [index, event] of events.entries()) {
95
+ const context = `markless/view event[${index}]`;
96
+ assertRecordShape(event, context);
97
+ assertStringField(event, "hostNodeId", context);
98
+ assertStringField(event, "eventName", context);
99
+ assertStringArrayField(event, "symbolIds", context);
100
+ if (event.syncPolicy !== void 0) assertSyncPolicy(event.syncPolicy, `${context}.syncPolicy`);
101
+ }
102
+ for (const [index, domUpdate] of domUpdates.entries()) {
103
+ const context = `markless/view domUpdate[${index}]`;
104
+ assertRecordShape(domUpdate, context);
105
+ assertStringField(domUpdate, "hostNodeId", context);
106
+ assertStringField(domUpdate, "source", context);
107
+ assertStringField(domUpdate, "graphNodeId", context);
108
+ assertStringArrayField(domUpdate, "path", context);
109
+ assertOptionalDomUpdateTarget(domUpdate.target, `${context}.target`);
110
+ assertOptionalStringField(domUpdate, "symbolId", context);
111
+ }
112
+ for (const [index, behavior] of behaviors.entries()) {
113
+ const context = `markless/view behavior[${index}]`;
114
+ assertRecordShape(behavior, context);
115
+ assertStringField(behavior, "hostNodeId", context);
116
+ assertStringField(behavior, "source", context);
117
+ assertStringField(behavior, "functionSource", context);
118
+ assertStringArrayField(behavior, "inputSources", context);
119
+ assertOptionalArrayField(behavior, "inputValues", context);
120
+ assertOptionalBehaviorInputGraphReads(behavior, context);
121
+ assertOptionalStringField(behavior, "symbolId", context);
122
+ }
123
+ for (const [index, handle] of elementHandles.entries()) {
124
+ const context = `markless/view elementHandle[${index}]`;
125
+ assertRecordShape(handle, context);
126
+ assertStringField(handle, "hostNodeId", context);
127
+ assertStringField(handle, "handleId", context);
128
+ assertStringField(handle, "name", context);
129
+ }
130
+ for (const [index, boundary] of asyncBoundaries.entries()) {
131
+ const context = `markless/view asyncBoundary[${index}]`;
132
+ assertRecordShape(boundary, context);
133
+ assertStringField(boundary, "id", context);
134
+ assertCommentAnchor(boundary.startAnchor, `${context}.startAnchor`);
135
+ assertCommentAnchor(boundary.endAnchor, `${context}.endAnchor`);
136
+ assertAsyncBoundaryReads(boundary.asyncReads, context);
137
+ }
138
+ assertOptionalKeyedRepeats(payload);
139
+ assertOptionalBranches(payload);
140
+ }
141
+ function assertProtocolVersion(version, type) {
142
+ if (version !== 1) throw protocolVersionMismatchError(type, version);
143
+ }
144
+ function isRecord(value) {
145
+ return typeof value === "object" && value !== null && !Array.isArray(value);
146
+ }
147
+ function requiredPayloadArrayField(record, key, payloadType) {
148
+ if (!Object.prototype.hasOwnProperty.call(record, key) || !Array.isArray(record[key])) throw invalidPayloadShapeError(payloadType, `Invalid ${payloadType} payload: expected ${key} array.`);
149
+ return record[key];
150
+ }
151
+ function assertRecordShape(value, context) {
152
+ if (!isRecord(value)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected object.`);
153
+ }
154
+ function assertStringField(record, key, context) {
155
+ if (typeof record[key] !== "string") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} string.`);
156
+ }
157
+ function assertOptionalStringField(record, key, context) {
158
+ if (record[key] !== void 0 && typeof record[key] !== "string") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} string.`);
159
+ }
160
+ function assertBooleanField(record, key, context) {
161
+ if (typeof record[key] !== "boolean") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} boolean.`);
162
+ }
163
+ function assertLiteralField(record, key, expected, context) {
164
+ if (record[key] !== expected) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} "${expected}".`);
165
+ }
166
+ function assertStringArrayField(record, key, context) {
167
+ if (!Array.isArray(record[key])) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} array.`);
168
+ for (const value of record[key]) if (typeof value !== "string") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} string array.`);
169
+ }
170
+ function assertNonNegativeIntegerArrayField(record, key, context) {
171
+ if (!Array.isArray(record[key])) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} array.`);
172
+ for (const value of record[key]) if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} non-negative integer array.`);
173
+ }
174
+ function assertOptionalStringArrayField(record, key, context) {
175
+ if (record[key] !== void 0) assertStringArrayField(record, key, context);
176
+ }
177
+ function assertOptionalArrayField(record, key, context) {
178
+ if (record[key] !== void 0 && !Array.isArray(record[key])) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} array.`);
179
+ }
180
+ function assertOptionalKeyedRepeats(record) {
181
+ if (record.keyedRepeats === void 0) return;
182
+ if (!Array.isArray(record.keyedRepeats)) throw invalidPayloadShapeError("markless/view", "Invalid markless/view payload: expected keyedRepeats array.");
183
+ for (const [index, repeat] of record.keyedRepeats.entries()) {
184
+ const context = `markless/view keyedRepeat[${index}]`;
185
+ assertRecordShape(repeat, context);
186
+ assertStringField(repeat, "id", context);
187
+ assertStringField(repeat, "parentHostNodeId", context);
188
+ assertOptionalStringField(repeat, "collectionGraphNodeId", context);
189
+ assertStringArrayField(repeat, "collectionPath", context);
190
+ assertStringArrayField(repeat, "keyPath", context);
191
+ assertStringField(repeat, "itemName", context);
192
+ assertNonNegativeIntegerField(repeat, "rowElementCount", context);
193
+ assertRowEvents(repeat.rowEvents, `${context}.rowEvents`);
194
+ }
195
+ }
196
+ function assertOptionalBranches(record) {
197
+ if (record.branches === void 0) return;
198
+ if (!Array.isArray(record.branches)) throw invalidPayloadShapeError("markless/view", "Invalid markless/view payload: expected branches array.");
199
+ for (const [index, branch] of record.branches.entries()) {
200
+ const context = `markless/view branch[${index}]`;
201
+ assertRecordShape(branch, context);
202
+ assertStringField(branch, "id", context);
203
+ assertCommentAnchor(branch.startAnchor, `${context}.startAnchor`);
204
+ assertCommentAnchor(branch.endAnchor, `${context}.endAnchor`);
205
+ assertOptionalStringField(branch, "symbolId", context);
206
+ assertOptionalGraphReads(branch, "testReads", context);
207
+ if (branch.armTests !== void 0 && !Array.isArray(branch.armTests)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected armTests array.`);
208
+ assertOptionalBranchArmRecords(branch, context);
209
+ }
210
+ }
211
+ function assertOptionalBranchArmRecords(record, context) {
212
+ if (record.armRecords === void 0) return;
213
+ if (!Array.isArray(record.armRecords)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected armRecords array.`);
214
+ for (const [index, arm] of record.armRecords.entries()) {
215
+ const armContext = `${context}.armRecords[${index}]`;
216
+ assertRecordShape(arm, armContext);
217
+ assertRowEvents(arm.events, `${armContext}.events`);
218
+ assertOptionalArrayField(arm, "domUpdates", armContext);
219
+ assertOptionalArrayField(arm, "behaviors", armContext);
220
+ assertOptionalArrayField(arm, "elementHandles", armContext);
221
+ }
222
+ }
223
+ function assertRowEvents(value, context) {
224
+ if (!Array.isArray(value)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected array.`);
225
+ for (const [index, event] of value.entries()) {
226
+ const eventContext = `${context}[${index}]`;
227
+ assertRecordShape(event, eventContext);
228
+ assertNonNegativeIntegerArrayField(event, "hostPath", eventContext);
229
+ assertStringField(event, "eventName", eventContext);
230
+ assertStringArrayField(event, "symbolIds", eventContext);
231
+ if (event.syncPolicy !== void 0) assertSyncPolicy(event.syncPolicy, `${eventContext}.syncPolicy`);
232
+ }
233
+ }
234
+ function assertOptionalGraphReads(record, key, context) {
235
+ const reads = record[key];
236
+ if (reads === void 0) return;
237
+ if (!Array.isArray(reads)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} array.`);
238
+ for (const [index, read] of reads.entries()) {
239
+ const readContext = `${context}.${key}[${index}]`;
240
+ assertRecordShape(read, readContext);
241
+ assertStringField(read, "source", readContext);
242
+ assertStringField(read, "graphNodeId", readContext);
243
+ assertStringArrayField(read, "path", readContext);
244
+ }
245
+ }
246
+ function assertOptionalBehaviorInputGraphReads(record, context) {
247
+ if (record.inputGraphReads === void 0) return;
248
+ if (!Array.isArray(record.inputGraphReads)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected inputGraphReads array.`);
249
+ for (const [index, read] of record.inputGraphReads.entries()) {
250
+ const readContext = `${context}.inputGraphReads[${index}]`;
251
+ assertRecordShape(read, readContext);
252
+ assertNonNegativeIntegerField(read, "inputIndex", readContext);
253
+ assertStringField(read, "source", readContext);
254
+ assertStringField(read, "graphNodeId", readContext);
255
+ assertStringArrayField(read, "path", readContext);
256
+ }
257
+ }
258
+ function assertOptionalComputedDependencies(record, context) {
259
+ if (record.dependencies === void 0) return;
260
+ if (!Array.isArray(record.dependencies)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected dependencies array.`);
261
+ for (const [index, dependency] of record.dependencies.entries()) {
262
+ const dependencyContext = `${context}.dependencies[${index}]`;
263
+ assertRecordShape(dependency, dependencyContext);
264
+ assertStringField(dependency, "graphNodeId", dependencyContext);
265
+ assertStringArrayField(dependency, "path", dependencyContext);
266
+ }
267
+ }
268
+ function assertOptionalSharedDefinitions(record) {
269
+ if (record.sharedDefinitions === void 0) return;
270
+ if (!Array.isArray(record.sharedDefinitions)) throw invalidPayloadShapeError("markless/state", "Invalid markless/state payload: expected sharedDefinitions array.");
271
+ for (const [index, definition] of record.sharedDefinitions.entries()) {
272
+ const context = `markless/state sharedDefinitions[${index}]`;
273
+ assertRecordShape(definition, context);
274
+ assertStringField(definition, "id", context);
275
+ assertStringField(definition, "name", context);
276
+ assertStringField(definition, "exportedName", context);
277
+ assertOptionalSharedScope(definition, context);
278
+ assertNonNegativeIntegerField(definition, "version", context);
279
+ assertStringArrayField(definition, "graphNodeIds", context);
280
+ assertOptionalSharedDependencies(definition, context);
281
+ assertOptionalSharedReturnProperties(definition, context);
282
+ }
283
+ }
284
+ function assertOptionalSharedScope(record, context) {
285
+ if (record.scope === void 0) return;
286
+ if (record.scope === "request" || record.scope === "container" || record.scope === "page") return;
287
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected scope request, container, or page.`);
288
+ }
289
+ function assertOptionalSharedDependencies(record, context) {
290
+ if (record.dependencies === void 0) return;
291
+ if (!Array.isArray(record.dependencies)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected dependencies array.`);
292
+ for (const [index, dependency] of record.dependencies.entries()) {
293
+ const dependencyContext = `${context}.dependencies[${index}]`;
294
+ assertRecordShape(dependency, dependencyContext);
295
+ assertStringField(dependency, "definitionId", dependencyContext);
296
+ assertStringField(dependency, "definitionName", dependencyContext);
297
+ }
298
+ }
299
+ function assertOptionalSharedReturnProperties(record, context) {
300
+ if (record.returnProperties === void 0) return;
301
+ if (!Array.isArray(record.returnProperties)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected returnProperties array.`);
302
+ for (const [index, property] of record.returnProperties.entries()) {
303
+ const propertyContext = `${context}.returnProperties[${index}]`;
304
+ assertRecordShape(property, propertyContext);
305
+ assertStringField(property, "kind", propertyContext);
306
+ assertStringField(property, "name", propertyContext);
307
+ if (property.kind === "graph") {
308
+ assertStringField(property, "graphNodeId", propertyContext);
309
+ assertStringArrayField(property, "path", propertyContext);
310
+ continue;
311
+ }
312
+ if (property.kind === "method") continue;
313
+ throw invalidPayloadShapeError(contextPayloadType(propertyContext), `Invalid ${propertyContext}: expected graph or method return property kind.`);
314
+ }
315
+ }
316
+ function assertOptionalAsyncComputedSnapshot(record, context) {
317
+ if (record.snapshot === void 0) return;
318
+ const snapshotContext = `${context}.snapshot`;
319
+ assertRecordShape(record.snapshot, snapshotContext);
320
+ assertStringField(record.snapshot, "status", snapshotContext);
321
+ assertNonNegativeIntegerField(record.snapshot, "version", snapshotContext);
322
+ if (record.snapshot.status === "idle") {
323
+ if (record.snapshot.version !== 0) throw invalidPayloadShapeError(contextPayloadType(snapshotContext), `Invalid ${snapshotContext}: expected idle version 0.`);
324
+ return;
325
+ }
326
+ if (record.snapshot.status !== "pending" && record.snapshot.status !== "fulfilled" && record.snapshot.status !== "rejected") throw invalidPayloadShapeError(contextPayloadType(snapshotContext), `Invalid ${snapshotContext}: expected supported async snapshot status.`);
327
+ if (!("key" in record.snapshot)) throw invalidPayloadShapeError(contextPayloadType(snapshotContext), `Invalid ${snapshotContext}: expected key.`);
328
+ assertSerializedGraphPayload(record.snapshot.key, `${snapshotContext}.key`);
329
+ if (record.snapshot.status === "fulfilled" && !("value" in record.snapshot)) throw invalidPayloadShapeError(contextPayloadType(snapshotContext), `Invalid ${snapshotContext}: expected value.`);
330
+ if (record.snapshot.status === "fulfilled") assertSerializedGraphPayload(record.snapshot.value, `${snapshotContext}.value`);
331
+ if (record.snapshot.status === "rejected" && !("error" in record.snapshot)) throw invalidPayloadShapeError(contextPayloadType(snapshotContext), `Invalid ${snapshotContext}: expected error.`);
332
+ if (record.snapshot.status === "rejected") assertSerializedGraphPayload(record.snapshot.error, `${snapshotContext}.error`);
333
+ }
334
+ function assertSerializedGraphPayload(value, context) {
335
+ assertRecordShape(value, context);
336
+ if (value.version !== 1) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected version 1.`);
337
+ if (!("root" in value)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected root.`);
338
+ if (!Array.isArray(value.records)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected records array.`);
339
+ const recordIds = /* @__PURE__ */ new Set();
340
+ const recordsById = /* @__PURE__ */ new Map();
341
+ for (const [index, record] of value.records.entries()) {
342
+ const recordContext = `${context}.records[${index}]`;
343
+ assertSerializedRecord(record, recordContext);
344
+ const recordId = record.id;
345
+ if (recordIds.has(recordId)) throw invalidPayloadShapeError(contextPayloadType(recordContext), `Invalid ${recordContext}: duplicate record id ${String(recordId)}.`);
346
+ recordIds.add(recordId);
347
+ recordsById.set(recordId, record);
348
+ }
349
+ assertSerializedSlot(value.root, `${context}.root`);
350
+ assertSerializedSlotReferences(value.root, `${context}.root`, recordIds);
351
+ for (const [index, record] of value.records.entries()) {
352
+ const recordContext = `${context}.records[${index}]`;
353
+ const recordObject = record;
354
+ assertSerializedRecordReferences(recordObject, recordContext, recordIds);
355
+ assertArrayBufferViewRecordRange(recordObject, recordContext, recordsById);
356
+ }
357
+ }
358
+ function assertSerializedRecord(value, context) {
359
+ assertRecordShape(value, context);
360
+ assertNonNegativeIntegerField(value, "id", context);
361
+ assertStringField(value, "type", context);
362
+ if (value.type === "object") {
363
+ assertSerializedEntries(value, "fields", context, "field");
364
+ return;
365
+ }
366
+ if (value.type === "array") {
367
+ assertSerializedSlotArray(value.items, `${context}.items`);
368
+ return;
369
+ }
370
+ if (value.type === "map") {
371
+ assertSerializedEntries(value, "entries", context, "entry");
372
+ return;
373
+ }
374
+ if (value.type === "set") {
375
+ assertSerializedSlotArray(value.values, `${context}.values`);
376
+ return;
377
+ }
378
+ if (value.type === "date" || value.type === "url") {
379
+ assertStringField(value, "value", context);
380
+ if (value.type === "date") assertIsoDateString(value.value, context);
381
+ if (value.type === "url") assertUrlString(value.value, context);
382
+ return;
383
+ }
384
+ if (value.type === "regexp") {
385
+ assertStringField(value, "source", context);
386
+ assertStringField(value, "flags", context);
387
+ assertRegExpParts(value.source, value.flags, context);
388
+ return;
389
+ }
390
+ if (value.type === "array-buffer") {
391
+ assertByteArrayField(value, "bytes", context);
392
+ return;
393
+ }
394
+ if (value.type === "typed-array") {
395
+ assertStringField(value, "arrayType", context);
396
+ assertSupportedTypedArrayType(value.arrayType, context);
397
+ assertSerializedSlot(value.buffer, `${context}.buffer`);
398
+ assertNonNegativeIntegerField(value, "byteOffset", context);
399
+ assertNonNegativeIntegerField(value, "length", context);
400
+ return;
401
+ }
402
+ if (value.type === "data-view") {
403
+ assertSerializedSlot(value.buffer, `${context}.buffer`);
404
+ assertNonNegativeIntegerField(value, "byteOffset", context);
405
+ assertNonNegativeIntegerField(value, "byteLength", context);
406
+ return;
407
+ }
408
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected supported serialized record type.`);
409
+ }
410
+ function assertSupportedTypedArrayType(value, context) {
411
+ if (value === "Int8Array" || value === "Uint8Array" || value === "Uint8ClampedArray" || value === "Int16Array" || value === "Uint16Array" || value === "Int32Array" || value === "Uint32Array" || value === "Float32Array" || value === "Float64Array" || value === "BigInt64Array" || value === "BigUint64Array") return;
412
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected supported typed array type.`);
413
+ }
414
+ function assertSerializedRecordReferences(record, context, recordIds) {
415
+ if (record.type === "object") {
416
+ assertSerializedEntryReferences(record.fields, `${context}.fields`, recordIds, false);
417
+ return;
418
+ }
419
+ if (record.type === "array") {
420
+ assertSerializedSlotArrayReferences(record.items, `${context}.items`, recordIds);
421
+ return;
422
+ }
423
+ if (record.type === "map") {
424
+ assertSerializedEntryReferences(record.entries, `${context}.entries`, recordIds, true);
425
+ return;
426
+ }
427
+ if (record.type === "set") {
428
+ assertSerializedSlotArrayReferences(record.values, `${context}.values`, recordIds);
429
+ return;
430
+ }
431
+ if (record.type === "typed-array" || record.type === "data-view") assertSerializedSlotReferences(record.buffer, `${context}.buffer`, recordIds);
432
+ }
433
+ function assertSerializedEntryReferences(value, context, recordIds, keyIsSlot) {
434
+ if (!Array.isArray(value)) return;
435
+ for (const [index, entry] of value.entries()) {
436
+ if (!Array.isArray(entry) || entry.length !== 2) continue;
437
+ if (keyIsSlot) assertSerializedSlotReferences(entry[0], `${context}[${index}][0]`, recordIds);
438
+ assertSerializedSlotReferences(entry[1], `${context}[${index}][1]`, recordIds);
439
+ }
440
+ }
441
+ function assertSerializedSlotArrayReferences(value, context, recordIds) {
442
+ if (!Array.isArray(value)) return;
443
+ for (const [index, slot] of value.entries()) assertSerializedSlotReferences(slot, `${context}[${index}]`, recordIds);
444
+ }
445
+ function assertSerializedSlotReferences(value, context, recordIds) {
446
+ if (!isRecord(value) || !("$ref" in value) || typeof value.$ref !== "number") return;
447
+ if (!Number.isInteger(value.$ref) || value.$ref < 0) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected $ref non-negative integer.`);
448
+ if (recordIds.has(value.$ref)) return;
449
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: unknown record ref ${String(value.$ref)}.`);
450
+ }
451
+ function assertArrayBufferViewRecordRange(record, context, recordsById) {
452
+ if (record.type !== "typed-array" && record.type !== "data-view") return;
453
+ const bytes = referencedArrayBufferRecord(record.buffer, context, recordsById).bytes;
454
+ if (!Array.isArray(bytes)) return;
455
+ const byteOffset = record.byteOffset;
456
+ if (record.type === "typed-array") {
457
+ const elementByteLength = typedArrayElementByteLength(record.arrayType);
458
+ if (byteOffset % elementByteLength !== 0) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: typed-array byteOffset must align to element byte length.`);
459
+ if (byteOffset + record.length * elementByteLength > bytes.length) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: typed-array byte range exceeds referenced array-buffer.`);
460
+ return;
461
+ }
462
+ if (byteOffset + record.byteLength > bytes.length) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: data-view byte range exceeds referenced array-buffer.`);
463
+ }
464
+ function referencedArrayBufferRecord(value, context, recordsById) {
465
+ if (!isRecord(value) || typeof value.$ref !== "number") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected buffer array-buffer ref.`);
466
+ const record = recordsById.get(value.$ref);
467
+ if (record?.type !== "array-buffer") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected buffer array-buffer ref.`);
468
+ return record;
469
+ }
470
+ function typedArrayElementByteLength(value) {
471
+ if (value === "Int8Array" || value === "Uint8Array" || value === "Uint8ClampedArray") return 1;
472
+ if (value === "Int16Array" || value === "Uint16Array") return 2;
473
+ if (value === "Int32Array" || value === "Uint32Array" || value === "Float32Array") return 4;
474
+ return 8;
475
+ }
476
+ function assertSerializedEntries(record, key, context, entryName) {
477
+ const entries = record[key];
478
+ if (!Array.isArray(entries)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} array.`);
479
+ for (const [index, entry] of entries.entries()) {
480
+ if (!Array.isArray(entry) || entry.length !== 2) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}.${key}[${index}]: expected ${entryName} pair.`);
481
+ if (key === "fields" && typeof entry[0] !== "string") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}.${key}[${index}]: expected field name string.`);
482
+ else if (key === "entries") assertSerializedSlot(entry[0], `${context}.${key}[${index}][0]`);
483
+ assertSerializedSlot(entry[1], `${context}.${key}[${index}][1]`);
484
+ }
485
+ }
486
+ function assertSerializedSlotArray(value, context) {
487
+ if (!Array.isArray(value)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected array.`);
488
+ for (const [index, slot] of value.entries()) assertSerializedSlot(slot, `${context}[${index}]`);
489
+ }
490
+ function assertByteArrayField(record, key, context) {
491
+ if (!Array.isArray(record[key])) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} array.`);
492
+ for (const value of record[key]) if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 255) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} byte array.`);
493
+ }
494
+ function assertSerializedSlot(value, context) {
495
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return;
496
+ if (!isRecord(value)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected serialized slot.`);
497
+ if ("$ref" in value) {
498
+ assertNonNegativeIntegerField(value, "$ref", context);
499
+ return;
500
+ }
501
+ if (value.$type === "undefined") return;
502
+ if (value.$type === "bigint") {
503
+ assertStringField(value, "value", context);
504
+ assertBigIntString(value.value, context);
505
+ return;
506
+ }
507
+ if (value.$type === "date" || value.$type === "url") {
508
+ assertStringField(value, "value", context);
509
+ if (value.$type === "date") assertIsoDateString(value.value, context);
510
+ if (value.$type === "url") assertUrlString(value.value, context);
511
+ return;
512
+ }
513
+ if (value.$type === "regexp") {
514
+ assertStringField(value, "source", context);
515
+ assertStringField(value, "flags", context);
516
+ assertRegExpParts(value.source, value.flags, context);
517
+ return;
518
+ }
519
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected serialized slot.`);
520
+ }
521
+ function assertNonNegativeIntegerField(record, key, context) {
522
+ if (typeof record[key] !== "number" || !Number.isInteger(record[key]) || record[key] < 0) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ${key} non-negative integer.`);
523
+ }
524
+ function assertBigIntString(value, context) {
525
+ if (typeof value === "string" && /^-?(0|[1-9]\d*)$/.test(value)) return;
526
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected bigint string.`);
527
+ }
528
+ function assertIsoDateString(value, context) {
529
+ if (typeof value === "string") {
530
+ const date = new Date(value);
531
+ if (!Number.isNaN(date.getTime()) && date.toISOString() === value) return;
532
+ }
533
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected ISO date string.`);
534
+ }
535
+ function assertUrlString(value, context) {
536
+ if (typeof value === "string") try {
537
+ new URL(value);
538
+ return;
539
+ } catch {}
540
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected valid URL string.`);
541
+ }
542
+ function assertRegExpParts(source, flags, context) {
543
+ if (typeof source === "string" && typeof flags === "string") try {
544
+ new RegExp(source, flags);
545
+ return;
546
+ } catch {}
547
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected valid regexp pattern and flags.`);
548
+ }
549
+ function assertStateValueKind(record, context) {
550
+ if (record.valueKind !== "scalar" && record.valueKind !== "object" && record.valueKind !== "array" && record.valueKind !== "unknown") throw invalidPayloadShapeError(contextPayloadType(context), "Invalid " + context + ": expected valueKind scalar, object, array, or unknown.");
551
+ }
552
+ function assertCommentAnchor(value, context) {
553
+ assertRecordShape(value, context);
554
+ assertLiteralField(value, "strategy", "dom-order-comment", context);
555
+ assertNonNegativeIntegerField(value, "index", context);
556
+ }
557
+ function assertAsyncBoundaryReads(value, context) {
558
+ if (!Array.isArray(value)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected asyncReads array.`);
559
+ for (const [index, read] of value.entries()) {
560
+ const readContext = `${context}.asyncRead[${index}]`;
561
+ assertRecordShape(read, readContext);
562
+ assertStringField(read, "source", readContext);
563
+ assertStringField(read, "graphNodeId", readContext);
564
+ assertStringArrayField(read, "path", readContext);
565
+ assertOptionalStringField(read, "runnerSymbolId", readContext);
566
+ }
567
+ }
568
+ function assertOptionalDomUpdateTarget(value, context) {
569
+ if (value === void 0) return;
570
+ assertRecordShape(value, context);
571
+ if (value.kind === "text") return;
572
+ if (value.kind === "class") return;
573
+ if (value.kind === "style") return;
574
+ if (value.kind === "attribute") {
575
+ if (typeof value.name !== "string") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected attribute name string.`);
576
+ return;
577
+ }
578
+ if (value.kind === "property") {
579
+ if (typeof value.name !== "string") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected property name string.`);
580
+ return;
581
+ }
582
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected supported DOM update target kind.`);
583
+ }
584
+ function assertSyncPolicy(value, context) {
585
+ assertRecordShape(value, context);
586
+ if ("branches" in value) {
587
+ if (!Array.isArray(value.branches)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected branches array.`);
588
+ for (const [index, branch] of value.branches.entries()) assertSyncPolicyBranch(branch, `${context}.branch[${index}]`);
589
+ return;
590
+ }
591
+ assertSyncPolicyBranch(value, context);
592
+ }
593
+ function assertSyncPolicyBranch(value, context) {
594
+ assertRecordShape(value, context);
595
+ assertSyncPolicyCondition(value.when, `${context}.when`);
596
+ if (!Array.isArray(value.actions)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected actions array.`);
597
+ for (const action of value.actions) if (action !== "preventDefault" && action !== "stopPropagation") throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected supported sync action.`);
598
+ }
599
+ function assertSyncPolicyCondition(value, context) {
600
+ assertRecordShape(value, context);
601
+ if (value.type === "and" || value.type === "or") {
602
+ if (!Array.isArray(value.conditions)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected conditions array.`);
603
+ for (const [index, condition] of value.conditions.entries()) assertSyncPolicyCondition(condition, `${context}.condition[${index}]`);
604
+ return;
605
+ }
606
+ if (value.type === "not") {
607
+ assertSyncPolicyCondition(value.condition, `${context}.condition`);
608
+ return;
609
+ }
610
+ if (value.type === "graph-truthy") {
611
+ assertStringField(value, "graphNodeId", context);
612
+ assertOptionalStringArrayField(value, "path", context);
613
+ return;
614
+ }
615
+ if (value.type === "constant-truthy") {
616
+ if (!("value" in value)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected value.`);
617
+ return;
618
+ }
619
+ if (value.type === "event-equals") {
620
+ assertStringField(value, "field", context);
621
+ if (!("value" in value)) throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected value.`);
622
+ return;
623
+ }
624
+ throw invalidPayloadShapeError(contextPayloadType(context), `Invalid ${context}: expected supported condition type.`);
625
+ }
626
+ function payloadInvalidError(payloadType, message, why, suggestions) {
627
+ return new RuntimePayloadError({
628
+ code: "MARKLESS_PAYLOAD_INVALID",
629
+ severity: "error",
630
+ phase: "payload",
631
+ title: "Invalid resumability payload",
632
+ message,
633
+ why,
634
+ payloadType,
635
+ payloadScript: payloadScriptSelector(payloadType),
636
+ suggestions,
637
+ docsUrl: "https://markless.dev/errors/MARKLESS_PAYLOAD_INVALID"
638
+ });
639
+ }
640
+ function invalidPayloadShapeError(payloadType, message) {
641
+ return payloadInvalidError(payloadType, message, `The ${payloadType} payload did not match the resumability protocol shape required by this runtime.`, [{ message: `Regenerate the ${payloadType} payload with the matching markless compiler/runtime version.` }]);
642
+ }
643
+ function protocolVersionMismatchError(payloadType, actualVersion) {
644
+ return new RuntimePayloadError({
645
+ code: "MARKLESS_PROTOCOL_VERSION_MISMATCH",
646
+ severity: "error",
647
+ phase: "payload",
648
+ title: "Unsupported resumability protocol version",
649
+ message: `Unsupported ${payloadType} protocol version ${String(actualVersion)}.`,
650
+ why: `The ${payloadType} payload was produced for protocol version ${String(actualVersion)}, but this runtime can only decode version ${String(1)}.`,
651
+ payloadType,
652
+ payloadScript: payloadScriptSelector(payloadType),
653
+ expectedVersion: 1,
654
+ actualVersion,
655
+ suggestions: [{ message: "Use matching markless compiler and runtime package versions." }],
656
+ docsUrl: "https://markless.dev/errors/MARKLESS_PROTOCOL_VERSION_MISMATCH"
657
+ });
658
+ }
659
+ function contextPayloadType(context) {
660
+ return context.startsWith("markless/state") ? "markless/state" : "markless/view";
661
+ }
662
+ function payloadScriptSelector(type) {
663
+ return `script[type="${type}"]`;
664
+ }
665
+ //#endregion
666
+ export { RuntimePayloadError, assertProtocolStateCellPayload, assertProtocolStatePayload, assertProtocolViewPayload, decodePayloadScripts, payloadInvalidError, payloadScriptSelector };