@panproto/core 0.2.0 → 0.4.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
@@ -1,4 +1,4 @@
1
- import { encode, decode } from "@msgpack/msgpack";
1
+ import { decode, encode } from "@msgpack/msgpack";
2
2
  class PanprotoError extends Error {
3
3
  name = "PanprotoError";
4
4
  constructor(message, options) {
@@ -49,7 +49,48 @@ async function loadWasm(input) {
49
49
  put_record: glue.put_record,
50
50
  compose_migrations: glue.compose_migrations,
51
51
  diff_schemas: glue.diff_schemas,
52
- free_handle: glue.free_handle
52
+ free_handle: glue.free_handle,
53
+ diff_schemas_full: glue.diff_schemas_full,
54
+ classify_diff: glue.classify_diff,
55
+ report_text: glue.report_text,
56
+ report_json: glue.report_json,
57
+ normalize_schema: glue.normalize_schema,
58
+ validate_schema: glue.validate_schema,
59
+ register_io_protocols: glue.register_io_protocols,
60
+ list_io_protocols: glue.list_io_protocols,
61
+ parse_instance: glue.parse_instance,
62
+ emit_instance: glue.emit_instance,
63
+ validate_instance: glue.validate_instance,
64
+ instance_to_json: glue.instance_to_json,
65
+ json_to_instance: glue.json_to_instance,
66
+ instance_element_count: glue.instance_element_count,
67
+ lens_from_combinators: glue.lens_from_combinators,
68
+ check_lens_laws: glue.check_lens_laws,
69
+ check_get_put: glue.check_get_put,
70
+ check_put_get: glue.check_put_get,
71
+ invert_migration: glue.invert_migration,
72
+ compose_lenses: glue.compose_lenses,
73
+ // Phase 4
74
+ list_builtin_protocols: glue.list_builtin_protocols,
75
+ get_builtin_protocol: glue.get_builtin_protocol,
76
+ // Phase 5
77
+ create_theory: glue.create_theory,
78
+ colimit_theories: glue.colimit_theories,
79
+ check_morphism: glue.check_morphism,
80
+ migrate_model: glue.migrate_model,
81
+ // Phase 6
82
+ vcs_init: glue.vcs_init,
83
+ vcs_add: glue.vcs_add,
84
+ vcs_commit: glue.vcs_commit,
85
+ vcs_log: glue.vcs_log,
86
+ vcs_status: glue.vcs_status,
87
+ vcs_diff: glue.vcs_diff,
88
+ vcs_branch: glue.vcs_branch,
89
+ vcs_checkout: glue.vcs_checkout,
90
+ vcs_merge: glue.vcs_merge,
91
+ vcs_stash: glue.vcs_stash,
92
+ vcs_stash_pop: glue.vcs_stash_pop,
93
+ vcs_blame: glue.vcs_blame
53
94
  };
54
95
  const memory = initOutput.memory;
55
96
  if (!memory) {
@@ -116,6 +157,280 @@ function packSchemaOps(ops) {
116
157
  function packMigrationMapping(mapping) {
117
158
  return encode(mapping);
118
159
  }
160
+ function renameField(oldName, newName) {
161
+ return { type: "rename-field", old: oldName, new: newName };
162
+ }
163
+ function addField(name, vertexKind, defaultValue) {
164
+ return { type: "add-field", name, vertexKind, default: defaultValue };
165
+ }
166
+ function removeField(name) {
167
+ return { type: "remove-field", name };
168
+ }
169
+ function wrapInObject(fieldName) {
170
+ return { type: "wrap-in-object", fieldName };
171
+ }
172
+ function hoistField(host, field) {
173
+ return { type: "hoist-field", host, field };
174
+ }
175
+ function coerceType(fromKind, toKind) {
176
+ return { type: "coerce-type", fromKind, toKind };
177
+ }
178
+ function compose(first, second) {
179
+ return { type: "compose", first, second };
180
+ }
181
+ function pipeline(combinators) {
182
+ const [first, ...rest] = combinators;
183
+ return rest.reduce((acc, c) => compose(acc, c), first);
184
+ }
185
+ class LensHandle {
186
+ #handle;
187
+ #wasm;
188
+ constructor(handle, wasm) {
189
+ this.#handle = handle;
190
+ this.#wasm = wasm;
191
+ }
192
+ /** The underlying WASM handle. Internal use only. */
193
+ get _handle() {
194
+ return this.#handle;
195
+ }
196
+ /**
197
+ * Forward projection: extract the view from a record.
198
+ *
199
+ * @param record - MessagePack-encoded input record
200
+ * @returns The projected view and opaque complement bytes
201
+ * @throws {@link WasmError} if the WASM call fails
202
+ */
203
+ get(record) {
204
+ try {
205
+ const outputBytes = this.#wasm.exports.get_record(
206
+ this.#handle.id,
207
+ record
208
+ );
209
+ const result = unpackFromWasm(outputBytes);
210
+ return {
211
+ view: result.view,
212
+ complement: result.complement instanceof Uint8Array ? result.complement : new Uint8Array(result.complement)
213
+ };
214
+ } catch (error) {
215
+ throw new WasmError(
216
+ `get_record failed: ${error instanceof Error ? error.message : String(error)}`,
217
+ { cause: error }
218
+ );
219
+ }
220
+ }
221
+ /**
222
+ * Backward put: restore a full record from a modified view and complement.
223
+ *
224
+ * @param view - MessagePack-encoded (possibly modified) projected view
225
+ * @param complement - The complement from a prior `get()` call
226
+ * @returns The restored full record
227
+ * @throws {@link WasmError} if the WASM call fails
228
+ */
229
+ put(view, complement) {
230
+ try {
231
+ const outputBytes = this.#wasm.exports.put_record(
232
+ this.#handle.id,
233
+ view,
234
+ complement
235
+ );
236
+ const data = unpackFromWasm(outputBytes);
237
+ return { data };
238
+ } catch (error) {
239
+ throw new WasmError(
240
+ `put_record failed: ${error instanceof Error ? error.message : String(error)}`,
241
+ { cause: error }
242
+ );
243
+ }
244
+ }
245
+ /**
246
+ * Check both GetPut and PutGet lens laws for an instance.
247
+ *
248
+ * @param instance - MessagePack-encoded instance data
249
+ * @returns Whether both laws hold and any violation message
250
+ * @throws {@link WasmError} if the WASM call fails
251
+ */
252
+ checkLaws(instance) {
253
+ try {
254
+ const resultBytes = this.#wasm.exports.check_lens_laws(
255
+ this.#handle.id,
256
+ instance
257
+ );
258
+ return unpackFromWasm(resultBytes);
259
+ } catch (error) {
260
+ throw new WasmError(
261
+ `check_lens_laws failed: ${error instanceof Error ? error.message : String(error)}`,
262
+ { cause: error }
263
+ );
264
+ }
265
+ }
266
+ /**
267
+ * Check the GetPut lens law for an instance.
268
+ *
269
+ * @param instance - MessagePack-encoded instance data
270
+ * @returns Whether the law holds and any violation message
271
+ * @throws {@link WasmError} if the WASM call fails
272
+ */
273
+ checkGetPut(instance) {
274
+ try {
275
+ const resultBytes = this.#wasm.exports.check_get_put(
276
+ this.#handle.id,
277
+ instance
278
+ );
279
+ return unpackFromWasm(resultBytes);
280
+ } catch (error) {
281
+ throw new WasmError(
282
+ `check_get_put failed: ${error instanceof Error ? error.message : String(error)}`,
283
+ { cause: error }
284
+ );
285
+ }
286
+ }
287
+ /**
288
+ * Check the PutGet lens law for an instance.
289
+ *
290
+ * @param instance - MessagePack-encoded instance data
291
+ * @returns Whether the law holds and any violation message
292
+ * @throws {@link WasmError} if the WASM call fails
293
+ */
294
+ checkPutGet(instance) {
295
+ try {
296
+ const resultBytes = this.#wasm.exports.check_put_get(
297
+ this.#handle.id,
298
+ instance
299
+ );
300
+ return unpackFromWasm(resultBytes);
301
+ } catch (error) {
302
+ throw new WasmError(
303
+ `check_put_get failed: ${error instanceof Error ? error.message : String(error)}`,
304
+ { cause: error }
305
+ );
306
+ }
307
+ }
308
+ /** Release the underlying WASM resource. */
309
+ [Symbol.dispose]() {
310
+ this.#handle[Symbol.dispose]();
311
+ }
312
+ }
313
+ function fromCombinators(schema, protocol, wasm, ...combinators) {
314
+ const wireCombs = combinators.map(combinatorToWire);
315
+ const combBytes = packToWasm(wireCombs);
316
+ try {
317
+ const rawHandle = wasm.exports.lens_from_combinators(
318
+ schema._handle.id,
319
+ protocol._handle.id,
320
+ combBytes
321
+ );
322
+ const handle = createHandle(rawHandle, wasm);
323
+ return new LensHandle(handle, wasm);
324
+ } catch (error) {
325
+ throw new WasmError(
326
+ `lens_from_combinators failed: ${error instanceof Error ? error.message : String(error)}`,
327
+ { cause: error }
328
+ );
329
+ }
330
+ }
331
+ function combinatorToWire(combinator) {
332
+ switch (combinator.type) {
333
+ case "rename-field":
334
+ return { RenameField: { old: combinator.old, new: combinator.new } };
335
+ case "add-field":
336
+ return { AddField: { name: combinator.name, vertex_kind: combinator.vertexKind, default: combinator.default } };
337
+ case "remove-field":
338
+ return { RemoveField: { name: combinator.name } };
339
+ case "wrap-in-object":
340
+ return { WrapInObject: { field_name: combinator.fieldName } };
341
+ case "hoist-field":
342
+ return { HoistField: { host: combinator.host, field: combinator.field } };
343
+ case "coerce-type":
344
+ return { CoerceType: { from_kind: combinator.fromKind, to_kind: combinator.toKind } };
345
+ case "compose":
346
+ return { Compose: [combinatorToWire(combinator.first), combinatorToWire(combinator.second)] };
347
+ }
348
+ }
349
+ class FullDiffReport {
350
+ /** The raw diff data. */
351
+ data;
352
+ /** @internal */
353
+ _reportBytes;
354
+ /** @internal */
355
+ _wasm;
356
+ /** @internal */
357
+ constructor(data, reportBytes, wasm) {
358
+ this.data = data;
359
+ this._reportBytes = reportBytes;
360
+ this._wasm = wasm;
361
+ }
362
+ /** Whether there are any changes at all. */
363
+ get hasChanges() {
364
+ const d = this.data;
365
+ return d.added_vertices.length > 0 || d.removed_vertices.length > 0 || d.kind_changes.length > 0 || d.added_edges.length > 0 || d.removed_edges.length > 0 || Object.keys(d.modified_constraints).length > 0 || d.added_hyper_edges.length > 0 || d.removed_hyper_edges.length > 0 || d.modified_hyper_edges.length > 0 || Object.keys(d.added_required).length > 0 || Object.keys(d.removed_required).length > 0 || Object.keys(d.added_nsids).length > 0 || d.removed_nsids.length > 0 || d.changed_nsids.length > 0 || d.added_variants.length > 0 || d.removed_variants.length > 0 || d.modified_variants.length > 0 || d.order_changes.length > 0 || d.added_recursion_points.length > 0 || d.removed_recursion_points.length > 0 || d.modified_recursion_points.length > 0 || d.usage_mode_changes.length > 0 || d.added_spans.length > 0 || d.removed_spans.length > 0 || d.modified_spans.length > 0 || d.nominal_changes.length > 0;
366
+ }
367
+ /** Classify the diff against a protocol, producing a compatibility report. */
368
+ classify(protocol) {
369
+ const rawBytes = this._wasm.exports.classify_diff(
370
+ protocol._handle.id,
371
+ this._reportBytes
372
+ );
373
+ const data = unpackFromWasm(rawBytes);
374
+ return new CompatReport(data, rawBytes, this._wasm);
375
+ }
376
+ }
377
+ class CompatReport {
378
+ /** The raw report data. */
379
+ data;
380
+ /** @internal */
381
+ _rawBytes;
382
+ /** @internal */
383
+ _wasm;
384
+ /** @internal */
385
+ constructor(data, rawBytes, wasm) {
386
+ this.data = data;
387
+ this._rawBytes = rawBytes;
388
+ this._wasm = wasm;
389
+ }
390
+ /** List of breaking changes. */
391
+ get breakingChanges() {
392
+ return this.data.breaking;
393
+ }
394
+ /** List of non-breaking changes. */
395
+ get nonBreakingChanges() {
396
+ return this.data.non_breaking;
397
+ }
398
+ /** Whether the changes are fully compatible (no breaking changes). */
399
+ get isCompatible() {
400
+ return this.data.compatible;
401
+ }
402
+ /** Whether there are any breaking changes. */
403
+ get isBreaking() {
404
+ return !this.data.compatible;
405
+ }
406
+ /** Whether the changes are backward-compatible (additions only, no removals). */
407
+ get isBackwardCompatible() {
408
+ return this.data.compatible;
409
+ }
410
+ /** Render as human-readable text. */
411
+ toText() {
412
+ return this._wasm.exports.report_text(this._rawBytes);
413
+ }
414
+ /** Render as a JSON string. */
415
+ toJson() {
416
+ return this._wasm.exports.report_json(this._rawBytes);
417
+ }
418
+ }
419
+ class ValidationResult {
420
+ /** The list of validation issues found. */
421
+ issues;
422
+ constructor(issues) {
423
+ this.issues = issues;
424
+ }
425
+ /** Whether the schema is valid (no issues found). */
426
+ get isValid() {
427
+ return this.issues.length === 0;
428
+ }
429
+ /** The number of validation issues. */
430
+ get errorCount() {
431
+ return this.issues.length;
432
+ }
433
+ }
119
434
  class SchemaBuilder {
120
435
  #protocolName;
121
436
  #protocolHandle;
@@ -350,7 +665,13 @@ class SchemaBuilder {
350
665
  ),
351
666
  required: Object.fromEntries(
352
667
  Array.from(this.#required.entries()).map(([k, v]) => [k, [...v]])
353
- )
668
+ ),
669
+ variants: {},
670
+ orderings: {},
671
+ recursionPoints: {},
672
+ usageModes: {},
673
+ spans: {},
674
+ nominal: {}
354
675
  };
