@panproto/core 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/dist/index.cjs ADDED
@@ -0,0 +1,945 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const msgpack = require("@msgpack/msgpack");
4
+ var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
5
+ class PanprotoError extends Error {
6
+ name = "PanprotoError";
7
+ constructor(message, options) {
8
+ super(message, options);
9
+ }
10
+ }
11
+ class WasmError extends PanprotoError {
12
+ name = "WasmError";
13
+ }
14
+ class SchemaValidationError extends PanprotoError {
15
+ constructor(message, errors) {
16
+ super(message);
17
+ this.errors = errors;
18
+ }
19
+ name = "SchemaValidationError";
20
+ }
21
+ class MigrationError extends PanprotoError {
22
+ name = "MigrationError";
23
+ }
24
+ class ExistenceCheckError extends PanprotoError {
25
+ constructor(message, report) {
26
+ super(message);
27
+ this.report = report;
28
+ }
29
+ name = "ExistenceCheckError";
30
+ }
31
+ const DEFAULT_WASM_URL = new URL("./panproto_wasm_bg.wasm", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
32
+ async function loadWasm(url) {
33
+ const wasmUrl = url ?? DEFAULT_WASM_URL;
34
+ try {
35
+ const response = typeof wasmUrl === "string" ? await fetch(wasmUrl) : await fetch(wasmUrl);
36
+ const { instance } = await WebAssembly.instantiateStreaming(response);
37
+ const exports$1 = instance.exports;
38
+ const memory = instance.exports["memory"];
39
+ if (!memory) {
40
+ throw new WasmError("WASM module missing memory export");
41
+ }
42
+ return { exports: exports$1, memory };
43
+ } catch (error) {
44
+ if (error instanceof WasmError) throw error;
45
+ throw new WasmError(
46
+ `Failed to load WASM module from ${String(wasmUrl)}`,
47
+ { cause: error }
48
+ );
49
+ }
50
+ }
51
+ const leakedHandleRegistry = new FinalizationRegistry((info) => {
52
+ try {
53
+ info.freeHandle(info.handle);
54
+ } catch {
55
+ }
56
+ });
57
+ class WasmHandle {
58
+ #handle;
59
+ #disposed = false;
60
+ #freeHandle;
61
+ constructor(handle, freeHandle) {
62
+ this.#handle = handle;
63
+ this.#freeHandle = freeHandle;
64
+ leakedHandleRegistry.register(this, { handle, freeHandle }, this);
65
+ }
66
+ /** The raw WASM handle id. Only for internal use within the SDK. */
67
+ get id() {
68
+ if (this.#disposed) {
69
+ throw new WasmError("Attempted to use a disposed handle");
70
+ }
71
+ return this.#handle;
72
+ }
73
+ /** Whether this handle has been disposed. */
74
+ get disposed() {
75
+ return this.#disposed;
76
+ }
77
+ /** Release the underlying WASM resource. */
78
+ [Symbol.dispose]() {
79
+ if (this.#disposed) return;
80
+ this.#disposed = true;
81
+ leakedHandleRegistry.unregister(this);
82
+ try {
83
+ this.#freeHandle(this.#handle);
84
+ } catch {
85
+ }
86
+ }
87
+ }
88
+ function createHandle(rawHandle, wasm) {
89
+ return new WasmHandle(rawHandle, (h) => wasm.exports.free_handle(h));
90
+ }
91
+ function packToWasm(value) {
92
+ return msgpack.encode(value);
93
+ }
94
+ function unpackFromWasm(bytes) {
95
+ return msgpack.decode(bytes);
96
+ }
97
+ function packSchemaOps(ops) {
98
+ return msgpack.encode(ops);
99
+ }
100
+ function packMigrationMapping(mapping) {
101
+ return msgpack.encode(mapping);
102
+ }
103
+ class SchemaBuilder {
104
+ #protocolName;
105
+ #protocolHandle;
106
+ #wasm;
107
+ #ops;
108
+ #vertices;
109
+ #edges;
110
+ #hyperEdges;
111
+ #constraints;
112
+ #required;
113
+ constructor(protocolName, protocolHandle, wasm, ops = [], vertices = /* @__PURE__ */ new Map(), edges = [], hyperEdges = /* @__PURE__ */ new Map(), constraints = /* @__PURE__ */ new Map(), required = /* @__PURE__ */ new Map()) {
114
+ this.#protocolName = protocolName;
115
+ this.#protocolHandle = protocolHandle;
116
+ this.#wasm = wasm;
117
+ this.#ops = ops;
118
+ this.#vertices = vertices;
119
+ this.#edges = edges;
120
+ this.#hyperEdges = hyperEdges;
121
+ this.#constraints = constraints;
122
+ this.#required = required;
123
+ }
124
+ /**
125
+ * Add a vertex to the schema.
126
+ *
127
+ * @param id - Unique vertex identifier
128
+ * @param kind - Vertex kind (e.g., 'record', 'object', 'string')
129
+ * @param options - Optional vertex configuration (nsid, etc.)
130
+ * @returns A new builder with the vertex added
131
+ * @throws {@link SchemaValidationError} if vertex id is already in use
132
+ */
133
+ vertex(id, kind, options) {
134
+ if (this.#vertices.has(id)) {
135
+ throw new SchemaValidationError(
136
+ `Vertex "${id}" already exists in schema`,
137
+ [`Duplicate vertex id: ${id}`]
138
+ );
139
+ }
140
+ const vertex = {
141
+ id,
142
+ kind,
143
+ nsid: options?.nsid
144
+ };
145
+ const op = {
146
+ op: "vertex",
147
+ id,
148
+ kind,
149
+ nsid: options?.nsid ?? null
150
+ };
151
+ const newVertices = new Map(this.#vertices);
152
+ newVertices.set(id, vertex);
153
+ return new SchemaBuilder(
154
+ this.#protocolName,
155
+ this.#protocolHandle,
156
+ this.#wasm,
157
+ [...this.#ops, op],
158
+ newVertices,
159
+ this.#edges,
160
+ this.#hyperEdges,
161
+ this.#constraints,
162
+ this.#required
163
+ );
164
+ }
165
+ /**
166
+ * Add a directed edge to the schema.
167
+ *
168
+ * @param src - Source vertex id
169
+ * @param tgt - Target vertex id
170
+ * @param kind - Edge kind (e.g., 'record-schema', 'prop')
171
+ * @param options - Optional edge configuration (name, etc.)
172
+ * @returns A new builder with the edge added
173
+ * @throws {@link SchemaValidationError} if source or target vertex does not exist
174
+ */
175
+ edge(src, tgt, kind, options) {
176
+ if (!this.#vertices.has(src)) {
177
+ throw new SchemaValidationError(
178
+ `Edge source "${src}" does not exist`,
179
+ [`Unknown source vertex: ${src}`]
180
+ );
181
+ }
182
+ if (!this.#vertices.has(tgt)) {
183
+ throw new SchemaValidationError(
184
+ `Edge target "${tgt}" does not exist`,
185
+ [`Unknown target vertex: ${tgt}`]
186
+ );
187
+ }
188
+ const edge = {
189
+ src,
190
+ tgt,
191
+ kind,
192
+ name: options?.name
193
+ };
194
+ const op = {
195
+ op: "edge",
196
+ src,
197
+ tgt,
198
+ kind,
199
+ name: options?.name ?? null
200
+ };
201
+ return new SchemaBuilder(
202
+ this.#protocolName,
203
+ this.#protocolHandle,
204
+ this.#wasm,
205
+ [...this.#ops, op],
206
+ this.#vertices,
207
+ [...this.#edges, edge],
208
+ this.#hyperEdges,
209
+ this.#constraints,
210
+ this.#required
211
+ );
212
+ }
213
+ /**
214
+ * Add a hyperedge to the schema.
215
+ *
216
+ * Only valid if the protocol's schema theory includes ThHypergraph.
217
+ *
218
+ * @param id - Unique hyperedge identifier
219
+ * @param kind - Hyperedge kind
220
+ * @param signature - Label-to-vertex mapping
221
+ * @param parentLabel - The label identifying the parent in the signature
222
+ * @returns A new builder with the hyperedge added
223
+ */
224
+ hyperEdge(id, kind, signature, parentLabel) {
225
+ const he = { id, kind, signature, parentLabel };
226
+ const op = {
227
+ op: "hyper_edge",
228
+ id,
229
+ kind,
230
+ signature,
231
+ parent: parentLabel
232
+ };
233
+ const newHyperEdges = new Map(this.#hyperEdges);
234
+ newHyperEdges.set(id, he);
235
+ return new SchemaBuilder(
236
+ this.#protocolName,
237
+ this.#protocolHandle,
238
+ this.#wasm,
239
+ [...this.#ops, op],
240
+ this.#vertices,
241
+ this.#edges,
242
+ newHyperEdges,
243
+ this.#constraints,
244
+ this.#required
245
+ );
246
+ }
247
+ /**
248
+ * Add a constraint to a vertex.
249
+ *
250
+ * @param vertexId - The vertex to constrain
251
+ * @param sort - Constraint sort (e.g., 'maxLength')
252
+ * @param value - Constraint value
253
+ * @returns A new builder with the constraint added
254
+ */
255
+ constraint(vertexId, sort, value) {
256
+ const c = { sort, value };
257
+ const op = {
258
+ op: "constraint",
259
+ vertex: vertexId,
260
+ sort,
261
+ value
262
+ };
263
+ const existing = this.#constraints.get(vertexId) ?? [];
264
+ const newConstraints = new Map(this.#constraints);
265
+ newConstraints.set(vertexId, [...existing, c]);
266
+ return new SchemaBuilder(
267
+ this.#protocolName,
268
+ this.#protocolHandle,
269
+ this.#wasm,
270
+ [...this.#ops, op],
271
+ this.#vertices,
272
+ this.#edges,
273
+ this.#hyperEdges,
274
+ newConstraints,
275
+ this.#required
276
+ );
277
+ }
278
+ /**
279
+ * Mark edges as required for a vertex.
280
+ *
281
+ * @param vertexId - The vertex with required edges
282
+ * @param edges - The edges that are required
283
+ * @returns A new builder with the requirement added
284
+ */
285
+ required(vertexId, edges) {
286
+ const op = {
287
+ op: "required",
288
+ vertex: vertexId,
289
+ edges: edges.map((e) => ({
290
+ src: e.src,
291
+ tgt: e.tgt,
292
+ kind: e.kind,
293
+ name: e.name ?? null
294
+ }))
295
+ };
296
+ const existing = this.#required.get(vertexId) ?? [];
297
+ const newRequired = new Map(this.#required);
298
+ newRequired.set(vertexId, [...existing, ...edges]);
299
+ return new SchemaBuilder(
300
+ this.#protocolName,
301
+ this.#protocolHandle,
302
+ this.#wasm,
303
+ [...this.#ops, op],
304
+ this.#vertices,
305
+ this.#edges,
306
+ this.#hyperEdges,
307
+ this.#constraints,
308
+ newRequired
309
+ );
310
+ }
311
+ /**
312
+ * Validate and build the schema.
313
+ *
314
+ * Sends all accumulated operations to WASM for validation and construction.
315
+ * Returns a `BuiltSchema` that holds the WASM handle and local data.
316
+ *
317
+ * @returns The validated, built schema
318
+ * @throws {@link SchemaValidationError} if the schema is invalid
319
+ */
320
+ build() {
321
+ const opsBytes = packSchemaOps([...this.#ops]);
322
+ const rawHandle = this.#wasm.exports.build_schema(
323
+ this.#protocolHandle.id,
324
+ opsBytes
325
+ );
326
+ const handle = createHandle(rawHandle, this.#wasm);
327
+ const data = {
328
+ protocol: this.#protocolName,
329
+ vertices: Object.fromEntries(this.#vertices),
330
+ edges: [...this.#edges],
331
+ hyperEdges: Object.fromEntries(this.#hyperEdges),
332
+ constraints: Object.fromEntries(
333
+ Array.from(this.#constraints.entries()).map(([k, v]) => [k, [...v]])
334
+ ),
335
+ required: Object.fromEntries(
336
+ Array.from(this.#required.entries()).map(([k, v]) => [k, [...v]])
337
+ )
338
+ };
339
+ return new BuiltSchema(handle, data, this.#wasm);
340
+ }
341
+ }
342
+ class BuiltSchema {
343
+ #handle;
344
+ #data;
345
+ #wasm;
346
+ constructor(handle, data, wasm) {
347
+ this.#handle = handle;
348
+ this.#data = data;
349
+ this.#wasm = wasm;
350
+ }
351
+ /** The WASM handle for this schema. Internal use only. */
352
+ get _handle() {
353
+ return this.#handle;
354
+ }
355
+ /** The WASM module reference. Internal use only. */
356
+ get _wasm() {
357
+ return this.#wasm;
358
+ }
359
+ /** The schema data (vertices, edges, constraints, etc.). */
360
+ get data() {
361
+ return this.#data;
362
+ }
363
+ /** The protocol name this schema belongs to. */
364
+ get protocol() {
365
+ return this.#data.protocol;
366
+ }
367
+ /** All vertices in the schema. */
368
+ get vertices() {
369
+ return this.#data.vertices;
370
+ }
371
+ /** All edges in the schema. */
372
+ get edges() {
373
+ return this.#data.edges;
374
+ }
375
+ /** Release the WASM-side schema resource. */
376
+ [Symbol.dispose]() {
377
+ this.#handle[Symbol.dispose]();
378
+ }
379
+ }
380
+ class Protocol {
381
+ #handle;
382
+ #spec;
383
+ #wasm;
384
+ constructor(handle, spec, wasm) {
385
+ this.#handle = handle;
386
+ this.#spec = spec;
387
+ this.#wasm = wasm;
388
+ }
389
+ /** The protocol name. */
390
+ get name() {
391
+ return this.#spec.name;
392
+ }
393
+ /** The full protocol specification. */
394
+ get spec() {
395
+ return this.#spec;
396
+ }
397
+ /** The WASM handle. Internal use only. */
398
+ get _handle() {
399
+ return this.#handle;
400
+ }
401
+ /**
402
+ * Start building a schema within this protocol.
403
+ *
404
+ * @returns A new `SchemaBuilder` bound to this protocol
405
+ */
406
+ schema() {
407
+ return new SchemaBuilder(this.#spec.name, this.#handle, this.#wasm);
408
+ }
409
+ /** Release the WASM-side protocol resource. */
410
+ [Symbol.dispose]() {
411
+ this.#handle[Symbol.dispose]();
412
+ }
413
+ }
414
+ function defineProtocol(spec, wasm) {
415
+ const wireSpec = {
416
+ name: spec.name,
417
+ schema_theory: spec.schemaTheory,
418
+ instance_theory: spec.instanceTheory,
419
+ edge_rules: spec.edgeRules.map((r) => ({
420
+ edge_kind: r.edgeKind,
421
+ src_kinds: [...r.srcKinds],
422
+ tgt_kinds: [...r.tgtKinds]
423
+ })),
424
+ obj_kinds: [...spec.objKinds],
425
+ constraint_sorts: [...spec.constraintSorts]
426
+ };
427
+ try {
428
+ const bytes = packToWasm(wireSpec);
429
+ const rawHandle = wasm.exports.define_protocol(bytes);
430
+ const handle = createHandle(rawHandle, wasm);
431
+ return new Protocol(handle, spec, wasm);
432
+ } catch (error) {
433
+ throw new PanprotoError(
434
+ `Failed to define protocol "${spec.name}": ${error instanceof Error ? error.message : String(error)}`,
435
+ { cause: error }
436
+ );
437
+ }
438
+ }
439
+ const ATPROTO_SPEC = {
440
+ name: "atproto",
441
+ schemaTheory: "ThConstrainedMultiGraph",
442
+ instanceTheory: "ThWTypeMeta",
443
+ edgeRules: [
444
+ { edgeKind: "record-schema", srcKinds: ["record"], tgtKinds: ["object"] },
445
+ { edgeKind: "prop", srcKinds: ["object"], tgtKinds: [] },
446
+ { edgeKind: "item", srcKinds: ["array"], tgtKinds: [] },
447
+ { edgeKind: "variant", srcKinds: ["union"], tgtKinds: [] },
448
+ { edgeKind: "ref", srcKinds: [], tgtKinds: ["record"] }
449
+ ],
450
+ objKinds: ["record", "object"],
451
+ constraintSorts: ["maxLength", "minLength", "maxGraphemes", "minGraphemes", "format"]
452
+ };
453
+ const SQL_SPEC = {
454
+ name: "sql",
455
+ schemaTheory: "ThConstrainedHypergraph",
456
+ instanceTheory: "ThFunctor",
457
+ edgeRules: [
458
+ { edgeKind: "column", srcKinds: ["table"], tgtKinds: ["type"] },
459
+ { edgeKind: "fk", srcKinds: ["table"], tgtKinds: ["table"] },
460
+ { edgeKind: "pk", srcKinds: ["table"], tgtKinds: ["column"] }
461
+ ],
462
+ objKinds: ["table"],
463
+ constraintSorts: ["nullable", "unique", "check", "default"]
464
+ };
465
+ const PROTOBUF_SPEC = {
466
+ name: "protobuf",
467
+ schemaTheory: "ThConstrainedGraph",
468
+ instanceTheory: "ThWType",
469
+ edgeRules: [
470
+ { edgeKind: "field", srcKinds: ["message"], tgtKinds: [] },
471
+ { edgeKind: "nested", srcKinds: ["message"], tgtKinds: ["message", "enum"] },
472
+ { edgeKind: "value", srcKinds: ["enum"], tgtKinds: ["enum-value"] }
473
+ ],
474
+ objKinds: ["message"],
475
+ constraintSorts: ["field-number", "repeated", "optional", "map-key", "map-value"]
476
+ };
477
+ const GRAPHQL_SPEC = {
478
+ name: "graphql",
479
+ schemaTheory: "ThConstrainedGraph",
480
+ instanceTheory: "ThWType",
481
+ edgeRules: [
482
+ { edgeKind: "field", srcKinds: ["type", "input"], tgtKinds: [] },
483
+ { edgeKind: "implements", srcKinds: ["type"], tgtKinds: ["interface"] },
484
+ { edgeKind: "member", srcKinds: ["union"], tgtKinds: ["type"] },
485
+ { edgeKind: "value", srcKinds: ["enum"], tgtKinds: ["enum-value"] }
486
+ ],
487
+ objKinds: ["type", "input"],
488
+ constraintSorts: ["non-null", "list", "deprecated"]
489
+ };
490
+ const JSON_SCHEMA_SPEC = {
491
+ name: "json-schema",
492
+ schemaTheory: "ThConstrainedGraph",
493
+ instanceTheory: "ThWType",
494
+ edgeRules: [
495
+ { edgeKind: "property", srcKinds: ["object"], tgtKinds: [] },
496
+ { edgeKind: "item", srcKinds: ["array"], tgtKinds: [] },
497
+ { edgeKind: "variant", srcKinds: ["oneOf", "anyOf"], tgtKinds: [] }
498
+ ],
499
+ objKinds: ["object"],
500
+ constraintSorts: ["minLength", "maxLength", "minimum", "maximum", "pattern", "format", "required"]
501
+ };
502
+ const BUILTIN_PROTOCOLS = /* @__PURE__ */ new Map([
503
+ ["atproto", ATPROTO_SPEC],
504
+ ["sql", SQL_SPEC],
505
+ ["protobuf", PROTOBUF_SPEC],
506
+ ["graphql", GRAPHQL_SPEC],
507
+ ["json-schema", JSON_SCHEMA_SPEC]
508
+ ]);
509
+ class MigrationBuilder {
510
+ #src;
511
+ #tgt;
512
+ #wasm;
513
+ #vertexMap;
514
+ #edgeMap;
515
+ #resolvers;
516
+ constructor(src, tgt, wasm, vertexMap = /* @__PURE__ */ new Map(), edgeMap = [], resolvers = []) {
517
+ this.#src = src;
518
+ this.#tgt = tgt;
519
+ this.#wasm = wasm;
520
+ this.#vertexMap = vertexMap;
521
+ this.#edgeMap = edgeMap;
522
+ this.#resolvers = resolvers;
523
+ }
524
+ /**
525
+ * Map a source vertex to a target vertex.
526
+ *
527
+ * @param srcVertex - Vertex id in the source schema
528
+ * @param tgtVertex - Vertex id in the target schema
529
+ * @returns A new builder with the mapping added
530
+ */
531
+ map(srcVertex, tgtVertex) {
532
+ const newMap = new Map(this.#vertexMap);
533
+ newMap.set(srcVertex, tgtVertex);
534
+ return new MigrationBuilder(
535
+ this.#src,
536
+ this.#tgt,
537
+ this.#wasm,
538
+ newMap,
539
+ this.#edgeMap,
540
+ this.#resolvers
541
+ );
542
+ }
543
+ /**
544
+ * Map a source edge to a target edge.
545
+ *
546
+ * @param srcEdge - Edge in the source schema
547
+ * @param tgtEdge - Edge in the target schema
548
+ * @returns A new builder with the edge mapping added
549
+ */
550
+ mapEdge(srcEdge, tgtEdge) {
551
+ return new MigrationBuilder(
552
+ this.#src,
553
+ this.#tgt,
554
+ this.#wasm,
555
+ this.#vertexMap,
556
+ [...this.#edgeMap, [srcEdge, tgtEdge]],
557
+ this.#resolvers
558
+ );
559
+ }
560
+ /**
561
+ * Add a resolver for ancestor contraction ambiguity.
562
+ *
563
+ * When a migration contracts nodes and the resulting edge between
564
+ * two vertex kinds is ambiguous, a resolver specifies which edge to use.
565
+ *
566
+ * @param srcKind - Source vertex kind in the contracted pair
567
+ * @param tgtKind - Target vertex kind in the contracted pair
568
+ * @param resolvedEdge - The edge to use for this pair
569
+ * @returns A new builder with the resolver added
570
+ */
571
+ resolve(srcKind, tgtKind, resolvedEdge) {
572
+ return new MigrationBuilder(
573
+ this.#src,
574
+ this.#tgt,
575
+ this.#wasm,
576
+ this.#vertexMap,
577
+ this.#edgeMap,
578
+ [...this.#resolvers, [[srcKind, tgtKind], resolvedEdge]]
579
+ );
580
+ }
581
+ /**
582
+ * Get the current migration specification.
583
+ *
584
+ * @returns The migration spec with all accumulated mappings
585
+ */
586
+ toSpec() {
587
+ return {
588
+ vertexMap: Object.fromEntries(this.#vertexMap),
589
+ edgeMap: [...this.#edgeMap],
590
+ resolvers: [...this.#resolvers]
591
+ };
592
+ }
593
+ /**
594
+ * Compile the migration for fast per-record application.
595
+ *
596
+ * Sends the migration specification to WASM for compilation.
597
+ * The resulting `CompiledMigration` can be used to transform records.
598
+ *
599
+ * @returns A compiled migration ready for record transformation
600
+ * @throws {@link MigrationError} if compilation fails
601
+ */
602
+ compile() {
603
+ const mapping = packMigrationMapping({
604
+ vertex_map: Object.fromEntries(this.#vertexMap),
605
+ edge_map: this.#edgeMap.map(([src, tgt]) => [
606
+ { src: src.src, tgt: src.tgt, kind: src.kind, name: src.name ?? null },
607
+ { src: tgt.src, tgt: tgt.tgt, kind: tgt.kind, name: tgt.name ?? null }
608
+ ]),
609
+ resolver: this.#resolvers.map(([[s, t], e]) => [
610
+ [s, t],
611
+ { src: e.src, tgt: e.tgt, kind: e.kind, name: e.name ?? null }
612
+ ])
613
+ });
614
+ try {
615
+ const rawHandle = this.#wasm.exports.compile_migration(
616
+ this.#src._handle.id,
617
+ this.#tgt._handle.id,
618
+ mapping
619
+ );
620
+ const handle = createHandle(rawHandle, this.#wasm);
621
+ return new CompiledMigration(handle, this.#wasm, this.toSpec());
622
+ } catch (error) {
623
+ throw new MigrationError(
624
+ `Failed to compile migration: ${error instanceof Error ? error.message : String(error)}`,
625
+ { cause: error }
626
+ );
627
+ }
628
+ }
629
+ }
630
+ class CompiledMigration {
631
+ #handle;
632
+ #wasm;
633
+ #spec;
634
+ constructor(handle, wasm, spec) {
635
+ this.#handle = handle;
636
+ this.#wasm = wasm;
637
+ this.#spec = spec;
638
+ }
639
+ /** The WASM handle for this migration. Internal use only. */
640
+ get _handle() {
641
+ return this.#handle;
642
+ }
643
+ /** The migration specification used to build this migration. */
644
+ get spec() {
645
+ return this.#spec;
646
+ }
647
+ /**
648
+ * Transform a record using this migration (forward direction).
649
+ *
650
+ * This is the hot path: data goes through WASM as MessagePack bytes
651
+ * with no intermediate JS-heap allocation.
652
+ *
653
+ * @param record - The input record to transform
654
+ * @returns The transformed record
655
+ * @throws {@link WasmError} if the WASM call fails
656
+ */
657
+ lift(record) {
658
+ const inputBytes = packToWasm(record);
659
+ try {
660
+ const outputBytes = this.#wasm.exports.lift_record(
661
+ this.#handle.id,
662
+ inputBytes
663
+ );
664
+ const data = unpackFromWasm(outputBytes);
665
+ return { data };
666
+ } catch (error) {
667
+ throw new WasmError(
668
+ `lift_record failed: ${error instanceof Error ? error.message : String(error)}`,
669
+ { cause: error }
670
+ );
671
+ }
672
+ }
673
+ /**
674
+ * Bidirectional get: extract view and complement from a record.
675
+ *
676
+ * The complement captures data discarded by the forward projection,
677
+ * enabling lossless round-tripping via `put()`.
678
+ *
679
+ * @param record - The input record
680
+ * @returns The projected view and opaque complement bytes
681
+ * @throws {@link WasmError} if the WASM call fails
682
+ */
683
+ get(record) {
684
+ const inputBytes = packToWasm(record);
685
+ try {
686
+ const outputBytes = this.#wasm.exports.get_record(
687
+ this.#handle.id,
688
+ inputBytes
689
+ );
690
+ const result = unpackFromWasm(outputBytes);
691
+ return {
692
+ view: result.view,
693
+ complement: result.complement instanceof Uint8Array ? result.complement : new Uint8Array(result.complement)
694
+ };
695
+ } catch (error) {
696
+ throw new WasmError(
697
+ `get_record failed: ${error instanceof Error ? error.message : String(error)}`,
698
+ { cause: error }
699
+ );
700
+ }
701
+ }
702
+ /**
703
+ * Bidirectional put: restore a full record from a modified view and complement.
704
+ *
705
+ * @param view - The (possibly modified) projected view
706
+ * @param complement - The complement from a prior `get()` call
707
+ * @returns The restored full record
708
+ * @throws {@link WasmError} if the WASM call fails
709
+ */
710
+ put(view, complement) {
711
+ const viewBytes = packToWasm(view);
712
+ try {
713
+ const outputBytes = this.#wasm.exports.put_record(
714
+ this.#handle.id,
715
+ viewBytes,
716
+ complement
717
+ );
718
+ const data = unpackFromWasm(outputBytes);
719
+ return { data };
720
+ } catch (error) {
721
+ throw new WasmError(
722
+ `put_record failed: ${error instanceof Error ? error.message : String(error)}`,
723
+ { cause: error }
724
+ );
725
+ }
726
+ }
727
+ /** Release the WASM-side compiled migration resource. */
728
+ [Symbol.dispose]() {
729
+ this.#handle[Symbol.dispose]();
730
+ }
731
+ }
732
+ function checkExistence(src, tgt, spec, wasm) {
733
+ const mapping = packMigrationMapping({
734
+ vertex_map: spec.vertexMap,
735
+ edge_map: spec.edgeMap.map(([s, t]) => [
736
+ { src: s.src, tgt: s.tgt, kind: s.kind, name: s.name ?? null },
737
+ { src: t.src, tgt: t.tgt, kind: t.kind, name: t.name ?? null }
738
+ ]),
739
+ resolver: spec.resolvers.map(([[s, t], e]) => [
740
+ [s, t],
741
+ { src: e.src, tgt: e.tgt, kind: e.kind, name: e.name ?? null }
742
+ ])
743
+ });
744
+ const resultBytes = wasm.exports.check_existence(
745
+ src._handle.id,
746
+ tgt._handle.id,
747
+ mapping
748
+ );
749
+ return unpackFromWasm(resultBytes);
750
+ }
751
+ function composeMigrations(m1, m2, wasm) {
752
+ try {
753
+ const rawHandle = wasm.exports.compose_migrations(
754
+ m1._handle.id,
755
+ m2._handle.id
756
+ );
757
+ const handle = createHandle(rawHandle, wasm);
758
+ const composedVertexMap = {};
759
+ for (const [src, intermediate] of Object.entries(m1.spec.vertexMap)) {
760
+ const final_ = m2.spec.vertexMap[intermediate];
761
+ composedVertexMap[src] = final_ ?? intermediate;
762
+ }
763
+ const composedSpec = {
764
+ vertexMap: composedVertexMap,
765
+ edgeMap: [...m1.spec.edgeMap, ...m2.spec.edgeMap],
766
+ resolvers: [...m1.spec.resolvers, ...m2.spec.resolvers]
767
+ };
768
+ return new CompiledMigration(handle, wasm, composedSpec);
769
+ } catch (error) {
770
+ throw new MigrationError(
771
+ `Failed to compose migrations: ${error instanceof Error ? error.message : String(error)}`,
772
+ { cause: error }
773
+ );
774
+ }
775
+ }
776
+ class Panproto {
777
+ #wasm;
778
+ #protocols;
779
+ constructor(wasm) {
780
+ this.#wasm = wasm;
781
+ this.#protocols = /* @__PURE__ */ new Map();
782
+ }
783
+ /**
784
+ * Initialize the panproto SDK by loading the WASM module.
785
+ *
786
+ * @param wasmUrl - Optional URL or path to the WASM binary.
787
+ * Defaults to the bundled binary.
788
+ * @returns An initialized Panproto instance
789
+ * @throws {@link import('./types.js').WasmError} if WASM loading fails
790
+ */
791
+ static async init(wasmUrl) {
792
+ const wasm = await loadWasm(wasmUrl);
793
+ return new Panproto(wasm);
794
+ }
795
+ /**
796
+ * Get or register a protocol by name.
797
+ *
798
+ * If the protocol is a built-in (e.g., 'atproto', 'sql'), it is
799
+ * automatically registered on first access. Custom protocols must
800
+ * be registered first with {@link Panproto.defineProtocol}.
801
+ *
802
+ * @param name - The protocol name
803
+ * @returns The protocol instance
804
+ * @throws {@link PanprotoError} if the protocol is not found
805
+ */
806
+ protocol(name) {
807
+ const cached = this.#protocols.get(name);
808
+ if (cached) return cached;
809
+ const builtinSpec = BUILTIN_PROTOCOLS.get(name);
810
+ if (builtinSpec) {
811
+ const proto = defineProtocol(builtinSpec, this.#wasm);
812
+ this.#protocols.set(name, proto);
813
+ return proto;
814
+ }
815
+ throw new PanprotoError(
816
+ `Protocol "${name}" not found. Register it with defineProtocol() first.`
817
+ );
818
+ }
819
+ /**
820
+ * Define and register a custom protocol.
821
+ *
822
+ * @param spec - The protocol specification
823
+ * @returns The registered protocol
824
+ * @throws {@link PanprotoError} if registration fails
825
+ */
826
+ defineProtocol(spec) {
827
+ const proto = defineProtocol(spec, this.#wasm);
828
+ this.#protocols.set(spec.name, proto);
829
+ return proto;
830
+ }
831
+ /**
832
+ * Start building a migration between two schemas.
833
+ *
834
+ * @param src - The source schema
835
+ * @param tgt - The target schema
836
+ * @returns A migration builder
837
+ */
838
+ migration(src, tgt) {
839
+ return new MigrationBuilder(src, tgt, this.#wasm);
840
+ }
841
+ /**
842
+ * Check existence conditions for a proposed migration.
843
+ *
844
+ * Verifies that the migration specification satisfies all
845
+ * protocol-derived constraints (edge coverage, kind consistency,
846
+ * required fields, etc.).
847
+ *
848
+ * @param src - The source schema
849
+ * @param tgt - The target schema
850
+ * @param builder - The migration builder with mappings
851
+ * @returns The existence report
852
+ */
853
+ checkExistence(src, tgt, builder) {
854
+ return checkExistence(src, tgt, builder.toSpec(), this.#wasm);
855
+ }
856
+ /**
857
+ * Compose two compiled migrations into a single migration.
858
+ *
859
+ * The resulting migration is equivalent to applying `m1` then `m2`.
860
+ *
861
+ * @param m1 - First migration (applied first)
862
+ * @param m2 - Second migration (applied second)
863
+ * @returns The composed migration
864
+ * @throws {@link import('./types.js').MigrationError} if composition fails
865
+ */
866
+ compose(m1, m2) {
867
+ return composeMigrations(m1, m2, this.#wasm);
868
+ }
869
+ /**
870
+ * Diff two schemas and produce a compatibility report.
871
+ *
872
+ * @param oldSchema - The old/source schema
873
+ * @param newSchema - The new/target schema
874
+ * @returns A diff report with changes and compatibility classification
875
+ */
876
+ diff(oldSchema, newSchema) {
877
+ const resultBytes = this.#wasm.exports.diff_schemas(
878
+ oldSchema._handle.id,
879
+ newSchema._handle.id
880
+ );
881
+ return unpackFromWasm(resultBytes);
882
+ }
883
+ /**
884
+ * Release all WASM resources held by this instance.
885
+ *
886
+ * Disposes all cached protocols. After disposal, this instance
887
+ * must not be used.
888
+ */
889
+ [Symbol.dispose]() {
890
+ for (const proto of this.#protocols.values()) {
891
+ proto[Symbol.dispose]();
892
+ }
893
+ this.#protocols.clear();
894
+ }
895
+ }
896
+ function renameField(oldName, newName) {
897
+ return { type: "rename-field", old: oldName, new: newName };
898
+ }
899
+ function addField(name, vertexKind, defaultValue) {
900
+ return { type: "add-field", name, vertexKind, default: defaultValue };
901
+ }
902
+ function removeField(name) {
903
+ return { type: "remove-field", name };
904
+ }
905
+ function wrapInObject(fieldName) {
906
+ return { type: "wrap-in-object", fieldName };
907
+ }
908
+ function hoistField(host, field) {
909
+ return { type: "hoist-field", host, field };
910
+ }
911
+ function coerceType(fromKind, toKind) {
912
+ return { type: "coerce-type", fromKind, toKind };
913
+ }
914
+ function compose(first, second) {
915
+ return { type: "compose", first, second };
916
+ }
917
+ function pipeline(combinators) {
918
+ return combinators.reduce((acc, c) => compose(acc, c));
919
+ }
920
+ exports.ATPROTO_SPEC = ATPROTO_SPEC;
921
+ exports.BUILTIN_PROTOCOLS = BUILTIN_PROTOCOLS;
922
+ exports.BuiltSchema = BuiltSchema;
923
+ exports.CompiledMigration = CompiledMigration;
924
+ exports.ExistenceCheckError = ExistenceCheckError;
925
+ exports.GRAPHQL_SPEC = GRAPHQL_SPEC;
926
+ exports.JSON_SCHEMA_SPEC = JSON_SCHEMA_SPEC;
927
+ exports.MigrationBuilder = MigrationBuilder;
928
+ exports.MigrationError = MigrationError;
929
+ exports.PROTOBUF_SPEC = PROTOBUF_SPEC;
930
+ exports.Panproto = Panproto;
931
+ exports.PanprotoError = PanprotoError;
932
+ exports.Protocol = Protocol;
933
+ exports.SQL_SPEC = SQL_SPEC;
934
+ exports.SchemaBuilder = SchemaBuilder;
935
+ exports.SchemaValidationError = SchemaValidationError;
936
+ exports.WasmError = WasmError;
937
+ exports.addField = addField;
938
+ exports.coerceType = coerceType;
939
+ exports.compose = compose;
940
+ exports.hoistField = hoistField;
941
+ exports.pipeline = pipeline;
942
+ exports.removeField = removeField;
943
+ exports.renameField = renameField;
944
+ exports.wrapInObject = wrapInObject;
945
+ //# sourceMappingURL=index.cjs.map