@calcit/procs 0.12.59 → 0.13.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.
Files changed (37) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/RFCs/08-04-strict-cirru-edn-decoding-rfc.md +342 -0
  3. package/RFCs/08-05-systematic-nil-reduction-rfc.md +174 -0
  4. package/RFCs/README.md +3 -1
  5. package/editing-history/202608050112-nil-reduction-foundation.md +33 -0
  6. package/editing-history/202608050943-pr-review-and-test-nil-reduction.md +29 -0
  7. package/editing-history/202608051010-explicit-unit-without-nil.md +30 -0
  8. package/editing-history/202608051720-nominal-option-api-migration.md +30 -0
  9. package/editing-history/202608051924-expand-option-collection-apis.md +31 -0
  10. package/editing-history/202608052014-js-nullish-boundary.md +29 -0
  11. package/editing-history/202608052255-nominal-reflection-and-destruct-apis.md +29 -0
  12. package/editing-history/202608060104-public-nil-contract-freeze.md +36 -0
  13. package/editing-history/202608060215-fix-hashmap-option-docs.md +4 -0
  14. package/editing-history/202608060220-release-0.13.0.md +5 -0
  15. package/history/202608040125-js-runtime-impl-identity.md +6 -0
  16. package/history/202608040241-strict-cirru-edn-decoding.md +27 -0
  17. package/history/202608040954-strict-edn-review-followups.md +16 -0
  18. package/history/202608041206-harden-js-impl-brand-check.md +5 -0
  19. package/history/202608041223-cover-inherited-js-impl-brand.md +4 -0
  20. package/history/202608041911-typed-data-shape-patch.md +30 -0
  21. package/history/202608042252-reject-decoded-edn-collisions.md +11 -0
  22. package/history/202608042338-strict-edn-js-collision-parity.md +21 -0
  23. package/history/202608042344-isolate-strict-edn-dependency-test.md +13 -0
  24. package/lib/calcit.procs.d.mts +35 -4
  25. package/lib/calcit.procs.mjs +233 -11
  26. package/lib/js-cirru.d.mts +2 -0
  27. package/lib/js-cirru.mjs +39 -17
  28. package/lib/js-impl.d.mts +1 -0
  29. package/lib/js-impl.mjs +28 -0
  30. package/lib/package.json +3 -2
  31. package/lib/typed-edn.d.mts +4 -0
  32. package/lib/typed-edn.mjs +5 -0
  33. package/package.json +3 -2
  34. package/ts-src/calcit.procs.mts +241 -13
  35. package/ts-src/js-cirru.mts +39 -19
  36. package/ts-src/js-impl.mts +33 -0
  37. package/ts-src/typed-edn.mts +7 -0
@@ -4,7 +4,7 @@ export const calcit_package_json = pkg;
4
4
  import { parse } from "@cirru/parser.ts";
5
5
  import { writeCirruCode } from "@cirru/writer.ts";
6
6
  import { CalcitSymbol, CalcitTag, CalcitRecur, castTag, newTag, refsRegistry, toString, getStringName, _$n__$e_, hashFunction, } from "./calcit-data.mjs";
7
- import { CalcitRef } from "./js-ref.mjs";
7
+ import { CalcitRef, atom } from "./js-ref.mjs";
8
8
  import { CalcitRecord } from "./js-record.mjs";
9
9
  import { CalcitImpl } from "./js-impl.mjs";
10
10
  import { CalcitStruct } from "./js-struct.mjs";
@@ -31,7 +31,8 @@ import { CalcitList, CalcitSliceList, foldl } from "./js-list.mjs";
31
31
  import { CalcitMap, CalcitSliceMap } from "./js-map.mjs";
32
32
  import { CalcitSet } from "./js-set.mjs";
33
33
  import { CalcitTuple } from "./js-tuple.mjs";
34
- import { to_calcit_data, extract_cirru_edn, CalcitCirruQuote } from "./js-cirru.mjs";
34
+ import { to_calcit_data, extract_cirru_edn, extract_cirru_edn_for_typed, CalcitCirruQuote } from "./js-cirru.mjs";
35
+ import { TypedEdnSetView } from "./typed-edn.mjs";
35
36
  let inNodeJs = typeof process !== "undefined" && process?.release?.name === "node";
