@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
@@ -21,7 +21,7 @@ import {
21
21
  hashFunction,
22
22
  } from "./calcit-data.mjs";
23
23
 
24
- import { CalcitRef } from "./js-ref.mjs";
24
+ import { CalcitRef, atom } from "./js-ref.mjs";
25
25
  import { CalcitRecord } from "./js-record.mjs";
26
26
  import { CalcitImpl } from "./js-impl.mjs";
27
27
  import { CalcitStruct } from "./js-struct.mjs";
@@ -50,7 +50,8 @@ import { CalcitList, CalcitSliceList, foldl } from "./js-list.mjs";
50
50
  import { CalcitMap, CalcitSliceMap } from "./js-map.mjs";
51
51
  import { CalcitSet } from "./js-set.mjs";
52
52
  import { CalcitTuple } from "./js-tuple.mjs";
53
- import { to_calcit_data, extract_cirru_edn, CalcitCirruQuote } from "./js-cirru.mjs";
53
+ import { to_calcit_data, extract_cirru_edn, extract_cirru_edn_for_typed, CalcitCirruQuote } from "./js-cirru.mjs";
54
+ import { TypedEdnSetView } from "./typed-edn.mjs";
54
55
 
55
56
  let inNodeJs = typeof process !== "undefined" && process?.release?.name === "node";
56
57
 
@@ -299,7 +300,9 @@ export let _$n_impl_$o__$o_new = (name: CalcitValue, ...pairs: CalcitValue[]): C
299
300
  let sourcePairs = pairs;
300
301
  if (pairs.length === 1 && pairs[0] instanceof CalcitImpl) {
301
302
  const sourceImpl = pairs[0];
302
- sourcePairs = sourceImpl.fields.map((field, idx) => new CalcitTuple(field, [sourceImpl.values[idx]], null));
303
+ sourcePairs = sourceImpl.fields.map(
304
+ (field, idx) => new CalcitTuple(newTag(field.value), [sourceImpl.values[idx]], null)
305
+ );
303
306
  }