355
676
  return new BuiltSchema(handle, data, this.#wasm);
356
677
  }
@@ -388,6 +709,25 @@ class BuiltSchema {
388
709
  get edges() {
389
710
  return this.#data.edges;
390
711
  }
712
+ /** @internal Create from raw handle (used by normalize). */
713
+ static _fromHandle(handle, data, _protocol, wasm) {
714
+ const wasmHandle = createHandle(handle, wasm);
715
+ return new BuiltSchema(wasmHandle, data, wasm);
716
+ }
717
+ /** Normalize this schema by collapsing reference chains. Returns a new BuiltSchema. */
718
+ normalize() {
719
+ const handle = this.#wasm.exports.normalize_schema(this.#handle.id);
720
+ return BuiltSchema._fromHandle(handle, this.#data, this.#data.protocol, this.#wasm);
721
+ }
722
+ /** Validate this schema against a protocol's rules. */
723
+ validate(protocol) {
724
+ const bytes = this.#wasm.exports.validate_schema(
725
+ this.#handle.id,
726
+ protocol._handle.id
727
+ );
728
+ const issues = unpackFromWasm(bytes);
729
+ return new ValidationResult(issues);
730
+ }
391
731
  /** Release the WASM-side schema resource. */
392
732
  [Symbol.dispose]() {
393
733
  this.#handle[Symbol.dispose]();
@@ -410,6 +750,18 @@ class Protocol {
410
750
  get spec() {
411
751
  return this.#spec;
412
752
  }
753
+ /** The edge rules for this protocol. */
754
+ get edgeRules() {
755
+ return this.#spec.edgeRules;
756
+ }
757
+ /** The constraint sorts for this protocol. */
758
+ get constraintSorts() {
759
+ return this.#spec.constraintSorts;
760
+ }
761
+ /** The object kinds for this protocol. */
762
+ get objectKinds() {
763
+ return this.#spec.objKinds;
764
+ }
413
765
  /** The WASM handle. Internal use only. */
414
766
  get _handle() {
415
767
  return this.#handle;
@@ -550,6 +902,34 @@ const BUILTIN_PROTOCOLS = /* @__PURE__ */ new Map([
550
902
  ["graphql", GRAPHQL_SPEC],
551
903
  ["json-schema", JSON_SCHEMA_SPEC]
552
904
  ]);
905
+ let _protocolNamesCache = null;
906
+ function getProtocolNames(wasm) {
907
+ if (_protocolNamesCache !== null) return _protocolNamesCache;
908
+ const bytes = wasm.exports.list_builtin_protocols();
909
+ _protocolNamesCache = unpackFromWasm(bytes);
910
+ return _protocolNamesCache;
911
+ }
912
+ function getBuiltinProtocol(name, wasm) {
913
+ try {
914
+ const nameBytes = new TextEncoder().encode(name);
915
+ const bytes = wasm.exports.get_builtin_protocol(nameBytes);
916
+ const wire = unpackFromWasm(bytes);
917
+ return {
918
+ name: wire.name,
919
+ schemaTheory: wire.schema_theory,
920
+ instanceTheory: wire.instance_theory,
921
+ edgeRules: wire.edge_rules.map((r) => ({
922
+ edgeKind: r.edge_kind,
923
+ srcKinds: r.src_kinds,
924
+ tgtKinds: r.tgt_kinds
925
+ })),
926
+ objKinds: wire.obj_kinds,
927
+ constraintSorts: wire.constraint_sorts
928
+ };
929
+ } catch {
930
+ return void 0;
931
+ }
932
+ }
553
933
  class MigrationBuilder {
554
934
  #src;
555
935
  #tgt;
@@ -634,6 +1014,50 @@ class MigrationBuilder {
634
1014
  resolvers: [...this.#resolvers]
635
1015
  };
636
1016
  }
1017
+ /**
1018
+ * Invert a bijective migration.
1019
+ *
1020
+ * Serializes the current mapping to MessagePack and calls the
1021
+ * `invert_migration` WASM entry point. Returns a new MigrationSpec
1022
+ * representing the inverted migration.
1023
+ *
1024
+ * @returns The inverted migration specification
1025
+ * @throws {@link MigrationError} if the migration is not bijective or inversion fails
1026
+ */
1027
+ invert() {
1028
+ const edgeMap = new Map(
1029
+ this.#edgeMap.map(([src, tgt]) => [
1030
+ { src: src.src, tgt: src.tgt, kind: src.kind, name: src.name ?? null },
1031
+ { src: tgt.src, tgt: tgt.tgt, kind: tgt.kind, name: tgt.name ?? null }
1032
+ ])
1033
+ );
1034
+ const resolver = new Map(
1035
+ this.#resolvers.map(([[s, t], e]) => [
1036
+ [s, t],
1037
+ { src: e.src, tgt: e.tgt, kind: e.kind, name: e.name ?? null }
1038
+ ])
1039
+ );
1040
+ const mapping = packMigrationMapping({
1041
+ vertex_map: Object.fromEntries(this.#vertexMap),
1042
+ edge_map: edgeMap,
1043
+ hyper_edge_map: {},
1044
+ label_map: /* @__PURE__ */ new Map(),
1045
+ resolver
1046
+ });
1047
+ try {
1048
+ const resultBytes = this.#wasm.exports.invert_migration(
1049
+ mapping,
1050
+ this.#src._handle.id,
1051
+ this.#tgt._handle.id
1052
+ );
1053
+ return unpackFromWasm(resultBytes);
1054
+ } catch (error) {
1055
+ throw new MigrationError(
1056
+ `Failed to invert migration: ${error instanceof Error ? error.message : String(error)}`,
1057
+ { cause: error }
1058
+ );
1059
+ }
1060
+ }
637
1061
  /**
638
1062
  * Compile the migration for fast per-record application.
639
1063
  *
@@ -834,6 +1258,433 @@ function composeMigrations(m1, m2, wasm) {
834
1258
  );
835
1259
  }
836
1260
  }
1261
+ class Instance {
1262
+ /** Raw MsgPack-encoded instance bytes (for passing back to WASM). */
1263
+ _bytes;
1264
+ /** The schema this instance conforms to. */
1265
+ _schema;
1266
+ /** @internal */
1267
+ _wasm;
1268
+ constructor(bytes, schema, wasm) {
1269
+ this._bytes = bytes;
1270
+ this._schema = schema;
1271
+ this._wasm = wasm;
1272
+ }
1273
+ /** Convert this instance to JSON bytes. */
1274
+ toJson() {
1275
+ return this._wasm.exports.instance_to_json(
1276
+ this._schema._handle.id,
1277
+ this._bytes
1278
+ );
1279
+ }
1280
+ /** Validate this instance against its schema. */
1281
+ validate() {
1282
+ const resultBytes = this._wasm.exports.validate_instance(
1283
+ this._schema._handle.id,
1284
+ this._bytes
1285
+ );
1286
+ const errors = unpackFromWasm(resultBytes);
1287
+ return {
1288
+ isValid: errors.length === 0,
1289
+ errors
1290
+ };
1291
+ }
1292
+ /** Get the number of elements in this instance. */
1293
+ get elementCount() {
1294
+ return this._wasm.exports.instance_element_count(this._bytes);
1295
+ }
1296
+ /**
1297
+ * Create an Instance from JSON input.
1298
+ *
1299
+ * @param schema - The schema the JSON data conforms to
1300
+ * @param json - JSON bytes
1301
+ * @param wasm - The WASM module
1302
+ * @returns A new Instance
1303
+ */
1304
+ static fromJson(schema, json, wasm) {
1305
+ const bytes = wasm.exports.json_to_instance(schema._handle.id, json);
1306
+ return new Instance(bytes, schema, wasm);
1307
+ }
1308
+ }
1309
+ const PROTOCOL_CATEGORIES = {
1310
+ annotation: [
1311
+ "brat",
1312
+ "conllu",
1313
+ "naf",
1314
+ "uima",
1315
+ "folia",
1316
+ "tei",
1317
+ "timeml",
1318
+ "elan",
1319
+ "iso_space",
1320
+ "paula",
1321
+ "laf_graf",
1322
+ "decomp",
1323
+ "ucca",
1324
+ "fovea",
1325
+ "bead",
1326
+ "web_annotation",
1327
+ "amr",
1328
+ "concrete",
1329
+ "nif"
1330
+ ],
1331
+ api: ["graphql", "openapi", "asyncapi", "jsonapi", "raml"],
1332
+ config: ["cloudformation", "ansible", "k8s_crd", "hcl"],
1333
+ data_schema: [
1334
+ "json_schema",
1335
+ "yaml_schema",
1336
+ "toml_schema",
1337
+ "cddl",
1338
+ "bson",
1339
+ "csv_table",
1340
+ "ini_schema"
1341
+ ],
1342
+ data_science: ["dataframe", "parquet", "arrow"],
1343
+ database: ["mongodb", "dynamodb", "cassandra", "neo4j", "sql", "redis"],
1344
+ domain: ["geojson", "fhir", "rss_atom", "vcard_ical", "swift_mt", "edi_x12"],
1345
+ serialization: [
1346
+ "protobuf",
1347
+ "avro",
1348
+ "thrift",
1349
+ "capnproto",
1350
+ "flatbuffers",
1351
+ "asn1",
1352
+ "bond",
1353
+ "msgpack_schema"
1354
+ ],
1355
+ type_system: [
1356
+ "typescript",
1357
+ "python",
1358
+ "rust_serde",
1359
+ "java",
1360
+ "go_struct",
1361
+ "kotlin",
1362
+ "csharp",
1363
+ "swift"
1364
+ ],
1365
+ web_document: [
1366
+ "atproto",
1367
+ "jsx",
1368
+ "vue",
1369
+ "svelte",
1370
+ "css",
1371
+ "html",
1372
+ "markdown",
1373
+ "xml_xsd",
1374
+ "docx",
1375
+ "odf"
1376
+ ]
1377
+ };
1378
+ const encoder$1 = new TextEncoder();
1379
+ class IoRegistry {
1380
+ _handle;
1381
+ _wasm;
1382
+ _protocolsCache = null;
1383
+ constructor(handle, wasm) {
1384
+ this._handle = handle;
1385
+ this._wasm = wasm;
1386
+ }
1387
+ /**
1388
+ * List all registered protocol names.
1389
+ *
1390
+ * The result is cached after the first call.
1391
+ */
1392
+ get protocols() {
1393
+ if (this._protocolsCache === null) {
1394
+ const bytes = this._wasm.exports.list_io_protocols(this._handle.id);
1395
+ this._protocolsCache = unpackFromWasm(bytes);
1396
+ }
1397
+ return this._protocolsCache;
1398
+ }
1399
+ /** Protocol names organized by category. */
1400
+ get categories() {
1401
+ return PROTOCOL_CATEGORIES;
1402
+ }
1403
+ /** Check if a protocol is registered. */
1404
+ hasProtocol(name) {
1405
+ return this.protocols.includes(name);
1406
+ }
1407
+ /**
1408
+ * Parse raw format bytes into an Instance.
1409
+ *
1410
+ * @param protocolName - The protocol codec name (e.g., 'graphql', 'protobuf')
1411
+ * @param schema - The schema the data conforms to
1412
+ * @param input - Raw format bytes to parse
1413
+ * @returns A new Instance wrapping the parsed data
1414
+ * @throws {@link PanprotoError} if the protocol is not registered or parsing fails
1415
+ */
1416
+ parse(protocolName, schema, input) {
1417
+ const nameBytes = encoder$1.encode(protocolName);
1418
+ const resultBytes = this._wasm.exports.parse_instance(
1419
+ this._handle.id,
1420
+ nameBytes,
1421
+ schema._handle.id,
1422
+ input
1423
+ );
1424
+ return new Instance(resultBytes, schema, this._wasm);
1425
+ }
1426
+ /**
1427
+ * Emit an Instance to raw format bytes.
1428
+ *
1429
+ * @param protocolName - The protocol codec name (e.g., 'graphql', 'protobuf')
1430
+ * @param schema - The schema the instance conforms to
1431
+ * @param instance - The instance to emit
1432
+ * @returns Raw format bytes
1433
+ * @throws {@link PanprotoError} if the protocol is not registered or emission fails
1434
+ */
1435
+ emit(protocolName, schema, instance) {
1436
+ const nameBytes = encoder$1.encode(protocolName);
1437
+ return this._wasm.exports.emit_instance(
1438
+ this._handle.id,
1439
+ nameBytes,
1440
+ schema._handle.id,
1441
+ instance._bytes
1442
+ );
1443
+ }
1444
+ /** Release the WASM-side IoRegistry resource. */
1445
+ [Symbol.dispose]() {
1446
+ this._handle[Symbol.dispose]();
1447
+ }
1448
+ }
1449
+ const encoder = new TextEncoder();
1450
+ class Repository {
1451
+ #handle;
1452
+ #wasm;
1453
+ #protocolName;
1454
+ constructor(handle, protocolName, wasm) {
1455
+ this.#handle = handle;
1456
+ this.#protocolName = protocolName;
1457
+ this.#wasm = wasm;
1458
+ }
1459
+ /**
1460
+ * Initialize a new in-memory repository.
1461
+ *
1462
+ * @param protocolName - The protocol this repository tracks
1463
+ * @param wasm - The WASM module
1464
+ * @returns A new disposable Repository
1465
+ */
1466
+ static init(protocolName, wasm) {
1467
+ const nameBytes = encoder.encode(protocolName);
1468
+ const rawHandle = wasm.exports.vcs_init(nameBytes);
1469
+ const handle = createHandle(rawHandle, wasm);
1470
+ return new Repository(handle, protocolName, wasm);
1471
+ }
1472
+ /** The protocol name this repository tracks. */
1473
+ get protocolName() {
1474
+ return this.#protocolName;
1475
+ }
1476
+ /** The underlying WASM handle. Internal use only. */
1477
+ get _handle() {
1478
+ return this.#handle;
1479
+ }
1480
+ /**
1481
+ * Stage a schema for the next commit.
1482
+ *
1483
+ * @param schema - The built schema to stage
1484
+ * @returns An object with the schema's object ID
1485
+ */
1486
+ add(schema) {
1487
+ try {
1488
+ const resultBytes = this.#wasm.exports.vcs_add(
1489
+ this.#handle.id,
1490
+ schema._handle.id
1491
+ );
1492
+ const result = unpackFromWasm(resultBytes);
1493
+ return { schemaId: result.schema_id };
1494
+ } catch (error) {
1495
+ throw new WasmError(
1496
+ `vcs_add failed: ${error instanceof Error ? error.message : String(error)}`,
1497
+ { cause: error }
1498
+ );
1499
+ }
1500
+ }
1501
+ /**
1502
+ * Create a commit from the current staging area.
1503
+ *
1504
+ * @param message - The commit message
1505
+ * @param author - The commit author
1506
+ * @returns The commit result
1507
+ */
1508
+ commit(message, author) {
1509
+ try {
1510
+ return this.#wasm.exports.vcs_commit(
1511
+ this.#handle.id,
1512
+ encoder.encode(message),
1513
+ encoder.encode(author)
1514
+ );
1515
+ } catch (error) {
1516
+ throw new WasmError(
1517
+ `vcs_commit failed: ${error instanceof Error ? error.message : String(error)}`,
1518
+ { cause: error }
1519
+ );
1520
+ }
1521
+ }
1522
+ /**
1523
+ * Walk the commit log from HEAD.
1524
+ *
1525
+ * @param count - Maximum number of log entries to return (default: 50)
1526
+ * @returns Array of commit log entries
1527
+ */
1528
+ log(count = 50) {
1529
+ try {
1530
+ const resultBytes = this.#wasm.exports.vcs_log(this.#handle.id, count);
1531
+ return unpackFromWasm(resultBytes);
1532
+ } catch (error) {
1533
+ throw new WasmError(
1534
+ `vcs_log failed: ${error instanceof Error ? error.message : String(error)}`,
1535
+ { cause: error }
1536
+ );
1537
+ }
1538
+ }
1539
+ /**
1540
+ * Get the repository status.
1541
+ *
1542
+ * @returns Current branch and HEAD commit info
1543
+ */
1544
+ status() {
1545
+ try {
1546
+ const resultBytes = this.#wasm.exports.vcs_status(this.#handle.id);
1547
+ return unpackFromWasm(resultBytes);
1548
+ } catch (error) {
1549
+ throw new WasmError(
1550
+ `vcs_status failed: ${error instanceof Error ? error.message : String(error)}`,
1551
+ { cause: error }
1552
+ );
1553
+ }
1554
+ }
1555
+ /**
1556
+ * Get diff information for the repository.
1557
+ *
1558
+ * @returns Diff result with branch info
1559
+ */
1560
+ diff() {
1561
+ try {
1562
+ const resultBytes = this.#wasm.exports.vcs_diff(this.#handle.id);
1563
+ return unpackFromWasm(resultBytes);
1564
+ } catch (error) {
1565
+ throw new WasmError(
1566
+ `vcs_diff failed: ${error instanceof Error ? error.message : String(error)}`,
1567
+ { cause: error }
1568
+ );
1569
+ }
1570
+ }
1571
+ /**
1572
+ * Create a new branch at the current HEAD.
1573
+ *
1574
+ * @param name - The branch name
1575
+ * @returns Operation result
1576
+ */
1577
+ branch(name) {
1578
+ try {
1579
+ const resultBytes = this.#wasm.exports.vcs_branch(
1580
+ this.#handle.id,
1581
+ encoder.encode(name)
1582
+ );
1583
+ return unpackFromWasm(resultBytes);
1584
+ } catch (error) {
1585
+ throw new WasmError(
1586
+ `vcs_branch failed: ${error instanceof Error ? error.message : String(error)}`,
1587
+ { cause: error }
1588
+ );
1589
+ }
1590
+ }
1591
+ /**
1592
+ * Checkout a branch.
1593
+ *
1594
+ * @param target - The branch name to checkout
1595
+ * @returns Operation result
1596
+ */
1597
+ checkout(target) {
1598
+ try {
1599
+ const resultBytes = this.#wasm.exports.vcs_checkout(
1600
+ this.#handle.id,
1601
+ encoder.encode(target)
1602
+ );
1603
+ return unpackFromWasm(resultBytes);
1604
+ } catch (error) {
1605
+ throw new WasmError(
1606
+ `vcs_checkout failed: ${error instanceof Error ? error.message : String(error)}`,
1607
+ { cause: error }
1608
+ );
1609
+ }
1610
+ }
1611
+ /**
1612
+ * Merge a branch into the current branch.
1613
+ *
1614
+ * @param branchName - The branch to merge
1615
+ * @returns Operation result
1616
+ */
1617
+ merge(branchName) {
1618
+ try {
1619
+ const resultBytes = this.#wasm.exports.vcs_merge(
1620
+ this.#handle.id,
1621
+ encoder.encode(branchName)
1622
+ );
1623
+ return unpackFromWasm(resultBytes);
1624
+ } catch (error) {
1625
+ throw new WasmError(
1626
+ `vcs_merge failed: ${error instanceof Error ? error.message : String(error)}`,
1627
+ { cause: error }
1628
+ );
1629
+ }
1630
+ }
1631
+ /**
1632
+ * Stash the current working state.
1633
+ *
1634
+ * @returns Operation result
1635
+ */
1636
+ stash() {
1637
+ try {
1638
+ const resultBytes = this.#wasm.exports.vcs_stash(this.#handle.id);
1639
+ return unpackFromWasm(resultBytes);
1640
+ } catch (error) {
1641
+ throw new WasmError(
1642
+ `vcs_stash failed: ${error instanceof Error ? error.message : String(error)}`,
1643
+ { cause: error }
1644
+ );
1645
+ }
1646
+ }
1647
+ /**
1648
+ * Pop the most recent stash entry.
1649
+ *
1650
+ * @returns Operation result
1651
+ */
1652
+ stashPop() {
1653
+ try {
1654
+ const resultBytes = this.#wasm.exports.vcs_stash_pop(this.#handle.id);
1655
+ return unpackFromWasm(resultBytes);
1656
+ } catch (error) {
1657
+ throw new WasmError(
1658
+ `vcs_stash_pop failed: ${error instanceof Error ? error.message : String(error)}`,
1659
+ { cause: error }
1660
+ );
1661
+ }
1662
+ }
1663
+ /**
1664
+ * Find which commit introduced a vertex.
1665
+ *
1666
+ * @param vertexId - The vertex ID to blame
1667
+ * @returns Blame result with commit info
1668
+ */
1669
+ blame(vertexId) {
1670
+ try {
1671
+ const resultBytes = this.#wasm.exports.vcs_blame(
1672
+ this.#handle.id,
1673
+ encoder.encode(vertexId)
1674
+ );
1675
+ return unpackFromWasm(resultBytes);
1676
+ } catch (error) {
1677
+ throw new WasmError(
1678
+ `vcs_blame failed: ${error instanceof Error ? error.message : String(error)}`,
1679
+ { cause: error }
1680
+ );
1681
+ }
1682
+ }
1683
+ /** Release the WASM-side repository resource. */
1684
+ [Symbol.dispose]() {
1685
+ this.#handle[Symbol.dispose]();
1686
+ }
1687
+ }
837
1688
  class Panproto {
838
1689
  #wasm;
839
1690
  #protocols;
@@ -874,6 +1725,12 @@ class Panproto {
874
1725
  this.#protocols.set(name, proto);
875
1726
  return proto;
876
1727
  }
1728
+ const wasmSpec = getBuiltinProtocol(name, this.#wasm);
1729
+ if (wasmSpec) {
1730
+ const proto = defineProtocol(wasmSpec, this.#wasm);
1731
+ this.#protocols.set(name, proto);
1732
+ return proto;
1733
+ }
877
1734
  throw new PanprotoError(
878
1735
  `Protocol "${name}" not found. Register it with defineProtocol() first.`
879
1736
  );
@@ -934,6 +1791,31 @@ class Panproto {
934
1791
  compose(m1, m2) {
935
1792
  return composeMigrations(m1, m2, this.#wasm);
936
1793
  }
1794
+ /**
1795
+ * Compose two lenses into a single lens.
1796
+ *
1797
+ * The resulting lens is equivalent to applying `l1` then `l2`.
1798
+ *
1799
+ * @param l1 - First lens (applied first)
1800
+ * @param l2 - Second lens (applied second)
1801
+ * @returns A new LensHandle representing the composition
1802
+ * @throws {@link import('./types.js').WasmError} if composition fails
1803
+ */
1804
+ composeLenses(l1, l2) {
1805
+ try {
1806
+ const rawHandle = this.#wasm.exports.compose_lenses(
1807
+ l1._handle.id,
1808
+ l2._handle.id
1809
+ );
1810
+ const handle = createHandle(rawHandle, this.#wasm);
1811
+ return new LensHandle(handle, this.#wasm);
1812
+ } catch (error) {
1813
+ throw new WasmError(
1814
+ `compose_lenses failed: ${error instanceof Error ? error.message : String(error)}`,
1815
+ { cause: error }
1816
+ );
1817
+ }
1818
+ }
937
1819
  /**
938
1820
  * Diff two schemas and produce a compatibility report.
939
1821
  *
@@ -948,6 +1830,90 @@ class Panproto {
948
1830
  );
949
1831
  return unpackFromWasm(resultBytes);
950
1832
  }
1833
+ /** Diff two schemas using the full panproto-check engine (20+ change categories). */
1834
+ diffFull(oldSchema, newSchema) {
1835
+ const bytes = this.#wasm.exports.diff_schemas_full(
1836
+ oldSchema._handle.id,
1837
+ newSchema._handle.id
1838
+ );
1839
+ const data = unpackFromWasm(bytes);
1840
+ return new FullDiffReport(data, bytes, this.#wasm);
1841
+ }
1842
+ /** Normalize a schema by collapsing reference chains. Returns a new BuiltSchema. */
1843
+ normalize(schema) {
1844
+ const handle = this.#wasm.exports.normalize_schema(schema._handle.id);
1845
+ return BuiltSchema._fromHandle(handle, schema.data, schema.protocol, this.#wasm);
1846
+ }
1847
+ /** Validate a schema against its protocol's rules. */
1848
+ validateSchema(schema, protocol) {
1849
+ const bytes = this.#wasm.exports.validate_schema(
1850
+ schema._handle.id,
1851
+ protocol._handle.id
1852
+ );
1853
+ const issues = unpackFromWasm(bytes);
1854
+ return new ValidationResult(issues);
1855
+ }
1856
+ /**
1857
+ * Create an I/O protocol registry for parsing and emitting instances.
1858
+ *
1859
+ * The returned registry wraps all 77 built-in protocol codecs and
1860
+ * implements `Disposable` for automatic cleanup.
1861
+ *
1862
+ * @returns A new IoRegistry
1863
+ */
1864
+ io() {
1865
+ const rawHandle = this.#wasm.exports.register_io_protocols();
1866
+ const handle = createHandle(rawHandle, this.#wasm);
1867
+ return new IoRegistry(handle, this.#wasm);
1868
+ }
1869
+ /**
1870
+ * Parse JSON bytes into an Instance.
1871
+ *
1872
+ * Convenience method that wraps `json_to_instance`.
1873
+ *
1874
+ * @param schema - The schema the JSON data conforms to
1875
+ * @param json - JSON bytes or a JSON string
1876
+ * @returns A new Instance
1877
+ */
1878
+ parseJson(schema, json) {
1879
+ const jsonBytes = typeof json === "string" ? new TextEncoder().encode(json) : json;
1880
+ return Instance.fromJson(schema, jsonBytes, this.#wasm);
1881
+ }
1882
+ /**
1883
+ * Convert an Instance to JSON bytes.
1884
+ *
1885
+ * Convenience method that wraps `instance_to_json`.
1886
+ *
1887
+ * @param schema - The schema the instance conforms to
1888
+ * @param instance - The instance to convert
1889
+ * @returns JSON bytes
1890
+ */
1891
+ toJson(schema, instance) {
1892
+ return this.#wasm.exports.instance_to_json(
1893
+ schema._handle.id,
1894
+ instance._bytes
1895
+ );
1896
+ }
1897
+ /**
1898
+ * List all built-in protocol names.
1899
+ *
1900
+ * Returns the names of all 76 built-in protocols supported by the
1901
+ * WASM layer.
1902
+ *
1903
+ * @returns Array of protocol name strings
1904
+ */
1905
+ listProtocols() {
1906
+ return [...getProtocolNames(this.#wasm)];
1907
+ }
1908
+ /**
1909
+ * Initialize an in-memory VCS repository.
1910
+ *
1911
+ * @param protocolName - The protocol name for this repository
1912
+ * @returns A disposable VCS Repository
1913
+ */
1914
+ initRepo(protocolName) {
1915
+ return Repository.init(protocolName, this.#wasm);
1916
+ }
951
1917
  /**
952
1918
  * Release all WASM resources held by this instance.
953
1919
  *
@@ -961,53 +1927,165 @@ class Panproto {
961
1927
  this.#protocols.clear();
962
1928
  }
963
1929
  }
964
- function renameField(oldName, newName) {
965
- return { type: "rename-field", old: oldName, new: newName };
966
- }
967
- function addField(name, vertexKind, defaultValue) {
968
- return { type: "add-field", name, vertexKind, default: defaultValue };
969
- }
970
- function removeField(name) {
971
- return { type: "remove-field", name };
1930
+ class TheoryHandle {
1931
+ #handle;
1932
+ /** @internal Retained for future sort/op inspection methods. */
1933
+ _wasm;
1934
+ constructor(handle, wasm) {
1935
+ this.#handle = handle;
1936
+ this._wasm = wasm;
1937
+ }
1938
+ /** The WASM handle. Internal use only. */
1939
+ get _handle() {
1940
+ return this.#handle;
1941
+ }
1942
+ /** Release the WASM-side theory resource. */
1943
+ [Symbol.dispose]() {
1944
+ this.#handle[Symbol.dispose]();
1945
+ }
972
1946
  }
973
- function wrapInObject(fieldName) {
974
- return { type: "wrap-in-object", fieldName };
1947
+ class TheoryBuilder {
1948
+ #name;
1949
+ #extends;
1950
+ #sorts;
1951
+ #ops;
1952
+ #eqs;
1953
+ constructor(name) {
1954
+ this.#name = name;
1955
+ this.#extends = [];
1956
+ this.#sorts = [];
1957
+ this.#ops = [];
1958
+ this.#eqs = [];
1959
+ }
1960
+ /** Declare that this theory extends a parent theory. */
1961
+ extends(parentName) {
1962
+ this.#extends.push(parentName);
1963
+ return this;
1964
+ }
1965
+ /** Add a simple sort (no parameters). */
1966
+ sort(name) {
1967
+ this.#sorts.push({ name, params: [] });
1968
+ return this;
1969
+ }
1970
+ /** Add a dependent sort with parameters. */
1971
+ dependentSort(name, params) {
1972
+ this.#sorts.push({ name, params });
1973
+ return this;
1974
+ }
1975
+ /** Add an operation. */
1976
+ op(name, inputs, output) {
1977
+ this.#ops.push({ name, inputs, output });
1978
+ return this;
1979
+ }
1980
+ /** Add an equation (axiom). */
1981
+ eq(name, lhs, rhs) {
1982
+ this.#eqs.push({ name, lhs, rhs });
1983
+ return this;
1984
+ }
1985
+ /** Get the theory specification. */
1986
+ toSpec() {
1987
+ return {
1988
+ name: this.#name,
1989
+ extends: this.#extends,
1990
+ sorts: this.#sorts,
1991
+ ops: this.#ops,
1992
+ eqs: this.#eqs
1993
+ };
1994
+ }
1995
+ /**
1996
+ * Build the theory and register it in WASM.
1997
+ *
1998
+ * @param wasm - The WASM module
1999
+ * @returns A disposable TheoryHandle
2000
+ * @throws {@link PanprotoError} if the WASM call fails
2001
+ */
2002
+ build(wasm) {
2003
+ return createTheory(this.toSpec(), wasm);
2004
+ }
975
2005
  }
976
- function hoistField(host, field) {
977
- return { type: "hoist-field", host, field };
2006
+ function createTheory(spec, wasm) {
2007
+ try {
2008
+ const bytes = packToWasm(spec);
2009
+ const rawHandle = wasm.exports.create_theory(bytes);
2010
+ const handle = createHandle(rawHandle, wasm);
2011
+ return new TheoryHandle(handle, wasm);
2012
+ } catch (error) {
2013
+ throw new PanprotoError(
2014
+ `Failed to create theory "${spec.name}": ${error instanceof Error ? error.message : String(error)}`,
2015
+ { cause: error }
2016
+ );
2017
+ }
978
2018
  }
979
- function coerceType(fromKind, toKind) {
980
- return { type: "coerce-type", fromKind, toKind };
2019
+ function colimit(t1, t2, shared, wasm) {
2020
+ try {
2021
+ const rawHandle = wasm.exports.colimit_theories(
2022
+ t1._handle.id,
2023
+ t2._handle.id,
2024
+ shared._handle.id
2025
+ );
2026
+ const handle = createHandle(rawHandle, wasm);
2027
+ return new TheoryHandle(handle, wasm);
2028
+ } catch (error) {
2029
+ throw new WasmError(
2030
+ `colimit_theories failed: ${error instanceof Error ? error.message : String(error)}`,
2031
+ { cause: error }
2032
+ );
2033
+ }
981
2034
  }
982
- function compose(first, second) {
983
- return { type: "compose", first, second };
2035
+ function checkMorphism(morphism, domain, codomain, wasm) {
2036
+ const morphBytes = packToWasm(morphism);
2037
+ const resultBytes = wasm.exports.check_morphism(
2038
+ morphBytes,
2039
+ domain._handle.id,
2040
+ codomain._handle.id
2041
+ );
2042
+ return unpackFromWasm(resultBytes);
984
2043
  }
985
- function pipeline(combinators) {
986
- const [first, ...rest] = combinators;
987
- return rest.reduce((acc, c) => compose(acc, c), first);
2044
+ function migrateModel(sortInterp, morphism, wasm) {
2045
+ const modelBytes = packToWasm(sortInterp);
2046
+ const morphBytes = packToWasm(morphism);
2047
+ const resultBytes = wasm.exports.migrate_model(modelBytes, morphBytes);
2048
+ return unpackFromWasm(resultBytes);
988
2049
  }
989
2050
  export {
990
2051
  ATPROTO_SPEC,
991
2052
  BUILTIN_PROTOCOLS,
992
2053
  BuiltSchema,
2054
+ CompatReport,
993
2055
  CompiledMigration,
994
2056
  ExistenceCheckError,
2057
+ FullDiffReport,
995
2058
  GRAPHQL_SPEC,
2059
+ Instance,
2060
+ IoRegistry,
996
2061
  JSON_SCHEMA_SPEC,
2062
+ LensHandle,
997
2063
  MigrationBuilder,
998
2064
  MigrationError,
999
2065
  PROTOBUF_SPEC,
2066
+ PROTOCOL_CATEGORIES,
1000
2067
  Panproto,
1001
2068
  PanprotoError,
1002
2069
  Protocol,
2070
+ Repository,
1003
2071
  SQL_SPEC,
1004
2072
  SchemaBuilder,
1005
2073
  SchemaValidationError,
2074
+ TheoryBuilder,
2075
+ TheoryHandle,
2076
+ ValidationResult,
1006
2077
  WasmError,
1007
2078
  addField,
2079
+ checkMorphism,
1008
2080
  coerceType,
2081
+ colimit,
1009
2082
  compose,
2083
+ createTheory,
2084
+ fromCombinators,
2085
+ getBuiltinProtocol,
2086
+ getProtocolNames,
1010
2087
  hoistField,
2088
+ migrateModel,
1011
2089
  pipeline,
1012
2090
  removeField,
1013
2091
  renameField,