@xnetjs/data 2.3.0 → 2.5.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/dist/index.js CHANGED
@@ -222,7 +222,73 @@ import {
222
222
  validateJsonData,
223
223
  validateRollupConfig,
224
224
  visibleFormQuestions
225
- } from "./chunk-56EXPOJ4.js";
225
+ } from "./chunk-TW5XJHNF.js";
226
+ import {
227
+ MemoryNodeStorageAdapter,
228
+ NodeStore,
229
+ PermissionError,
230
+ QUERY_AST_VERSION,
231
+ SQLiteNodeStorageAdapter,
232
+ TEMP_ID_PREFIX,
233
+ and,
234
+ avg,
235
+ between,
236
+ contains,
237
+ count,
238
+ countDistinct,
239
+ createNodeStorageAdapter,
240
+ createSchemaLookup,
241
+ dashboardQuerySet,
242
+ defineNodeQueryAST,
243
+ defineQuerySetAST,
244
+ defineSavedViewDescriptor,
245
+ eq,
246
+ evaluateQueryASTPlannerGate,
247
+ executeQueryASTLoadedAggregates,
248
+ filterQueryASTLoadedRows,
249
+ follow,
250
+ from,
251
+ getQueryASTRelationIndexRequirements,
252
+ groupBy,
253
+ gt,
254
+ gte,
255
+ having,
256
+ includesAny,
257
+ isNotNull,
258
+ isNull,
259
+ isTempId,
260
+ lt,
261
+ lte,
262
+ matchesQueryASTLoadedRow,
263
+ max,
264
+ min,
265
+ neq,
266
+ not,
267
+ or,
268
+ planQueryASTAggregates,
269
+ queryOperators,
270
+ querySetCount,
271
+ resolveTempIds,
272
+ startsWith,
273
+ sum,
274
+ validateQueryAST,
275
+ validateSavedViewDescriptor
276
+ } from "./chunk-ODOPVGUX.js";
277
+ import {
278
+ applyNodeQueryDescriptor,
279
+ createNodeQueryDescriptor,
280
+ decodeNodeQueryCursor,
281
+ encodeNodeQueryCursor,
282
+ filterNodeQueryResults,
283
+ getNodeQuerySearchTokens,
284
+ matchesNodeQueryDescriptor,
285
+ nodeQueryDescriptorNeedsBoundedReload,
286
+ nodeQueryDescriptorToOptions,
287
+ serializeNodeQueryDescriptor,
288
+ sortNodeQueryResults,
289
+ withoutNodeQueryMaterializedView,
290
+ withoutNodeQueryPagination
291
+ } from "./chunk-WOEFYS7Y.js";
226
292
  import {
227
293
  LensRegistry,
228
294
  MAX_LINK_PREVIEWS_PER_MESSAGE,
@@ -291,7 +357,7 @@ import {
291
357
  taskBranchName,
292
358
  transform,
293
359
  when
294
- } from "./chunk-32IVMT6Z.js";
360
+ } from "./chunk-MAB662PR.js";
295
361
  import {
296
362
  AbuseReportSchema,
297
363
  AppealSchema,
@@ -544,7 +610,7 @@ import {
544
610
  } from "./chunk-R2AZXT3D.js";
545
611
  import {
546
612
  PageSchema
547
- } from "./chunk-Y2XWK7AA.js";
613
+ } from "./chunk-JF4VCZER.js";
548
614
  import {
549
615
  FOLDER_SCHEMA_IRI,
550
616
  FolderSchema,
@@ -599,72 +665,6 @@ import {
599
665
  DatabaseViewSchema
600
666
  } from "./chunk-UFV5QWCL.js";
601
667
  import "./chunk-RLRAWZJE.js";
602
- import {
603
- MemoryNodeStorageAdapter,
604
- NodeStore,
605
- PermissionError,
606
- QUERY_AST_VERSION,
607
- SQLiteNodeStorageAdapter,
608
- TEMP_ID_PREFIX,
609
- and,
610
- avg,
611
- between,
612
- contains,
613
- count,
614
- countDistinct,
615
- createNodeStorageAdapter,
616
- createSchemaLookup,
617
- dashboardQuerySet,
618
- defineNodeQueryAST,
619
- defineQuerySetAST,
620
- defineSavedViewDescriptor,
621
- eq,
622
- evaluateQueryASTPlannerGate,
623
- executeQueryASTLoadedAggregates,
624
- filterQueryASTLoadedRows,
625
- follow,
626
- from,
627
- getQueryASTRelationIndexRequirements,
628
- groupBy,
629
- gt,
630
- gte,
631
- having,
632
- includesAny,
633
- isNotNull,
634
- isNull,
635
- isTempId,
636
- lt,
637
- lte,
638
- matchesQueryASTLoadedRow,
639
- max,
640
- min,
641
- neq,
642
- not,
643
- or,
644
- planQueryASTAggregates,
645
- queryOperators,
646
- querySetCount,
647
- resolveTempIds,
648
- startsWith,
649
- sum,
650
- validateQueryAST,
651
- validateSavedViewDescriptor
652
- } from "./chunk-27AF2RSY.js";
653
- import {
654
- applyNodeQueryDescriptor,
655
- createNodeQueryDescriptor,
656
- decodeNodeQueryCursor,
657
- encodeNodeQueryCursor,
658
- filterNodeQueryResults,
659
- getNodeQuerySearchTokens,
660
- matchesNodeQueryDescriptor,
661
- nodeQueryDescriptorNeedsBoundedReload,
662
- nodeQueryDescriptorToOptions,
663
- serializeNodeQueryDescriptor,
664
- sortNodeQueryResults,
665
- withoutNodeQueryMaterializedView,
666
- withoutNodeQueryPagination
667
- } from "./chunk-WOEFYS7Y.js";
668
668
  import {
669
669
  evaluateLedgerWrite,
670
670
  foldAccountRecord,
@@ -2049,9 +2049,646 @@ var BlobService = class {
2049
2049
  }
2050
2050
  };
2051
2051
 
2052
+ // src/portability/types.ts
2053
+ var XNETPACK_FORMAT_VERSION = "xnetpack/1";
2054
+ var BUNDLE_ENTRY = {
2055
+ manifest: "manifest.json",
2056
+ changes: "changes.ndjson",
2057
+ blobIndex: "blobs.ndjson",
2058
+ yjsDocs: "yjs/docs.ndjson"
2059
+ };
2060
+ var FRONTIER_HEADS_CAP = 32;
2061
+
2062
+ // src/portability/serialize.ts
2063
+ import { bytesToBase64, base64ToBytes, hash, hashHex } from "@xnetjs/crypto";
2064
+ var textEncoder = new TextEncoder();
2065
+ var textDecoder = new TextDecoder();
2066
+ function toPortableChangeRecord(change) {
2067
+ const record = {
2068
+ id: change.id,
2069
+ type: change.type,
2070
+ hash: change.hash,
2071
+ nodeId: change.payload.nodeId,
2072
+ schemaId: change.payload.schemaId,
2073
+ lamportTime: change.lamport,
2074
+ authorDid: change.authorDID,
2075
+ wallTime: change.wallTime,
2076
+ parentHash: change.parentHash,
2077
+ payload: change.payload,
2078
+ signatureB64: bytesToBase64(change.signature),
2079
+ // protocolVersion is part of the hashed fields — dropping it makes the
2080
+ // record fail verifyChangeHash on the importing side.
2081
+ protocolVersion: change.protocolVersion,
2082
+ batchId: change.batchId,
2083
+ batchIndex: change.batchIndex,
2084
+ batchSize: change.batchSize
2085
+ };
2086
+ return record;
2087
+ }
2088
+ function fromPortableChangeRecord(record) {
2089
+ const payload = record.payload && !record.payload.schemaId && record.schemaId ? { ...record.payload, schemaId: record.schemaId } : record.payload;
2090
+ return {
2091
+ id: record.id,
2092
+ type: record.type,
2093
+ hash: record.hash,
2094
+ parentHash: record.parentHash,
2095
+ authorDID: record.authorDid,
2096
+ signature: base64ToBytes(record.signatureB64),
2097
+ wallTime: record.wallTime,
2098
+ lamport: record.lamportTime,
2099
+ payload,
2100
+ protocolVersion: record.protocolVersion,
2101
+ batchId: record.batchId,
2102
+ batchIndex: record.batchIndex,
2103
+ batchSize: record.batchSize
2104
+ };
2105
+ }
2106
+ function sortObjectKeys(obj) {
2107
+ if (obj === null || typeof obj !== "object") return obj;
2108
+ if (Array.isArray(obj)) return obj.map(sortObjectKeys);
2109
+ const sorted = {};
2110
+ for (const key of Object.keys(obj).sort()) {
2111
+ sorted[key] = sortObjectKeys(obj[key]);
2112
+ }
2113
+ return sorted;
2114
+ }
2115
+ function canonicalManifestBytes(manifest) {
2116
+ const unsigned = { ...manifest };
2117
+ delete unsigned.signatureB64;
2118
+ return textEncoder.encode(JSON.stringify(sortObjectKeys(unsigned)));
2119
+ }
2120
+ function createNdjsonDigest() {
2121
+ const lineDigests = [];
2122
+ return {
2123
+ addLine(line) {
2124
+ lineDigests.push(hash(textEncoder.encode(line)));
2125
+ },
2126
+ finish() {
2127
+ const concat = new Uint8Array(lineDigests.length * 32);
2128
+ lineDigests.forEach((d, i) => concat.set(d, i * 32));
2129
+ return hashHex(concat);
2130
+ },
2131
+ lineCount() {
2132
+ return lineDigests.length;
2133
+ }
2134
+ };
2135
+ }
2136
+ function digestEntryBytes(bytes) {
2137
+ return hashHex(bytes);
2138
+ }
2139
+ function combineEntryDigests(entries) {
2140
+ const lines = [...entries.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([path, digest]) => `${path}
2141
+ ${digest}
2142
+ `);
2143
+ return hashHex(textEncoder.encode(lines.join("")));
2144
+ }
2145
+ function encodeNdjsonLine(value) {
2146
+ return JSON.stringify(value);
2147
+ }
2148
+ function decodeUtf8(bytes) {
2149
+ return textDecoder.decode(bytes);
2150
+ }
2151
+ function encodeUtf8(text2) {
2152
+ return textEncoder.encode(text2);
2153
+ }
2154
+
2155
+ // src/portability/write.ts
2156
+ import { compareChangeApplicationOrder } from "@xnetjs/core";
2157
+ import { CURRENT_PROTOCOL_VERSION } from "@xnetjs/sync";
2158
+ import { bytesToBase64 as bytesToBase642 } from "@xnetjs/crypto";
2159
+ function blobEntryPath(cid) {
2160
+ const match = /^cid:([a-z0-9-]+):([0-9a-f]+)$/i.exec(cid);
2161
+ if (match) return `blobs/${match[1]}/${match[2]}`;
2162
+ return `blobs/raw/${encodeURIComponent(cid)}`;
2163
+ }
2164
+ function frontierOf(changes) {
2165
+ let lamport = 0;
2166
+ for (const change of changes) if (change.lamport > lamport) lamport = change.lamport;
2167
+ const heads = changes.filter((c) => c.lamport === lamport).slice(0, FRONTIER_HEADS_CAP).map((c) => c.hash);
2168
+ return { lamport, heads, changeCount: changes.length };
2169
+ }
2170
+ function spaceOf(properties) {
2171
+ return properties["space"];
2172
+ }
2173
+ async function collectScopedNodeIds(store, scope) {
2174
+ if (scope.kind === "nodes") return [...scope.nodeIds];
2175
+ if (scope.kind === "schemas") {
2176
+ const ids2 = [];
2177
+ for (const schemaId of scope.schemaIds) {
2178
+ const nodes = await store.list({ schemaId, includeDeleted: true });
2179
+ for (const node of nodes) ids2.push(node.id);
2180
+ }
2181
+ return ids2;
2182
+ }
2183
+ if (scope.kind !== "space") return [];
2184
+ const all = await store.list({ includeDeleted: true });
2185
+ const ids = [];
2186
+ for (const node of all) {
2187
+ if (node.id === scope.spaceId) {
2188
+ ids.push(node.id);
2189
+ continue;
2190
+ }
2191
+ const space = spaceOf(node.properties);
2192
+ if (space === scope.spaceId) ids.push(node.id);
2193
+ else if (Array.isArray(space) && space.includes(scope.spaceId)) ids.push(node.id);
2194
+ }
2195
+ return ids;
2196
+ }
2197
+ async function collectChanges(store, scope, since) {
2198
+ let changes;
2199
+ if (scope.kind === "full") {
2200
+ changes = since ? await store.getChangesSince(since.lamport) : await store.getAllChanges();
2201
+ } else {
2202
+ const nodeIds = await collectScopedNodeIds(store, scope);
2203
+ const byHash = /* @__PURE__ */ new Map();
2204
+ for (const nodeId of nodeIds) {
2205
+ for (const change of await store.getChanges(nodeId)) {
2206
+ if (!since || change.lamport > since.lamport) byHash.set(change.hash, change);
2207
+ }
2208
+ }
2209
+ changes = [...byHash.values()];
2210
+ }
2211
+ return changes.sort(
2212
+ (a, b) => compareChangeApplicationOrder(
2213
+ { lamport: a.lamport, author: a.authorDID },
2214
+ { lamport: b.lamport, author: b.authorDID }
2215
+ )
2216
+ );
2217
+ }
2218
+ async function writeBundle(store, scope, sink, options) {
2219
+ const entryDigests = /* @__PURE__ */ new Map();
2220
+ const changes = await collectChanges(store, scope, options.since);
2221
+ const changesDigest = createNdjsonDigest();
2222
+ const changeLines = [];
2223
+ for (const change of changes) {
2224
+ const line = encodeNdjsonLine(toPortableChangeRecord(change));
2225
+ changesDigest.addLine(line);
2226
+ changeLines.push(line);
2227
+ }
2228
+ await sink.writeEntry(
2229
+ BUNDLE_ENTRY.changes,
2230
+ encodeUtf8(changeLines.join("\n") + (changeLines.length ? "\n" : ""))
2231
+ );
2232
+ entryDigests.set(BUNDLE_ENTRY.changes, changesDigest.finish());
2233
+ let blobCount = 0;
2234
+ const blobIndexDigest = createNdjsonDigest();
2235
+ const blobIndexLines = [];
2236
+ if (options.blobPort) {
2237
+ for await (const blob of options.blobPort.list()) {
2238
+ const path = blobEntryPath(blob.cid);
2239
+ await sink.writeEntry(path, blob.bytes);
2240
+ entryDigests.set(path, digestEntryBytes(blob.bytes));
2241
+ const record = {
2242
+ cid: blob.cid,
2243
+ path,
2244
+ size: blob.bytes.byteLength,
2245
+ mimeType: blob.mimeType
2246
+ };
2247
+ const line = encodeNdjsonLine(record);
2248
+ blobIndexDigest.addLine(line);
2249
+ blobIndexLines.push(line);
2250
+ blobCount++;
2251
+ }
2252
+ }
2253
+ await sink.writeEntry(
2254
+ BUNDLE_ENTRY.blobIndex,
2255
+ encodeUtf8(blobIndexLines.join("\n") + (blobIndexLines.length ? "\n" : ""))
2256
+ );
2257
+ entryDigests.set(BUNDLE_ENTRY.blobIndex, blobIndexDigest.finish());
2258
+ let yjsCount = 0;
2259
+ const yjsDigest = createNdjsonDigest();
2260
+ const yjsLines = [];
2261
+ if (options.yjsPort) {
2262
+ for await (const doc of options.yjsPort.list()) {
2263
+ const line = encodeNdjsonLine({ nodeId: doc.nodeId, updateB64: bytesToBase642(doc.update) });
2264
+ yjsDigest.addLine(line);
2265
+ yjsLines.push(line);
2266
+ yjsCount++;
2267
+ }
2268
+ }
2269
+ await sink.writeEntry(
2270
+ BUNDLE_ENTRY.yjsDocs,
2271
+ encodeUtf8(yjsLines.join("\n") + (yjsLines.length ? "\n" : ""))
2272
+ );
2273
+ entryDigests.set(BUNDLE_ENTRY.yjsDocs, yjsDigest.finish());
2274
+ const manifest = {
2275
+ formatVersion: XNETPACK_FORMAT_VERSION,
2276
+ protocolVersion: { change: CURRENT_PROTOCOL_VERSION },
2277
+ ownerDid: options.ownerDid,
2278
+ scope,
2279
+ createdAt: Date.now(),
2280
+ frontier: frontierOf(changes),
2281
+ prerequisites: options.since,
2282
+ counts: { changes: changes.length, blobs: blobCount, yjsDocs: yjsCount },
2283
+ contentDigest: combineEntryDigests(entryDigests)
2284
+ };
2285
+ if (options.manifestSigner) {
2286
+ const signature = await options.manifestSigner(canonicalManifestBytes(manifest));
2287
+ manifest.signatureB64 = bytesToBase642(signature);
2288
+ }
2289
+ await sink.writeEntry(BUNDLE_ENTRY.manifest, encodeUtf8(JSON.stringify(manifest, null, 2)));
2290
+ return manifest;
2291
+ }
2292
+
2293
+ // src/portability/verify.ts
2294
+ import { verifyChange, verifyChangeHash, CURRENT_PROTOCOL_VERSION as CURRENT_PROTOCOL_VERSION2 } from "@xnetjs/sync";
2295
+ import { base64ToBytes as base64ToBytes2, verify as verifySignature } from "@xnetjs/crypto";
2296
+ import { parseDID } from "@xnetjs/identity";
2297
+ function parseCidHex(path) {
2298
+ const match = /^blobs\/([a-z0-9-]+)\/([0-9a-f]+)$/i.exec(path);
2299
+ if (!match || match[1] === "raw") return null;
2300
+ return { algo: match[1], hex: match[2] };
2301
+ }
2302
+ async function verifyBundle(source, options = {}) {
2303
+ const verifyChangeSignatures = options.verifyChangeSignatures ?? true;
2304
+ const issues = [];
2305
+ const error = (code, detail, subject) => issues.push({ severity: "error", code, detail, subject });
2306
+ const warning = (code, detail, subject) => issues.push({ severity: "warning", code, detail, subject });
2307
+ const manifestBytes = await source.readEntry(BUNDLE_ENTRY.manifest);
2308
+ if (!manifestBytes) {
2309
+ error("unknown-format", "bundle has no manifest.json");
2310
+ return { ok: false, manifest: null, issues, danglingParents: 0 };
2311
+ }
2312
+ let manifest;
2313
+ try {
2314
+ manifest = JSON.parse(decodeUtf8(manifestBytes));
2315
+ } catch (err) {
2316
+ error("unknown-format", `manifest.json is not valid JSON: ${err.message}`);
2317
+ return { ok: false, manifest: null, issues, danglingParents: 0 };
2318
+ }
2319
+ if (manifest.formatVersion !== XNETPACK_FORMAT_VERSION) {
2320
+ error("unknown-format", `unsupported formatVersion "${manifest.formatVersion}"`);
2321
+ return { ok: false, manifest, issues, danglingParents: 0 };
2322
+ }
2323
+ if ((manifest.protocolVersion?.change ?? 0) > CURRENT_PROTOCOL_VERSION2) {
2324
+ error(
2325
+ "future-protocol",
2326
+ `bundle uses change protocol v${manifest.protocolVersion.change}, this build supports v${CURRENT_PROTOCOL_VERSION2}`
2327
+ );
2328
+ }
2329
+ if (!manifest.signatureB64) {
2330
+ warning("manifest-unsigned", "manifest carries no signature");
2331
+ } else {
2332
+ try {
2333
+ const publicKey = parseDID(manifest.ownerDid);
2334
+ const signature = base64ToBytes2(manifest.signatureB64);
2335
+ if (!verifySignature(canonicalManifestBytes(manifest), signature, publicKey)) {
2336
+ error(
2337
+ "manifest-signature-invalid",
2338
+ `manifest signature does not verify against ${manifest.ownerDid}`
2339
+ );
2340
+ }
2341
+ } catch (err) {
2342
+ error(
2343
+ "manifest-signature-invalid",
2344
+ `cannot verify manifest signature: ${err.message}`
2345
+ );
2346
+ }
2347
+ }
2348
+ const entryDigests = /* @__PURE__ */ new Map();
2349
+ const changesDigest = createNdjsonDigest();
2350
+ const presentHashes = /* @__PURE__ */ new Set();
2351
+ const parentHashes = [];
2352
+ let lineNumber = 0;
2353
+ for await (const line of source.readLines(BUNDLE_ENTRY.changes)) {
2354
+ lineNumber++;
2355
+ changesDigest.addLine(line);
2356
+ let record;
2357
+ try {
2358
+ record = JSON.parse(line);
2359
+ } catch {
2360
+ error(
2361
+ "change-unparseable",
2362
+ `changes.ndjson line ${lineNumber} is not valid JSON`,
2363
+ `line:${lineNumber}`
2364
+ );
2365
+ continue;
2366
+ }
2367
+ let change;
2368
+ try {
2369
+ change = fromPortableChangeRecord(record);
2370
+ } catch (err) {
2371
+ error("change-unparseable", `line ${lineNumber}: ${err.message}`, record.hash);
2372
+ continue;
2373
+ }
2374
+ if (verifyChangeSignatures) {
2375
+ if (!verifyChangeHash(change)) {
2376
+ error(
2377
+ "change-hash-invalid",
2378
+ `change ${change.id} fails hash re-computation (tampered?)`,
2379
+ record.hash
2380
+ );
2381
+ continue;
2382
+ }
2383
+ try {
2384
+ const publicKey = parseDID(change.authorDID);
2385
+ if (!verifyChange(change, publicKey)) {
2386
+ error(
2387
+ "change-signature-invalid",
2388
+ `change ${change.id} signature does not match ${change.authorDID}`,
2389
+ record.hash
2390
+ );
2391
+ continue;
2392
+ }
2393
+ } catch (err) {
2394
+ error(
2395
+ "change-signature-invalid",
2396
+ `change ${change.id}: ${err.message}`,
2397
+ record.hash
2398
+ );
2399
+ continue;
2400
+ }
2401
+ }
2402
+ presentHashes.add(change.hash);
2403
+ parentHashes.push(change.parentHash);
2404
+ }
2405
+ entryDigests.set(BUNDLE_ENTRY.changes, changesDigest.finish());
2406
+ if (changesDigest.lineCount() !== manifest.counts.changes) {
2407
+ error(
2408
+ "count-mismatch",
2409
+ `manifest declares ${manifest.counts.changes} changes, bundle has ${changesDigest.lineCount()}`
2410
+ );
2411
+ }
2412
+ const prerequisiteHeads = new Set(manifest.prerequisites?.heads ?? []);
2413
+ let danglingParents = 0;
2414
+ for (const parent of parentHashes) {
2415
+ if (parent !== null && !presentHashes.has(parent) && !prerequisiteHeads.has(parent)) {
2416
+ danglingParents++;
2417
+ }
2418
+ }
2419
+ if (danglingParents > 0) {
2420
+ warning(
2421
+ "dangling-parent",
2422
+ `${danglingParents} change(s) reference parents outside the bundle (expected for scoped/incremental exports)`
2423
+ );
2424
+ }
2425
+ const blobIndexDigest = createNdjsonDigest();
2426
+ const indexedPaths = /* @__PURE__ */ new Set();
2427
+ for await (const line of source.readLines(BUNDLE_ENTRY.blobIndex)) {
2428
+ blobIndexDigest.addLine(line);
2429
+ try {
2430
+ const record = JSON.parse(line);
2431
+ indexedPaths.add(record.path);
2432
+ } catch {
2433
+ error("change-unparseable", "blobs.ndjson contains an unparseable line");
2434
+ }
2435
+ }
2436
+ entryDigests.set(BUNDLE_ENTRY.blobIndex, blobIndexDigest.finish());
2437
+ if (blobIndexDigest.lineCount() !== manifest.counts.blobs) {
2438
+ error(
2439
+ "count-mismatch",
2440
+ `manifest declares ${manifest.counts.blobs} blobs, index has ${blobIndexDigest.lineCount()}`
2441
+ );
2442
+ }
2443
+ for (const path of await source.listEntries("blobs/")) {
2444
+ if (path === BUNDLE_ENTRY.blobIndex) continue;
2445
+ const bytes = await source.readEntry(path);
2446
+ if (!bytes) continue;
2447
+ entryDigests.set(path, digestEntryBytes(bytes));
2448
+ const cid = parseCidHex(path);
2449
+ if (cid && cid.algo === "blake3" && digestEntryBytes(bytes) !== cid.hex.toLowerCase()) {
2450
+ error("blob-digest-mismatch", `blob ${path} bytes do not hash to their filename`, path);
2451
+ }
2452
+ if (!indexedPaths.has(path)) {
2453
+ warning("count-mismatch", `blob entry ${path} is not listed in blobs.ndjson`, path);
2454
+ }
2455
+ }
2456
+ const yjsDigest = createNdjsonDigest();
2457
+ for await (const line of source.readLines(BUNDLE_ENTRY.yjsDocs)) {
2458
+ yjsDigest.addLine(line);
2459
+ }
2460
+ entryDigests.set(BUNDLE_ENTRY.yjsDocs, yjsDigest.finish());
2461
+ if (yjsDigest.lineCount() !== manifest.counts.yjsDocs) {
2462
+ error(
2463
+ "count-mismatch",
2464
+ `manifest declares ${manifest.counts.yjsDocs} yjs docs, bundle has ${yjsDigest.lineCount()}`
2465
+ );
2466
+ }
2467
+ if (combineEntryDigests(entryDigests) !== manifest.contentDigest) {
2468
+ error("content-digest-mismatch", "entry digests do not match manifest.contentDigest");
2469
+ }
2470
+ return {
2471
+ ok: !issues.some((i) => i.severity === "error"),
2472
+ manifest,
2473
+ issues,
2474
+ danglingParents
2475
+ };
2476
+ }
2477
+
2478
+ // src/portability/apply.ts
2479
+ import { compareChangeApplicationOrder as compareChangeApplicationOrder2 } from "@xnetjs/core";
2480
+ import { base64ToBytes as base64ToBytes3 } from "@xnetjs/crypto";
2481
+ var BundleImportError = class extends Error {
2482
+ constructor(message, code) {
2483
+ super(message);
2484
+ this.code = code;
2485
+ this.name = "BundleImportError";
2486
+ }
2487
+ };
2488
+ var APPLY_BATCH_SIZE = 500;
2489
+ async function applyBundle(store, source, options) {
2490
+ const verifyReport = await verifyBundle(source, { verifyChangeSignatures: false });
2491
+ if (!verifyReport.ok || !verifyReport.manifest) {
2492
+ const details = verifyReport.issues.filter((i) => i.severity === "error").map((i) => i.detail).join("; ");
2493
+ throw new BundleImportError(`bundle failed verification: ${details}`, "verify-failed");
2494
+ }
2495
+ const manifest = verifyReport.manifest;
2496
+ if (!manifest.signatureB64 && !options.allowUnsigned) {
2497
+ throw new BundleImportError(
2498
+ "bundle manifest is unsigned \u2014 pass allowUnsigned to import anyway",
2499
+ "unsigned-manifest"
2500
+ );
2501
+ }
2502
+ if (manifest.ownerDid !== options.importerDid && !options.allowForeignOwner) {
2503
+ throw new BundleImportError(
2504
+ `bundle is owned by ${manifest.ownerDid} but the importer is ${options.importerDid} \u2014 pass allowForeignOwner to import a bundle from another identity`,
2505
+ "foreign-owner"
2506
+ );
2507
+ }
2508
+ const missingPrerequisites = [];
2509
+ if (manifest.prerequisites) {
2510
+ for (const head of manifest.prerequisites.heads) {
2511
+ if (!await store.hasChange(head)) missingPrerequisites.push(head);
2512
+ }
2513
+ if (missingPrerequisites.length > 0 && !options.ignoreMissingPrerequisites) {
2514
+ throw new BundleImportError(
2515
+ `store is missing ${missingPrerequisites.length} prerequisite head(s) \u2014 this incremental bundle needs its base bundle imported first (or pass ignoreMissingPrerequisites)`,
2516
+ "missing-prerequisites"
2517
+ );
2518
+ }
2519
+ }
2520
+ const quarantined = [];
2521
+ const quarantine = (record) => {
2522
+ quarantined.push(record);
2523
+ options.onQuarantine?.(record);
2524
+ };
2525
+ const changes = [];
2526
+ for await (const line of source.readLines(BUNDLE_ENTRY.changes)) {
2527
+ try {
2528
+ changes.push(fromPortableChangeRecord(JSON.parse(line)));
2529
+ } catch (err) {
2530
+ quarantine({ kind: "change", subject: line.slice(0, 80), reason: err.message });
2531
+ }
2532
+ }
2533
+ changes.sort(
2534
+ (a, b) => compareChangeApplicationOrder2(
2535
+ { lamport: a.lamport, author: a.authorDID },
2536
+ { lamport: b.lamport, author: b.authorDID }
2537
+ )
2538
+ );
2539
+ let applied = 0;
2540
+ let duplicates = 0;
2541
+ for (let i = 0; i < changes.length; i += APPLY_BATCH_SIZE) {
2542
+ for (const change of changes.slice(i, i + APPLY_BATCH_SIZE)) {
2543
+ try {
2544
+ if (await store.hasChange(change.hash)) {
2545
+ duplicates++;
2546
+ continue;
2547
+ }
2548
+ await store.applyRemoteChange(change);
2549
+ applied++;
2550
+ } catch (err) {
2551
+ quarantine({
2552
+ kind: "change",
2553
+ subject: change.hash,
2554
+ reason: err instanceof Error ? err.message : String(err)
2555
+ });
2556
+ }
2557
+ }
2558
+ }
2559
+ let blobsInstalled = 0;
2560
+ if (options.blobPort) {
2561
+ for await (const line of source.readLines(BUNDLE_ENTRY.blobIndex)) {
2562
+ let record;
2563
+ try {
2564
+ record = JSON.parse(line);
2565
+ } catch (err) {
2566
+ quarantine({ kind: "blob", subject: line.slice(0, 80), reason: err.message });
2567
+ continue;
2568
+ }
2569
+ try {
2570
+ if (await options.blobPort.has(record.cid)) continue;
2571
+ const bytes = await source.readEntry(record.path);
2572
+ if (!bytes) {
2573
+ quarantine({
2574
+ kind: "blob",
2575
+ subject: record.cid,
2576
+ reason: `bundle entry ${record.path} missing`
2577
+ });
2578
+ continue;
2579
+ }
2580
+ await options.blobPort.put(bytes, { cid: record.cid, mimeType: record.mimeType });
2581
+ blobsInstalled++;
2582
+ } catch (err) {
2583
+ quarantine({ kind: "blob", subject: record.cid, reason: err.message });
2584
+ }
2585
+ }
2586
+ }
2587
+ let yjsDocsApplied = 0;
2588
+ if (options.yjsPort) {
2589
+ for await (const line of source.readLines(BUNDLE_ENTRY.yjsDocs)) {
2590
+ let record;
2591
+ try {
2592
+ record = JSON.parse(line);
2593
+ } catch (err) {
2594
+ quarantine({ kind: "yjs-doc", subject: line.slice(0, 80), reason: err.message });
2595
+ continue;
2596
+ }
2597
+ try {
2598
+ await options.yjsPort.apply(record.nodeId, base64ToBytes3(record.updateB64));
2599
+ yjsDocsApplied++;
2600
+ } catch (err) {
2601
+ quarantine({ kind: "yjs-doc", subject: record.nodeId, reason: err.message });
2602
+ }
2603
+ }
2604
+ }
2605
+ return { applied, duplicates, quarantined, blobsInstalled, yjsDocsApplied, missingPrerequisites };
2606
+ }
2607
+ async function readBundleManifest(source) {
2608
+ const bytes = await source.readEntry(BUNDLE_ENTRY.manifest);
2609
+ if (!bytes) return null;
2610
+ try {
2611
+ return JSON.parse(decodeUtf8(bytes));
2612
+ } catch {
2613
+ return null;
2614
+ }
2615
+ }
2616
+
2617
+ // src/portability/memory-bundle.ts
2618
+ var MemoryBundleSink = class {
2619
+ entries = /* @__PURE__ */ new Map();
2620
+ writeEntry(path, data) {
2621
+ this.entries.set(path, data);
2622
+ }
2623
+ toSource() {
2624
+ return new MemoryBundleSource(this.entries);
2625
+ }
2626
+ };
2627
+ var MemoryBundleSource = class {
2628
+ constructor(entries) {
2629
+ this.entries = entries;
2630
+ }
2631
+ async readEntry(path) {
2632
+ return this.entries.get(path) ?? null;
2633
+ }
2634
+ async *readLines(path) {
2635
+ const bytes = this.entries.get(path);
2636
+ if (!bytes) return;
2637
+ let start = 0;
2638
+ const text2 = decodeUtf8(bytes);
2639
+ while (start < text2.length) {
2640
+ let end = text2.indexOf("\n", start);
2641
+ if (end === -1) end = text2.length;
2642
+ const line = text2.slice(start, end);
2643
+ if (line.length > 0) yield line;
2644
+ start = end + 1;
2645
+ }
2646
+ }
2647
+ async listEntries(prefix) {
2648
+ return [...this.entries.keys()].filter((p) => p.startsWith(prefix)).sort();
2649
+ }
2650
+ };
2651
+
2652
+ // src/portability/store-yjs-port.ts
2653
+ import * as Y2 from "yjs";
2654
+ function createStoreYjsPort(store) {
2655
+ return {
2656
+ async *list() {
2657
+ const nodes = await store.list({ includeDeleted: true });
2658
+ for (const node of nodes) {
2659
+ const content = await store.getDocumentContent(node.id);
2660
+ if (content && content.byteLength > 0) {
2661
+ yield { nodeId: node.id, update: content };
2662
+ }
2663
+ }
2664
+ },
2665
+ async apply(nodeId, update) {
2666
+ const existing = await store.getDocumentContent(nodeId);
2667
+ if (!existing || existing.byteLength === 0) {
2668
+ await store.setDocumentContent(nodeId, update);
2669
+ return;
2670
+ }
2671
+ let merged;
2672
+ const doc = new Y2.Doc();
2673
+ try {
2674
+ Y2.applyUpdate(doc, existing);
2675
+ Y2.applyUpdate(doc, update);
2676
+ merged = Y2.encodeStateAsUpdate(doc);
2677
+ } catch (err) {
2678
+ throw new Error(
2679
+ `document content for ${nodeId} is not a mergeable Yjs update (${err.message}); left the existing content untouched`
2680
+ );
2681
+ } finally {
2682
+ doc.destroy();
2683
+ }
2684
+ await store.setDocumentContent(nodeId, merged);
2685
+ }
2686
+ };
2687
+ }
2688
+
2052
2689
  // src/index.ts
2053
2690
  import {
2054
- Doc,
2691
+ Doc as Doc2,
2055
2692
  Map as Map3,
2056
2693
  Array as Array2,
2057
2694
  Text,
@@ -2092,8 +2729,10 @@ export {
2092
2729
  AgentSessionSchema,
2093
2730
  AppealSchema,
2094
2731
  BUDGET_SCHEMA_IRI,
2732
+ BUNDLE_ENTRY,
2095
2733
  BlobService,
2096
2734
  BudgetSchema,
2735
+ BundleImportError,
2097
2736
  CELL_PREFIX,
2098
2737
  CHANNEL_KINDS,
2099
2738
  CHECKPOINT_SCHEMA_IRI,
@@ -2196,6 +2835,8 @@ export {
2196
2835
  MediaAssetSchema,
2197
2836
  MeetingSchema,
2198
2837
  MeetingTranscriptSchema,
2838
+ MemoryBundleSink,
2839
+ MemoryBundleSource,
2199
2840
  MemoryItemSchema,
2200
2841
  MemoryNodeStorageAdapter,
2201
2842
  MessageRequestSchema,
@@ -2284,8 +2925,9 @@ export {
2284
2925
  TranscriptionSchema,
2285
2926
  UserWidgetSchema,
2286
2927
  WorkspaceSchema,
2928
+ XNETPACK_FORMAT_VERSION,
2287
2929
  Array2 as YArray,
2288
- Doc as YDoc,
2930
+ Doc2 as YDoc,
2289
2931
  Map3 as YMap,
2290
2932
  Text as YText,
2291
2933
  XmlElement as YXmlElement,
@@ -2306,6 +2948,7 @@ export {
2306
2948
  agentSessionId,
2307
2949
  aggregate,
2308
2950
  and,
2951
+ applyBundle,
2309
2952
  applyNodeQueryDescriptor,
2310
2953
  applySignedUpdate,
2311
2954
  asRowHeight,
@@ -2313,6 +2956,7 @@ export {
2313
2956
  avg,
2314
2957
  batchComputeRollups,
2315
2958
  between,
2959
+ blobEntryPath,
2316
2960
  bucketPresenceCount,
2317
2961
  buildDatabaseSchema,
2318
2962
  buildEffectiveSchema,
@@ -2328,6 +2972,7 @@ export {
2328
2972
  bumpSchemaVersion,
2329
2973
  canManageSpace,
2330
2974
  canModifyColumn,
2975
+ canonicalManifestBytes,
2331
2976
  captureUpdate,
2332
2977
  cellKey,
2333
2978
  cellValueToText,
@@ -2383,6 +3028,7 @@ export {
2383
3028
  createSelectOption,
2384
3029
  createSort,
2385
3030
  createSortQuery,
3031
+ createStoreYjsPort,
2386
3032
  createVersionEntry,
2387
3033
  createView2 as createView,
2388
3034
  createView as createViewNode,
@@ -2458,6 +3104,7 @@ export {
2458
3104
  formatValue,
2459
3105
  from,
2460
3106
  fromCellProperties,
3107
+ fromPortableChangeRecord,
2461
3108
  gameSchemas,
2462
3109
  generateColumnIdMap,
2463
3110
  generateSortKey,
@@ -2626,6 +3273,7 @@ export {
2626
3273
  queryOperators,
2627
3274
  queryRows,
2628
3275
  querySetCount,
3276
+ readBundleManifest,
2629
3277
  rebalanceDatabase,
2630
3278
  rebalanceSortKeys,
2631
3279
  recoveryRecordId,
@@ -2697,6 +3345,7 @@ export {
2697
3345
  toCellProperties,
2698
3346
  toColumnDefinitions,
2699
3347
  toFieldNode,
3348
+ toPortableChangeRecord,
2700
3349
  toSelectOptionNode,
2701
3350
  toggleGroupCollapsed,
2702
3351
  toggleGroupCollapsed2 as toggleGroupCollapsedState,
@@ -2719,11 +3368,13 @@ export {
2719
3368
  validateRollupConfig,
2720
3369
  validateSavedViewDescriptor,
2721
3370
  validateSchemaDefinitionNode,
3371
+ verifyBundle,
2722
3372
  verifyUpdate,
2723
3373
  visibleFormQuestions,
2724
3374
  when,
2725
3375
  withoutNodeQueryMaterializedView,
2726
3376
  withoutNodeQueryPagination,
2727
3377
  wouldCreateFolderCycle,
2728
- wouldCreateSpaceCycle
3378
+ wouldCreateSpaceCycle,
3379
+ writeBundle
2729
3380
  };