304
307
  for (let idx = 0; idx < sourcePairs.length; idx++) {
305
308
  const pairValue = sourcePairs[idx];
@@ -939,6 +942,18 @@ export let _$n_list_$o_first = (xs: CalcitValue): CalcitValue => {
939
942
  console.error(xs);
940
943
  throw new Error("Expected a list");
941
944
  };
945
+
946
+ export let _$n_list_$o_last = (xs: CalcitValue): CalcitValue => {
947
+ if (xs instanceof CalcitList || xs instanceof CalcitSliceList) {
948
+ if (xs.isEmpty()) {
949
+ return null;
950
+ }
951
+ return xs.get(xs.len() - 1);
952
+ }
953
+ console.error(xs);
954
+ throw new Error("Expected a list");
955
+ };
956
+
942
957
  export let _$n_str_$o_first = (xs: CalcitValue): CalcitValue => {
943
958
  if (typeof xs === "string") {
944
959
  return xs[0];
@@ -980,10 +995,10 @@ export let timeout_call = (duration: number, f: CalcitFn): null => {
980
995
  return null;
981
996
  };
982
997
 
983
- export let _$n_list_$o_rest = (xs: CalcitValue): CalcitValue => {
998
+ export let _$n_list_$o_rest = (xs: CalcitValue): CalcitList | CalcitSliceList => {
984
999
  if (xs instanceof CalcitList || xs instanceof CalcitSliceList) {
985
1000
  if (xs.len() === 0) {
986
- return null;
1001
+ return xs.slice(0, 0);
987
1002
  }
988
1003
  return xs.rest();
989
1004
  }
@@ -1038,10 +1053,10 @@ export let last = (xs: CalcitValue): CalcitValue => {
1038
1053
  throw new Error("Data not ready for last");
1039
1054
  };
1040
1055
 
1041
- export let butlast = (xs: CalcitValue): CalcitValue => {
1056
+ export let butlast = (xs: CalcitValue): CalcitList | CalcitSliceList | string => {
1042
1057
  if (xs instanceof CalcitList || xs instanceof CalcitSliceList) {
1043
1058
  if (xs.len() === 0) {
1044
- return null;
1059
+ return xs.slice(0, 0);
1045
1060
  }
1046
1061
  return xs.slice(0, xs.len() - 1);
1047
1062
  }
@@ -1346,12 +1361,12 @@ export let _$n_str_$o_find_index = (x: string, y: string): number => {
1346
1361
  return x.indexOf(y);
1347
1362
  };
1348
1363
 
1349
- export let parse_float = (x: string): number | null => {
1350
- const value = parseFloat(x);
1351
- if (Number.isNaN(value)) {
1364
+ export let _$n_parse_float = (x: string): number | null => {
1365
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(x)) {
1352
1366
  return null;
1353
1367
  }
1354
- return value;
1368
+ const value = Number(x);
1369
+ return Number.isNaN(value) ? null : value;
1355
1370
  };
1356
1371
  export let trim = (x: string, c: string): string => {
1357
1372
  if (c != null) {
@@ -1422,7 +1437,7 @@ export let js_delete = (obj: any, name: string): any => {
1422
1437
  return delete obj[name];
1423
1438
  };
1424
1439
 
1425
- export let get_env = (name: string, v0: string): string => {
1440
+ export let _$n_get_env = (name: string, v0?: CalcitValue): CalcitValue => {
1426
1441
  let v = undefined;
1427
1442
  if (inNodeJs) {
1428
1443
  // only available for Node.js
@@ -1436,9 +1451,14 @@ export let get_env = (name: string, v0: string): string => {
1436
1451
  if (v == null && v0 == null) {
1437
1452
  console.warn(`(get-env "${name}"): config not found`);
1438
1453
  }
1439
- return v ?? v0;
1454
+ return v ?? v0 ?? null;
1440
1455
  };
1441
1456
 
1457
+ // Keep already-emitted JS bundles loadable across the nominal Option migration.
1458
+ // Newly generated code calls the internal raw proc; source-level `get-env`
1459
+ // remains the typed Option wrapper defined in calcit.core.
1460
+ export let get_env = _$n_get_env;
1461
+
1442
1462
  export let turn_tag = (x: CalcitValue): CalcitTag => {
1443
1463
  if (typeof x === "string") {
1444
1464
  return newTag(x);
@@ -1656,6 +1676,214 @@ export let parse_cirru_edn = (code: string, options: CalcitValue) => {
1656
1676
  }
1657
1677
  };
1658
1678
 
1679
+ type DataShapeNode =
1680
+ | { kind: "unit" | "bool" | "number" | "string" | "symbol" | "tag" | "buffer" | "cirru-quote" }
1681
+ | { kind: "optional" | "list" | "set" | "ref"; inner: number }
1682
+ | { kind: "map"; key: number; value: number }
1683
+ | { kind: "struct"; nominal: CalcitStruct; fields: Array<[string, number]> }
1684
+ | { kind: "enum"; nominal: CalcitEnum; variants: Array<{ tag: string; payload: number[] }> };
1685
+
1686
+ type DataShapeGraph = {
1687
+ version: number;
1688
+ root: number;
1689
+ fingerprint: string;
1690
+ nodes: DataShapeNode[];
1691
+ };
1692
+
1693
+ const typed_edn_kind = (value: any): string => {
1694
+ if (value == null) return "nil";
1695
+ if (typeof value === "boolean") return "bool";
1696
+ if (typeof value === "number") return "number";
1697
+ if (typeof value === "string") return "string";
1698
+ if (value instanceof CalcitSymbol) return "symbol";
1699
+ if (value instanceof CalcitTag) return "tag";
1700
+ if (value instanceof CalcitList || value instanceof CalcitSliceList) return "list";
1701
+ if (value instanceof CalcitMap || value instanceof CalcitSliceMap) return "map";
1702
+ if (value instanceof CalcitSet || value instanceof TypedEdnSetView) return "set";
1703
+ if (value instanceof CalcitRecord) return "record";
1704
+ if (value instanceof CalcitTuple) return value.enumPrototype == null ? "tuple" : "enum";
1705
+ if (value instanceof CalcitRef) return "atom";
1706
+ if (value instanceof CalcitCirruQuote) return "cirru-quote";
1707
+ if (value instanceof Uint8Array) return "buffer";
1708
+ return "unsupported";
1709
+ };
1710
+
1711
+ const typed_edn_error = (path: string, message: string): never => {
1712
+ throw new Error(`parse-cirru-edn-as failed at ${path}: ${message}`);
1713
+ };
1714
+
1715
+ const enum_prototype_name = (value: CalcitEnum | CalcitRecord): string => {
1716
+ return value instanceof CalcitEnum ? value.name() : value.name.value;
1717
+ };
1718
+
1719
+ const decode_typed_edn_node = (graph: DataShapeGraph, nodeId: number, input: any, path: string, depth: number): any => {
1720
+ if (depth > 1024) typed_edn_error(path, "decode nesting exceeds 1024");
1721
+ const node = graph.nodes[nodeId];
1722
+ if (node == null) typed_edn_error(path, `invalid data shape node #${nodeId}`);
1723
+
1724
+ switch (node.kind) {
1725
+ case "unit":
1726
+ if (input == null) return null;
1727
+ return typed_edn_error(path, `expected nil, got ${typed_edn_kind(input)}`);
1728
+ case "bool":
1729
+ if (typeof input === "boolean") return input;
1730
+ return typed_edn_error(path, `expected bool, got ${typed_edn_kind(input)}`);
1731
+ case "number":
1732
+ if (typeof input === "number") return input;
1733
+ return typed_edn_error(path, `expected number, got ${typed_edn_kind(input)}`);
1734
+ case "string":
1735
+ if (typeof input === "string") return input;
1736
+ return typed_edn_error(path, `expected string, got ${typed_edn_kind(input)}`);
1737
+ case "symbol":
1738
+ if (input instanceof CalcitSymbol) return input;
1739
+ return typed_edn_error(path, `expected symbol, got ${typed_edn_kind(input)}`);
1740
+ case "tag":
1741
+ if (input instanceof CalcitTag) return input;
1742
+ return typed_edn_error(path, `expected tag, got ${typed_edn_kind(input)}`);
1743
+ case "buffer":
1744
+ if (input instanceof Uint8Array) return input;
1745
+ return typed_edn_error(path, `expected buffer, got ${typed_edn_kind(input)}`);
1746
+ case "cirru-quote":
1747
+ if (input instanceof CalcitCirruQuote) return input;
1748
+ return typed_edn_error(path, `expected cirru-quote, got ${typed_edn_kind(input)}`);
1749
+ case "optional":
1750
+ return input == null ? null : decode_typed_edn_node(graph, node.inner, input, path, depth + 1);
1751
+ case "list": {
1752
+ if (!(input instanceof CalcitList || input instanceof CalcitSliceList)) {
1753
+ return typed_edn_error(path, `expected list, got ${typed_edn_kind(input)}`);
1754
+ }
1755
+ const values = Array.from(input.items()).map((value, idx) =>
1756
+ decode_typed_edn_node(graph, node.inner, value, `${path}[${idx}]`, depth + 1)
1757
+ );
1758
+ return new CalcitSliceList(values);
1759
+ }
1760
+ case "set": {
1761
+ if (!(input instanceof CalcitSet || input instanceof TypedEdnSetView)) {
1762
+ return typed_edn_error(path, `expected set, got ${typed_edn_kind(input)}`);
1763
+ }
1764
+ const values: any[] = [];
1765
+ const inputValues = input instanceof TypedEdnSetView ? input.items : input.values();
1766
+ inputValues.forEach((value) => {
1767
+ const itemPath = `${path}.item`;
1768
+ const decoded = decode_typed_edn_node(graph, node.inner, value, itemPath, depth + 1);
1769
+ if (values.some((existing) => _$n__$e_(existing, decoded))) {
1770
+ typed_edn_error(itemPath, "duplicate decoded set value");
1771
+ }
1772
+ values.push(decoded);
1773
+ });
1774
+ return new CalcitSet(values);
1775
+ }
1776
+ case "map": {
1777
+ if (!(input instanceof CalcitMap || input instanceof CalcitSliceMap)) {
1778
+ return typed_edn_error(path, `expected map, got ${typed_edn_kind(input)}`);
1779
+ }
1780
+ const entries: any[] = [];
1781
+ input.pairs().forEach(([key, value]) => {
1782
+ const keyPath = `${path}.key`;
1783
+ const decodedKey = decode_typed_edn_node(graph, node.key, key, keyPath, depth + 1);
1784
+ for (let idx = 0; idx < entries.length; idx += 2) {
1785
+ if (_$n__$e_(entries[idx], decodedKey)) {
1786
+ typed_edn_error(keyPath, "duplicate decoded map key");
1787
+ }
1788
+ }
1789
+ const decodedValue = decode_typed_edn_node(graph, node.value, value, `${path}.value`, depth + 1);
1790
+ entries.push(decodedKey, decodedValue);
1791
+ });
1792
+ return new CalcitSliceMap(entries);
1793
+ }
1794
+ case "ref":
1795
+ if (!(input instanceof CalcitRef)) return typed_edn_error(path, `expected atom, got ${typed_edn_kind(input)}`);
1796
+ return atom(decode_typed_edn_node(graph, node.inner, input.value, `${path}.value`, depth + 1));
1797
+ case "struct": {
1798
+ if (!(input instanceof CalcitRecord)) {
1799
+ return typed_edn_error(path, `expected record :${node.nominal.name.value}, got ${typed_edn_kind(input)}`);
1800
+ }
1801
+ if (input.name.value !== node.nominal.name.value) {
1802
+ return typed_edn_error(path, `expected record :${node.nominal.name.value}, got record :${input.name.value}`);
1803
+ }
1804
+ const expectedNames = node.fields.map(([name]) => name);
1805
+ const actualNames = input.fields.map((field) => field.value);
1806
+ const missing = expectedNames.filter((name) => !actualNames.includes(name)).sort();
1807
+ const unknown = actualNames.filter((name) => !expectedNames.includes(name)).sort();
1808
+ if (missing.length > 0 || unknown.length > 0 || expectedNames.length !== actualNames.length) {
1809
+ return typed_edn_error(
1810
+ path,
1811
+ `record :${node.nominal.name.value} fields mismatch; missing [${missing.join(", ")}], unknown [${unknown.join(", ")}]`
1812
+ );
1813
+ }
1814
+ const decodedFields = new Map<string, CalcitValue>();
1815
+ node.fields.forEach(([name, fieldNode]) => {
1816
+ const idx = actualNames.indexOf(name);
1817
+ decodedFields.set(name, decode_typed_edn_node(graph, fieldNode, input.values[idx], `${path}.${name}`, depth + 1));
1818
+ });
1819
+ // Native declarations use lexical field order while the JS runtime keeps
1820
+ // its interned-tag order. Re-align values to the nominal JS declaration.
1821
+ if (decodedFields.size !== node.nominal.fields.length) {
1822
+ return typed_edn_error(path, `record :${node.nominal.name.value} decoder fields do not match its nominal declaration`);
1823
+ }
1824
+ const values = node.nominal.fields.map((field) => {
1825
+ if (!decodedFields.has(field.value)) {
1826
+ return typed_edn_error(path, `record :${node.nominal.name.value} is missing declared field :${field.value}`);
1827
+ }
1828
+ return decodedFields.get(field.value)!;
1829
+ });
1830
+ return new CalcitRecord(node.nominal.name, node.nominal.fields, values, node.nominal);
1831
+ }
1832
+ case "enum": {
1833
+ if (!(input instanceof CalcitTuple) || input.enumPrototype == null) {
1834
+ return typed_edn_error(path, `expected enum :${node.nominal.name()}, got ${typed_edn_kind(input)}`);
1835
+ }
1836
+ const actualEnumName = enum_prototype_name(input.enumPrototype);
1837
+ if (actualEnumName !== node.nominal.name()) {
1838
+ return typed_edn_error(path, `expected enum :${node.nominal.name()}, got enum :${actualEnumName}`);
1839
+ }
1840
+ if (!(input.tag instanceof CalcitTag)) {
1841
+ return typed_edn_error(path, `enum :${node.nominal.name()} variant must be a tag`);
1842
+ }
1843
+ const inputTag = input.tag;
1844
+ const variant = node.variants.find((candidate) => candidate.tag === inputTag.value);
1845
+ if (variant == null) {
1846
+ return typed_edn_error(path, `enum :${node.nominal.name()} has no variant :${inputTag.value}`);
1847
+ }
1848
+ if (variant.payload.length !== input.extra.length) {
1849
+ return typed_edn_error(
1850
+ path,
1851
+ `enum :${node.nominal.name()} variant :${variant.tag} expects ${variant.payload.length} payload(s), got ${input.extra.length}`
1852
+ );
1853
+ }
1854
+ const values = variant.payload.map((payloadNode, idx) =>
1855
+ decode_typed_edn_node(graph, payloadNode, input.extra[idx], `${path}.payload[${idx}]`, depth + 1)
1856
+ );
1857
+ return new CalcitTuple(newTag(variant.tag), values, node.nominal);
1858
+ }
1859
+ default:
1860
+ return typed_edn_error(path, `invalid data shape node kind: ${(node as any).kind}`);
1861
+ }
1862
+ };
1863
+
1864
+ export let parse_cirru_edn_as = (code: string, graph: DataShapeGraph): CalcitValue => {
1865
+ if (typeof code !== "string") throw new Error(`parse-cirru-edn-as expected a string, got ${typed_edn_kind(code)}`);
1866
+ if (graph.version !== 1) throw new Error(`parse-cirru-edn-as expected data shape ABI version 1, got ${graph.version}`);
1867
+ if (typeof graph.fingerprint !== "string" || graph.fingerprint.length === 0) {
1868
+ throw new Error("parse-cirru-edn-as expected a non-empty data shape fingerprint");
1869
+ }
1870
+ const enumOptions: CalcitValue[] = [];
1871
+ for (const node of graph.nodes) {
1872
+ if (node.kind === "enum") enumOptions.push(newTag(node.nominal.name()), node.nominal);
1873
+ }
1874
+
1875
+ let input: any;
1876
+ try {
1877
+ const nodes = parse(code);
1878
+ if (nodes.length !== 1) throw new Error(`expected EDN in a single node, got ${nodes.length}`);
1879
+ input = extract_cirru_edn_for_typed(nodes[0], new CalcitSliceMap(enumOptions));
1880
+ } catch (error) {
1881
+ const message = error instanceof Error ? error.message : `${error}`;
1882
+ throw new Error(`parse-cirru-edn-as failed to parse Cirru EDN: ${message}`);
1883
+ }
1884
+ return decode_typed_edn_node(graph, graph.root, input, "$", 0) as CalcitValue;
1885
+ };
1886
+
1659
1887
  const json_to_calcit = (value: any): CalcitValue => {
1660
1888
  if (value == null) return null;
1661
1889
  if (typeof value === "string") return value;
@@ -13,6 +13,7 @@ import { CalcitImpl } from "./js-impl.mjs";
13
13
  import { CalcitRef } from "./js-ref.mjs";
14
14
  import { deepEqual } from "@calcit/ternary-tree/lib/utils.mjs";
15
15
  import { atom } from "./js-ref.mjs";
16
+ import { TypedEdnSetView } from "./typed-edn.mjs";
16
17
 
17
18
  type CirruEdnFormat = string | CirruEdnFormat[];
18
19
 
@@ -256,7 +257,7 @@ const tag_to_string = (tag: CalcitValue): string => {
256
257
  throw new Error(`Unsupported tag for EDN: ${tag}`);
257
258
  };
258
259
 
259
- export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): CalcitValue => {
260
+ const extract_cirru_edn_inner = (x: CirruEdnFormat, options: CalcitValue, preserveSourceEntries: boolean): any => {
260
261
  if (typeof x === "string") {
261
262
  if (x === "nil") {
262
263
  return null;
@@ -291,6 +292,7 @@ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): Calcit
291
292
  }
292
293
  if (x[0] === "{}") {
293
294
  let result: Array<CalcitValue> = [];
295
+ const sourceKeys: CirruEdnFormat[] = [];
294
296
  x.forEach((pair, idx) => {
295
297
  if (idx === 0) {
296
298
  return; // skip first `{}` symbol
@@ -298,7 +300,15 @@ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): Calcit
298
300
  if (pair instanceof Array) {
299
301
  if (pair[0] === ";") return;
300
302
  if (pair.length === 2) {
301
- result.push(extract_cirru_edn(pair[0], options), extract_cirru_edn(pair[1], options));
303
+ const key = extract_cirru_edn_inner(pair[0], options, preserveSourceEntries);
304
+ const value = extract_cirru_edn_inner(pair[1], options, preserveSourceEntries);
305
+ const existingIdx = preserveSourceEntries ? sourceKeys.findIndex((sourceKey) => deepEqual(sourceKey, pair[0])) : -1;
306
+ if (existingIdx >= 0) {
307
+ result[(existingIdx << 1) + 1] = value;
308
+ } else {
309
+ if (preserveSourceEntries) sourceKeys.push(pair[0]);
310
+ result.push(key, value);
311
+ }
302
312
  } else {
303
313
  throw new Error(`Expected a pair, got: ${pair}`);
304
314
  }
@@ -323,7 +333,10 @@ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): Calcit
323
333
  if (pair[0] === ";") return;
324
334
  if (pair.length === 2) {
325
335
  if (typeof pair[0] === "string") {
326
- entries.push([extractFieldTag(pair[0]), extract_cirru_edn(pair[1], options)]);
336
+ entries.push([
337
+ extractFieldTag(pair[0]),
338
+ extract_cirru_edn_inner(pair[1], options, preserveSourceEntries),
339
+ ]);
327
340
  } else {
328
341
  throw new Error(`Expected string as field, got: ${pair}`);
329
342
  }
@@ -348,10 +361,9 @@ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): Calcit
348
361
  if (options instanceof CalcitMap || options instanceof CalcitSliceMap) {
349
362
  let v = options.get(extractFieldTag(name));
350
363
  if (v != null && v instanceof CalcitRecord) {
351
- if (!deepEqual(v.fields, fields)) {
352
- throw new Error(`Fields mismatch for ${name}, expected ${fields}, got ${v.fields}`);
364
+ if (deepEqual(v.fields, fields)) {
365
+ return new CalcitRecord(extractFieldTag(name), fields, values, v.structRef);
353
366
  }
354
- return new CalcitRecord(extractFieldTag(name), fields, values, v.structRef);
355
367
  }
356
368
  }
357
369
 
@@ -368,19 +380,19 @@ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): Calcit
368
380
  x
369
381
  .slice(1)
370
382
  .filter(notComment)
371
- .map((x) => extract_cirru_edn(x, options))
383
+ .map((x) => extract_cirru_edn_inner(x, options, preserveSourceEntries))
372
384
  );
373
385
  }
374
386
  if (x[0] === "#{}") {
375
- return new CalcitSet(
376
- x
377
- .slice(1)
378
- .filter(notComment)
379
- .map((x) => extract_cirru_edn(x, options))
380
- );
387
+ const sourceItems = x.slice(1).filter(notComment);
388
+ const uniqueSourceItems = preserveSourceEntries
389
+ ? sourceItems.filter((item, idx) => !sourceItems.slice(0, idx).some((existing) => deepEqual(existing, item)))
390
+ : sourceItems;
391
+ const items = uniqueSourceItems.map((x) => extract_cirru_edn_inner(x, options, preserveSourceEntries));
392
+ return preserveSourceEntries ? new TypedEdnSetView(items) : new CalcitSet(items);
381
393
  }
382
394
  if (x[0] === "do" && x.length === 2) {
383
- return extract_cirru_edn(x[1], options);
395
+ return extract_cirru_edn_inner(x[1], options, preserveSourceEntries);
384
396
  }
385
397
  if (x[0] === "quote") {
386
398
  if (x.length !== 2) {
@@ -393,11 +405,11 @@ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): Calcit
393
405
  throw new Error(`tuple expects at least 1 value, got: ${x}`);
394
406
  }
395
407
  return new CalcitTuple(
396
- extract_cirru_edn(x[1], options),
408
+ extract_cirru_edn_inner(x[1], options, preserveSourceEntries),
397
409
  x
398
410
  .slice(2)
399
411
  .filter(notComment)
400
- .map((x) => extract_cirru_edn(x, options))
412
+ .map((x) => extract_cirru_edn_inner(x, options, preserveSourceEntries))
401
413
  );
402
414
  }
403
415
  if (x[0] === "%::") {
@@ -413,11 +425,11 @@ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): Calcit
413
425
  const proto = enumPrototype != null ? unwrap_enum_prototype_local(enumPrototype) : null;
414
426
  const enumTag = proto != null ? proto.name.toString() : enumName;
415
427
  return new CalcitTuple(
416
- extract_cirru_edn(x[2], options),
428
+ extract_cirru_edn_inner(x[2], options, preserveSourceEntries),
417
429
  x
418
430
  .slice(3)
419
431
  .filter(notComment)
420
- .map((x) => extract_cirru_edn(x, options)),
432
+ .map((x) => extract_cirru_edn_inner(x, options, preserveSourceEntries)),
421
433
  enumPrototype
422
434
  );
423
435
  }
@@ -425,13 +437,21 @@ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): Calcit
425
437
  if (x.length !== 2) {
426
438
  throw new Error(`atom expects 1 argument, got: ${x}`);
427
439
  }
428
- return atom(extract_cirru_edn(x[1], options));
440
+ return atom(extract_cirru_edn_inner(x[1], options, preserveSourceEntries));
429
441
  }
430
442
  }
431
443
  console.error(x);
432
444
  throw new Error(`Unexpected data from EDN: ${x}`);
433
445
  };
434
446
 
447
+ export let extract_cirru_edn = (x: CirruEdnFormat, options: CalcitValue): CalcitValue => {
448
+ return extract_cirru_edn_inner(x, options, false) as CalcitValue;
449
+ };
450
+
451
+ export let extract_cirru_edn_for_typed = (x: CirruEdnFormat, options: CalcitValue): CalcitValue | TypedEdnSetView => {
452
+ return extract_cirru_edn_inner(x, options, true) as CalcitValue | TypedEdnSetView;
453
+ };
454
+
435
455
  export let format_cirru_edn = (data: CalcitValue, useInline: boolean = true): string => {
436
456
  if (data == null) {
437
457
  return "\ndo nil" + "\n";
@@ -3,6 +3,8 @@ import { CalcitValue } from "./js-primes.mjs";
3
3
  import { CalcitTag, castTag, findInFields, toString } from "./calcit-data.mjs";
4
4
  import type { CalcitTrait } from "./js-trait.mjs";
5
5
 
6
+ const CALCIT_IMPL_BRAND = Symbol.for("@calcit/procs/CalcitImpl");
7
+
6
8
  export class CalcitImpl {
7
9
  name: CalcitTag;
8
10
  origin: CalcitTrait | null;
@@ -10,7 +12,38 @@ export class CalcitImpl {
10
12
  values: Array<CalcitValue>;
11
13
  cachedHash: Hash;
12
14
 
15
+ static [Symbol.hasInstance](value: unknown): boolean {
16
+ if (typeof value !== "object" || value === null) return false;
17
+
18
+ const candidate = value as Record<PropertyKey, unknown>;
19
+ const brand = Object.getOwnPropertyDescriptor(candidate, CALCIT_IMPL_BRAND);
20
+ const name = Object.getOwnPropertyDescriptor(candidate, "name");
21
+ const origin = Object.getOwnPropertyDescriptor(candidate, "origin");
22
+ const fields = Object.getOwnPropertyDescriptor(candidate, "fields");
23
+ const values = Object.getOwnPropertyDescriptor(candidate, "values");
24
+ const cachedHash = Object.getOwnPropertyDescriptor(candidate, "cachedHash");
25
+
26
+ return (
27
+ brand?.value === true &&
28
+ name !== undefined &&
29
+ origin !== undefined &&
30
+ fields !== undefined &&
31
+ values !== undefined &&
32
+ cachedHash !== undefined &&
33
+ typeof name.value === "object" &&
34
+ name.value !== null &&
35
+ typeof (name.value as { value?: unknown }).value === "string" &&
36
+ (origin.value === null || typeof origin.value === "object") &&
37
+ Array.isArray(fields.value) &&
38
+ Array.isArray(values.value)
39
+ );
40
+ }
41
+
13
42
  constructor(name: CalcitTag, fields: Array<CalcitTag>, values: Array<CalcitValue>, origin: CalcitTrait | null = null) {
43
+ // Vite can temporarily retain two copies of @calcit/procs while refreshing
44
+ // optimized dependencies. A global, non-enumerable brand keeps impl values
45
+ // recognizable across those otherwise distinct module instances.
46
+ Object.defineProperty(this, CALCIT_IMPL_BRAND, { value: true });
14
47
  this.name = name;
15
48
  this.origin = origin;
16
49
  this.fields = fields;
@@ -0,0 +1,7 @@
1
+ export class TypedEdnSetView {
2
+ readonly items: any[];
3
+
4
+ constructor(items: any[]) {
5
+ this.items = items;
6
+ }
7
+ }