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