36
37
  export let type_of = (x) => {
37
38
  if (typeof x === "string") {
@@ -256,7 +257,7 @@ export let _$n_impl_$o__$o_new = (name, ...pairs) => {
256
257
  let sourcePairs = pairs;
257
258
  if (pairs.length === 1 && pairs[0] instanceof CalcitImpl) {
258
259
  const sourceImpl = pairs[0];
259
- sourcePairs = sourceImpl.fields.map((field, idx) => new CalcitTuple(field, [sourceImpl.values[idx]], null));
260
+ sourcePairs = sourceImpl.fields.map((field, idx) => new CalcitTuple(newTag(field.value), [sourceImpl.values[idx]], null));
260
261
  }
261
262
  for (let idx = 0; idx < sourcePairs.length; idx++) {
262
263
  const pairValue = sourcePairs[idx];
@@ -856,6 +857,16 @@ export let _$n_list_$o_first = (xs) => {
856
857
  console.error(xs);
857
858
  throw new Error("Expected a list");
858
859
  };
860
+ export let _$n_list_$o_last = (xs) => {
861
+ if (xs instanceof CalcitList || xs instanceof CalcitSliceList) {
862
+ if (xs.isEmpty()) {
863
+ return null;
864
+ }
865
+ return xs.get(xs.len() - 1);
866
+ }
867
+ console.error(xs);
868
+ throw new Error("Expected a list");
869
+ };
859
870
  export let _$n_str_$o_first = (xs) => {
860
871
  if (typeof xs === "string") {
861
872
  return xs[0];
@@ -897,7 +908,7 @@ export let timeout_call = (duration, f) => {
897
908
  export let _$n_list_$o_rest = (xs) => {
898
909
  if (xs instanceof CalcitList || xs instanceof CalcitSliceList) {
899
910
  if (xs.len() === 0) {
900
- return null;
911
+ return xs.slice(0, 0);
901
912
  }
902
913
  return xs.rest();
903
914
  }
@@ -947,7 +958,7 @@ export let last = (xs) => {
947
958
  export let butlast = (xs) => {
948
959
  if (xs instanceof CalcitList || xs instanceof CalcitSliceList) {
949
960
  if (xs.len() === 0) {
950
- return null;
961
+ return xs.slice(0, 0);
951
962
  }
952
963
  return xs.slice(0, xs.len() - 1);
953
964
  }
@@ -1234,12 +1245,12 @@ export let _$n_str_$o_slice = (xs, m, n) => {
1234
1245
  export let _$n_str_$o_find_index = (x, y) => {
1235
1246
  return x.indexOf(y);
1236
1247
  };
1237
- export let parse_float = (x) => {
1238
- const value = parseFloat(x);
1239
- if (Number.isNaN(value)) {
1248
+ export let _$n_parse_float = (x) => {
1249
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(x)) {
1240
1250
  return null;
1241
1251
  }
1242
- return value;
1252
+ const value = Number(x);
1253
+ return Number.isNaN(value) ? null : value;
1243
1254
  };
1244
1255
  export let trim = (x, c) => {
1245
1256
  if (c != null) {
@@ -1304,7 +1315,7 @@ export let js_set = aset;
1304
1315
  export let js_delete = (obj, name) => {
1305
1316
  return delete obj[name];
1306
1317
  };
1307
- export let get_env = (name, v0) => {
1318
+ export let _$n_get_env = (name, v0) => {
1308
1319
  let v = undefined;
1309
1320
  if (inNodeJs) {
1310
1321
  // only available for Node.js
@@ -1319,8 +1330,12 @@ export let get_env = (name, v0) => {
1319
1330
  if (v == null && v0 == null) {
1320
1331
  console.warn(`(get-env "${name}"): config not found`);
1321
1332
  }
1322
- return v ?? v0;
1333
+ return v ?? v0 ?? null;
1323
1334
  };
1335
+ // Keep already-emitted JS bundles loadable across the nominal Option migration.
1336
+ // Newly generated code calls the internal raw proc; source-level `get-env`
1337
+ // remains the typed Option wrapper defined in calcit.core.
1338
+ export let get_env = _$n_get_env;
1324
1339
  export let turn_tag = (x) => {
1325
1340
  if (typeof x === "string") {
1326
1341
  return newTag(x);
@@ -1525,6 +1540,213 @@ export let parse_cirru_edn = (code, options) => {
1525
1540
  throw new Error(`Expected EDN in a single node, got ${nodes.length}`);
1526
1541
  }
1527
1542
  };
1543
+ const typed_edn_kind = (value) => {
1544
+ if (value == null)
1545
+ return "nil";
1546
+ if (typeof value === "boolean")
1547
+ return "bool";
1548
+ if (typeof value === "number")
1549
+ return "number";
1550
+ if (typeof value === "string")
1551
+ return "string";
1552
+ if (value instanceof CalcitSymbol)
1553
+ return "symbol";
1554
+ if (value instanceof CalcitTag)
1555
+ return "tag";
1556
+ if (value instanceof CalcitList || value instanceof CalcitSliceList)
1557
+ return "list";
1558
+ if (value instanceof CalcitMap || value instanceof CalcitSliceMap)
1559
+ return "map";
1560
+ if (value instanceof CalcitSet || value instanceof TypedEdnSetView)
1561
+ return "set";
1562
+ if (value instanceof CalcitRecord)
1563
+ return "record";
1564
+ if (value instanceof CalcitTuple)
1565
+ return value.enumPrototype == null ? "tuple" : "enum";
1566
+ if (value instanceof CalcitRef)
1567
+ return "atom";
1568
+ if (value instanceof CalcitCirruQuote)
1569
+ return "cirru-quote";
1570
+ if (value instanceof Uint8Array)
1571
+ return "buffer";
1572
+ return "unsupported";
1573
+ };
1574
+ const typed_edn_error = (path, message) => {
1575
+ throw new Error(`parse-cirru-edn-as failed at ${path}: ${message}`);
1576
+ };
1577
+ const enum_prototype_name = (value) => {
1578
+ return value instanceof CalcitEnum ? value.name() : value.name.value;
1579
+ };
1580
+ const decode_typed_edn_node = (graph, nodeId, input, path, depth) => {
1581
+ if (depth > 1024)
1582
+ typed_edn_error(path, "decode nesting exceeds 1024");
1583
+ const node = graph.nodes[nodeId];
1584
+ if (node == null)
1585
+ typed_edn_error(path, `invalid data shape node #${nodeId}`);
1586
+ switch (node.kind) {
1587
+ case "unit":
1588
+ if (input == null)
1589
+ return null;
1590
+ return typed_edn_error(path, `expected nil, got ${typed_edn_kind(input)}`);
1591
+ case "bool":
1592
+ if (typeof input === "boolean")
1593
+ return input;
1594
+ return typed_edn_error(path, `expected bool, got ${typed_edn_kind(input)}`);
1595
+ case "number":
1596
+ if (typeof input === "number")
1597
+ return input;
1598
+ return typed_edn_error(path, `expected number, got ${typed_edn_kind(input)}`);
1599
+ case "string":
1600
+ if (typeof input === "string")
1601
+ return input;
1602
+ return typed_edn_error(path, `expected string, got ${typed_edn_kind(input)}`);
1603
+ case "symbol":
1604
+ if (input instanceof CalcitSymbol)
1605
+ return input;
1606
+ return typed_edn_error(path, `expected symbol, got ${typed_edn_kind(input)}`);
1607
+ case "tag":
1608
+ if (input instanceof CalcitTag)
1609
+ return input;
1610
+ return typed_edn_error(path, `expected tag, got ${typed_edn_kind(input)}`);
1611
+ case "buffer":
1612
+ if (input instanceof Uint8Array)
1613
+ return input;
1614
+ return typed_edn_error(path, `expected buffer, got ${typed_edn_kind(input)}`);
1615
+ case "cirru-quote":
1616
+ if (input instanceof CalcitCirruQuote)
1617
+ return input;
1618
+ return typed_edn_error(path, `expected cirru-quote, got ${typed_edn_kind(input)}`);
1619
+ case "optional":
1620
+ return input == null ? null : decode_typed_edn_node(graph, node.inner, input, path, depth + 1);
1621
+ case "list": {
1622
+ if (!(input instanceof CalcitList || input instanceof CalcitSliceList)) {
1623
+ return typed_edn_error(path, `expected list, got ${typed_edn_kind(input)}`);
1624
+ }
1625
+ const values = Array.from(input.items()).map((value, idx) => decode_typed_edn_node(graph, node.inner, value, `${path}[${idx}]`, depth + 1));
1626
+ return new CalcitSliceList(values);
1627
+ }
1628
+ case "set": {
1629
+ if (!(input instanceof CalcitSet || input instanceof TypedEdnSetView)) {
1630
+ return typed_edn_error(path, `expected set, got ${typed_edn_kind(input)}`);
1631
+ }
1632
+ const values = [];
1633
+ const inputValues = input instanceof TypedEdnSetView ? input.items : input.values();
1634
+ inputValues.forEach((value) => {
1635
+ const itemPath = `${path}.item`;
1636
+ const decoded = decode_typed_edn_node(graph, node.inner, value, itemPath, depth + 1);
1637
+ if (values.some((existing) => _$n__$e_(existing, decoded))) {
1638
+ typed_edn_error(itemPath, "duplicate decoded set value");
1639
+ }
1640
+ values.push(decoded);
1641
+ });
1642
+ return new CalcitSet(values);
1643
+ }
1644
+ case "map": {
1645
+ if (!(input instanceof CalcitMap || input instanceof CalcitSliceMap)) {
1646
+ return typed_edn_error(path, `expected map, got ${typed_edn_kind(input)}`);
1647
+ }
1648
+ const entries = [];
1649
+ input.pairs().forEach(([key, value]) => {
1650
+ const keyPath = `${path}.key`;
1651
+ const decodedKey = decode_typed_edn_node(graph, node.key, key, keyPath, depth + 1);
1652
+ for (let idx = 0; idx < entries.length; idx += 2) {
1653
+ if (_$n__$e_(entries[idx], decodedKey)) {
1654
+ typed_edn_error(keyPath, "duplicate decoded map key");
1655
+ }
1656
+ }
1657
+ const decodedValue = decode_typed_edn_node(graph, node.value, value, `${path}.value`, depth + 1);
1658
+ entries.push(decodedKey, decodedValue);
1659
+ });
1660
+ return new CalcitSliceMap(entries);
1661
+ }
1662
+ case "ref":
1663
+ if (!(input instanceof CalcitRef))
1664
+ return typed_edn_error(path, `expected atom, got ${typed_edn_kind(input)}`);
1665
+ return atom(decode_typed_edn_node(graph, node.inner, input.value, `${path}.value`, depth + 1));
1666
+ case "struct": {
1667
+ if (!(input instanceof CalcitRecord)) {
1668
+ return typed_edn_error(path, `expected record :${node.nominal.name.value}, got ${typed_edn_kind(input)}`);
1669
+ }
1670
+ if (input.name.value !== node.nominal.name.value) {
1671
+ return typed_edn_error(path, `expected record :${node.nominal.name.value}, got record :${input.name.value}`);
1672
+ }
1673
+ const expectedNames = node.fields.map(([name]) => name);
1674
+ const actualNames = input.fields.map((field) => field.value);
1675
+ const missing = expectedNames.filter((name) => !actualNames.includes(name)).sort();
1676
+ const unknown = actualNames.filter((name) => !expectedNames.includes(name)).sort();
1677
+ if (missing.length > 0 || unknown.length > 0 || expectedNames.length !== actualNames.length) {
1678
+ return typed_edn_error(path, `record :${node.nominal.name.value} fields mismatch; missing [${missing.join(", ")}], unknown [${unknown.join(", ")}]`);
1679
+ }
1680
+ const decodedFields = new Map();
1681
+ node.fields.forEach(([name, fieldNode]) => {
1682
+ const idx = actualNames.indexOf(name);
1683
+ decodedFields.set(name, decode_typed_edn_node(graph, fieldNode, input.values[idx], `${path}.${name}`, depth + 1));
1684
+ });
1685
+ // Native declarations use lexical field order while the JS runtime keeps
1686
+ // its interned-tag order. Re-align values to the nominal JS declaration.
1687
+ if (decodedFields.size !== node.nominal.fields.length) {
1688
+ return typed_edn_error(path, `record :${node.nominal.name.value} decoder fields do not match its nominal declaration`);
1689
+ }
1690
+ const values = node.nominal.fields.map((field) => {
1691
+ if (!decodedFields.has(field.value)) {
1692
+ return typed_edn_error(path, `record :${node.nominal.name.value} is missing declared field :${field.value}`);
1693
+ }
1694
+ return decodedFields.get(field.value);
1695
+ });
1696
+ return new CalcitRecord(node.nominal.name, node.nominal.fields, values, node.nominal);
1697
+ }
1698
+ case "enum": {
1699
+ if (!(input instanceof CalcitTuple) || input.enumPrototype == null) {
1700
+ return typed_edn_error(path, `expected enum :${node.nominal.name()}, got ${typed_edn_kind(input)}`);
1701
+ }
1702
+ const actualEnumName = enum_prototype_name(input.enumPrototype);
1703
+ if (actualEnumName !== node.nominal.name()) {
1704
+ return typed_edn_error(path, `expected enum :${node.nominal.name()}, got enum :${actualEnumName}`);
1705
+ }
1706
+ if (!(input.tag instanceof CalcitTag)) {
1707
+ return typed_edn_error(path, `enum :${node.nominal.name()} variant must be a tag`);
1708
+ }
1709
+ const inputTag = input.tag;
1710
+ const variant = node.variants.find((candidate) => candidate.tag === inputTag.value);
1711
+ if (variant == null) {
1712
+ return typed_edn_error(path, `enum :${node.nominal.name()} has no variant :${inputTag.value}`);
1713
+ }
1714
+ if (variant.payload.length !== input.extra.length) {
1715
+ return typed_edn_error(path, `enum :${node.nominal.name()} variant :${variant.tag} expects ${variant.payload.length} payload(s), got ${input.extra.length}`);
1716
+ }
1717
+ const values = variant.payload.map((payloadNode, idx) => decode_typed_edn_node(graph, payloadNode, input.extra[idx], `${path}.payload[${idx}]`, depth + 1));
1718
+ return new CalcitTuple(newTag(variant.tag), values, node.nominal);
1719
+ }
1720
+ default:
1721
+ return typed_edn_error(path, `invalid data shape node kind: ${node.kind}`);
1722
+ }
1723
+ };
1724
+ export let parse_cirru_edn_as = (code, graph) => {
1725
+ if (typeof code !== "string")
1726
+ throw new Error(`parse-cirru-edn-as expected a string, got ${typed_edn_kind(code)}`);
1727
+ if (graph.version !== 1)
1728
+ throw new Error(`parse-cirru-edn-as expected data shape ABI version 1, got ${graph.version}`);
1729
+ if (typeof graph.fingerprint !== "string" || graph.fingerprint.length === 0) {
1730
+ throw new Error("parse-cirru-edn-as expected a non-empty data shape fingerprint");
1731
+ }
1732
+ const enumOptions = [];
1733
+ for (const node of graph.nodes) {
1734
+ if (node.kind === "enum")
1735
+ enumOptions.push(newTag(node.nominal.name()), node.nominal);
1736
+ }
1737
+ let input;
1738
+ try {
1739
+ const nodes = parse(code);
1740
+ if (nodes.length !== 1)
1741
+ throw new Error(`expected EDN in a single node, got ${nodes.length}`);
1742
+ input = extract_cirru_edn_for_typed(nodes[0], new CalcitSliceMap(enumOptions));
1743
+ }
1744
+ catch (error) {
1745
+ const message = error instanceof Error ? error.message : `${error}`;
1746
+ throw new Error(`parse-cirru-edn-as failed to parse Cirru EDN: ${message}`);
1747
+ }
1748
+ return decode_typed_edn_node(graph, graph.root, input, "$", 0);
1749
+ };
1528
1750
  const json_to_calcit = (value) => {
1529
1751
  if (value == null)
1530
1752
  return null;
@@ -1,6 +1,7 @@
1
1
  import { CirruWriterNode } from "@cirru/writer.ts";
2
2
  import { CalcitValue } from "./js-primes.mjs";
3
3
  import { CalcitList } from "./js-list.mjs";
4
+ import { TypedEdnSetView } from "./typed-edn.mjs";
4
5
  type CirruEdnFormat = string | CirruEdnFormat[];
5
6
  export declare class CalcitCirruQuote {
6
7
  value: CirruWriterNode[];
@@ -16,6 +17,7 @@ export declare let format_cirru_one_liner: (data: CalcitCirruQuote | CalcitList)
16
17
  /** better use string version of Cirru EDN in future */
17
18
  export declare let to_cirru_edn: (x: CalcitValue) => CirruEdnFormat;
18
19
  export declare let extract_cirru_edn: (x: CirruEdnFormat, options: CalcitValue) => CalcitValue;
20
+ export declare let extract_cirru_edn_for_typed: (x: CirruEdnFormat, options: CalcitValue) => CalcitValue | TypedEdnSetView;
19
21
  export declare let format_cirru_edn: (data: CalcitValue, useInline?: boolean) => string;
20
22
  export declare let to_calcit_data: (x: any, noKeyword?: boolean) => CalcitValue;
21
23
  /** deep compare cirru array */
package/lib/js-cirru.mjs CHANGED
@@ -11,6 +11,7 @@ import { CalcitImpl } from "./js-impl.mjs";
11
11
  import { CalcitRef } from "./js-ref.mjs";
12
12
  import { deepEqual } from "@calcit/ternary-tree/lib/utils.mjs";
13
13
  import { atom } from "./js-ref.mjs";
14
+ import { TypedEdnSetView } from "./typed-edn.mjs";
14
15
  export class CalcitCirruQuote {
15
16
  constructor(value) {
16
17
  if (value == null) {
@@ -260,7 +261,7 @@ const tag_to_string = (tag) => {
260
261
  return tag.name.toString();
261
262
  throw new Error(`Unsupported tag for EDN: ${tag}`);
262
263
  };
263
- export let extract_cirru_edn = (x, options) => {
264
+ const extract_cirru_edn_inner = (x, options, preserveSourceEntries) => {
264
265
  if (typeof x === "string") {
265
266
  if (x === "nil") {
266
267
  return null;
@@ -295,6 +296,7 @@ export let extract_cirru_edn = (x, options) => {
295
296
  }
296
297
  if (x[0] === "{}") {
297
298
  let result = [];
299
+ const sourceKeys = [];
298
300
  x.forEach((pair, idx) => {
299
301
  if (idx === 0) {
300
302
  return; // skip first `{}` symbol
@@ -303,7 +305,17 @@ export let extract_cirru_edn = (x, options) => {
303
305
  if (pair[0] === ";")
304
306
  return;
305
307
  if (pair.length === 2) {
306
- result.push(extract_cirru_edn(pair[0], options), extract_cirru_edn(pair[1], options));
308
+ const key = extract_cirru_edn_inner(pair[0], options, preserveSourceEntries);
309
+ const value = extract_cirru_edn_inner(pair[1], options, preserveSourceEntries);
310
+ const existingIdx = preserveSourceEntries ? sourceKeys.findIndex((sourceKey) => deepEqual(sourceKey, pair[0])) : -1;
311
+ if (existingIdx >= 0) {
312
+ result[(existingIdx << 1) + 1] = value;
313
+ }
314
+ else {
315
+ if (preserveSourceEntries)
316
+ sourceKeys.push(pair[0]);
317
+ result.push(key, value);
318
+ }
307
319
  }
308
320
  else {
309
321
  throw new Error(`Expected a pair, got: ${pair}`);
@@ -331,7 +343,10 @@ export let extract_cirru_edn = (x, options) => {
331
343
  return;
332
344
  if (pair.length === 2) {
333
345
  if (typeof pair[0] === "string") {
334
- entries.push([extractFieldTag(pair[0]), extract_cirru_edn(pair[1], options)]);
346
+ entries.push([
347
+ extractFieldTag(pair[0]),
348
+ extract_cirru_edn_inner(pair[1], options, preserveSourceEntries),
349
+ ]);
335
350
  }
336
351
  else {
337
352
  throw new Error(`Expected string as field, got: ${pair}`);
@@ -357,10 +372,9 @@ export let extract_cirru_edn = (x, options) => {
357
372
  if (options instanceof CalcitMap || options instanceof CalcitSliceMap) {
358
373
  let v = options.get(extractFieldTag(name));
359
374
  if (v != null && v instanceof CalcitRecord) {
360
- if (!deepEqual(v.fields, fields)) {
361
- throw new Error(`Fields mismatch for ${name}, expected ${fields}, got ${v.fields}`);
375
+ if (deepEqual(v.fields, fields)) {
376
+ return new CalcitRecord(extractFieldTag(name), fields, values, v.structRef);
362
377
  }
363
- return new CalcitRecord(extractFieldTag(name), fields, values, v.structRef);
364
378
  }
365
379
  }
366
380
  return new CalcitRecord(extractFieldTag(name), fields, values);
@@ -375,16 +389,18 @@ export let extract_cirru_edn = (x, options) => {
375
389
  return new CalcitSliceList(x
376
390
  .slice(1)
377
391
  .filter(notComment)
378
- .map((x) => extract_cirru_edn(x, options)));
392
+ .map((x) => extract_cirru_edn_inner(x, options, preserveSourceEntries)));
379
393
  }
380
394
  if (x[0] === "#{}") {
381
- return new CalcitSet(x
382
- .slice(1)
383
- .filter(notComment)
384
- .map((x) => extract_cirru_edn(x, options)));
395
+ const sourceItems = x.slice(1).filter(notComment);
396
+ const uniqueSourceItems = preserveSourceEntries
397
+ ? sourceItems.filter((item, idx) => !sourceItems.slice(0, idx).some((existing) => deepEqual(existing, item)))
398
+ : sourceItems;
399
+ const items = uniqueSourceItems.map((x) => extract_cirru_edn_inner(x, options, preserveSourceEntries));
400
+ return preserveSourceEntries ? new TypedEdnSetView(items) : new CalcitSet(items);
385
401
  }
386
402
  if (x[0] === "do" && x.length === 2) {
387
- return extract_cirru_edn(x[1], options);
403
+ return extract_cirru_edn_inner(x[1], options, preserveSourceEntries);
388
404
  }
389
405
  if (x[0] === "quote") {
390
406
  if (x.length !== 2) {
@@ -396,10 +412,10 @@ export let extract_cirru_edn = (x, options) => {
396
412
  if (x.length < 2) {
397
413
  throw new Error(`tuple expects at least 1 value, got: ${x}`);
398
414
  }
399
- return new CalcitTuple(extract_cirru_edn(x[1], options), x
415
+ return new CalcitTuple(extract_cirru_edn_inner(x[1], options, preserveSourceEntries), x
400
416
  .slice(2)
401
417
  .filter(notComment)
402
- .map((x) => extract_cirru_edn(x, options)));
418
+ .map((x) => extract_cirru_edn_inner(x, options, preserveSourceEntries)));
403
419
  }
404
420
  if (x[0] === "%::") {
405
421
  if (x.length < 3) {
@@ -413,21 +429,27 @@ export let extract_cirru_edn = (x, options) => {
413
429
  // unwrap prototype to record then extract name
414
430
  const proto = enumPrototype != null ? unwrap_enum_prototype_local(enumPrototype) : null;
415
431
  const enumTag = proto != null ? proto.name.toString() : enumName;
416
- return new CalcitTuple(extract_cirru_edn(x[2], options), x
432
+ return new CalcitTuple(extract_cirru_edn_inner(x[2], options, preserveSourceEntries), x
417
433
  .slice(3)
418
434
  .filter(notComment)
419
- .map((x) => extract_cirru_edn(x, options)), enumPrototype);
435
+ .map((x) => extract_cirru_edn_inner(x, options, preserveSourceEntries)), enumPrototype);
420
436
  }
421
437
  if (x[0] === "atom") {
422
438
  if (x.length !== 2) {
423
439
  throw new Error(`atom expects 1 argument, got: ${x}`);
424
440
  }
425
- return atom(extract_cirru_edn(x[1], options));
441
+ return atom(extract_cirru_edn_inner(x[1], options, preserveSourceEntries));
426
442
  }
427
443
  }
428
444
  console.error(x);
429
445
  throw new Error(`Unexpected data from EDN: ${x}`);
430
446
  };
447
+ export let extract_cirru_edn = (x, options) => {
448
+ return extract_cirru_edn_inner(x, options, false);
449
+ };
450
+ export let extract_cirru_edn_for_typed = (x, options) => {
451
+ return extract_cirru_edn_inner(x, options, true);
452
+ };
431
453
  export let format_cirru_edn = (data, useInline = true) => {
432
454
  if (data == null) {
433
455
  return "\ndo nil" + "\n";
package/lib/js-impl.d.mts CHANGED
@@ -8,6 +8,7 @@ export declare class CalcitImpl {
8
8
  fields: Array<CalcitTag>;
9
9
  values: Array<CalcitValue>;
10
10
  cachedHash: Hash;
11
+ static [Symbol.hasInstance](value: unknown): boolean;
11
12
  constructor(name: CalcitTag, fields: Array<CalcitTag>, values: Array<CalcitValue>, origin?: CalcitTrait | null);
12
13
  get(k: CalcitValue): CalcitValue;
13
14
  getOrNil(k: CalcitValue): CalcitValue;
package/lib/js-impl.mjs CHANGED
@@ -1,6 +1,34 @@
1
1
  import { castTag, findInFields, toString } from "./calcit-data.mjs";
2
+ const CALCIT_IMPL_BRAND = Symbol.for("@calcit/procs/CalcitImpl");
2
3
  export class CalcitImpl {
4
+ static [Symbol.hasInstance](value) {
5
+ if (typeof value !== "object" || value === null)
6
+ return false;
7
+ const candidate = value;
8
+ const brand = Object.getOwnPropertyDescriptor(candidate, CALCIT_IMPL_BRAND);
9
+ const name = Object.getOwnPropertyDescriptor(candidate, "name");
10
+ const origin = Object.getOwnPropertyDescriptor(candidate, "origin");
11
+ const fields = Object.getOwnPropertyDescriptor(candidate, "fields");
12
+ const values = Object.getOwnPropertyDescriptor(candidate, "values");
13
+ const cachedHash = Object.getOwnPropertyDescriptor(candidate, "cachedHash");
14
+ return (brand?.value === true &&
15
+ name !== undefined &&
16
+ origin !== undefined &&
17
+ fields !== undefined &&
18
+ values !== undefined &&
19
+ cachedHash !== undefined &&
20
+ typeof name.value === "object" &&
21
+ name.value !== null &&
22
+ typeof name.value.value === "string" &&
23
+ (origin.value === null || typeof origin.value === "object") &&
24
+ Array.isArray(fields.value) &&
25
+ Array.isArray(values.value));
26
+ }
3
27
  constructor(name, fields, values, origin = null) {
28
+ // Vite can temporarily retain two copies of @calcit/procs while refreshing
29
+ // optimized dependencies. A global, non-enumerable brand keeps impl values
30
+ // recognizable across those otherwise distinct module instances.
31
+ Object.defineProperty(this, CALCIT_IMPL_BRAND, { value: true });
4
32
  this.name = name;
5
33
  this.origin = origin;
6
34
  this.fields = fields;
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.12.59",
3
+ "version": "0.13.0",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",
@@ -16,9 +16,10 @@
16
16
  "test-rs": "cargo test -q",
17
17
  "test-fail": "cargo test -q --bin cr type_fail_",
18
18
  "test-snippets": "cargo test -q snippets::tests",
19
+ "check-js-runtime": "node scripts/check-js-runtime-identity.mjs",
19
20
  "bench-recur-smoke": "cargo run --bin cr -- calcit/test.cirru js && node --input-type=module -e \"import { test_loop } from './js-out/test-recursion.main.mjs'; const n=3000; const t0=process.hrtime.bigint(); for(let i=0;i<n;i++) test_loop(); const dt=Number(process.hrtime.bigint()-t0)/1e6; console.log('test_loop_ms='+dt.toFixed(3));\"",
20
21
  "check-smooth": "yarn fmt-rs && yarn lint-rs && yarn test-rs && yarn check-all",
21
- "check-all": "yarn compile && yarn check-agent-interface && yarn try-rs && yarn try-js && yarn try-ir && yarn try-wasm",
22
+ "check-all": "yarn compile && yarn check-js-runtime && yarn check-agent-interface && yarn try-rs && yarn try-js && yarn try-ir && yarn try-wasm",
22
23
  "check-agent-interface": "cargo build --bin cr && node scripts/check-agent-interface.mjs",
23
24
  "try-all": "yarn check-all",
24
25
  "try-rs": "cargo run --bin cr -- calcit/test.cirru",
@@ -0,0 +1,4 @@
1
+ export declare class TypedEdnSetView {
2
+ readonly items: any[];
3
+ constructor(items: any[]);
4
+ }
@@ -0,0 +1,5 @@
1
+ export class TypedEdnSetView {
2
+ constructor(items) {
3
+ this.items = items;
4
+ }
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.12.59",
3
+ "version": "0.13.0",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "devDependencies": {
6
6
  "@types/node": "^25.7.0",
@@ -16,9 +16,10 @@
16
16
  "test-rs": "cargo test -q",
17
17
  "test-fail": "cargo test -q --bin cr type_fail_",
18
18
  "test-snippets": "cargo test -q snippets::tests",
19
+ "check-js-runtime": "node scripts/check-js-runtime-identity.mjs",
19
20
  "bench-recur-smoke": "cargo run --bin cr -- calcit/test.cirru js && node --input-type=module -e \"import { test_loop } from './js-out/test-recursion.main.mjs'; const n=3000; const t0=process.hrtime.bigint(); for(let i=0;i<n;i++) test_loop(); const dt=Number(process.hrtime.bigint()-t0)/1e6; console.log('test_loop_ms='+dt.toFixed(3));\"",
20
21
  "check-smooth": "yarn fmt-rs && yarn lint-rs && yarn test-rs && yarn check-all",
21
- "check-all": "yarn compile && yarn check-agent-interface && yarn try-rs && yarn try-js && yarn try-ir && yarn try-wasm",
22
+ "check-all": "yarn compile && yarn check-js-runtime && yarn check-agent-interface && yarn try-rs && yarn try-js && yarn try-ir && yarn try-wasm",
22
23
  "check-agent-interface": "cargo build --bin cr && node scripts/check-agent-interface.mjs",
23
24
  "try-all": "yarn check-all",
24
25
  "try-rs": "cargo run --bin cr -- calcit/test.cirru",