@calcit/procs 0.28.0 → 0.29.0-alpha.2

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.
@@ -225,6 +225,9 @@ let defaultHash_struct = valueHash("struct:");
225
225
  let defaultHash_enum = valueHash("enum:");
226
226
  let defaultHash_cirru_quote = valueHash("cirru-quote:");
227
227
  let defaultHash_unknown = valueHash("unknown:");
228
+ let defaultHash_buffer = valueHash("buffer:");
229
+ /** Identity hashes for host values, kept outside the objects so frozen or DOM objects are never mutated. */
230
+ let hostValueHashes = new WeakMap();
228
231
  let fnHashCounter = 0;
229
232
  let jsObjectHashCounter = 0;
230
233
  export let hashFunction = (x) => {
@@ -265,15 +268,20 @@ export let hashFunction = (x) => {
265
268
  if (typeof x === "function") {
266
269
  // method values are closures created on the fly (see invoke_method_closure);
267
270
  // hash by method name so equal methods share the same hash, matching isEqual
271
+ // cache in a WeakMap instead of writing onto the function, so frozen functions hash safely
272
+ const cachedFn = hostValueHashes.get(x);
273
+ if (cachedFn != null) {
274
+ return cachedFn;
275
+ }
268
276
  const methodName = x.__calcitMethodName;
269
277
  if (methodName != null) {
270
278
  let h = mergeValueHash(defaultHash_fn, methodName);
271
- x[calcit_dirty_hash_key] = h;
279
+ hostValueHashes.set(x, h);
272
280
  return h;
273
281
  }
274
282
  fnHashCounter = fnHashCounter + 1;
275
283
  let h = mergeValueHash(defaultHash_fn, fnHashCounter);
276
- x[calcit_dirty_hash_key] = h;
284
+ hostValueHashes.set(x, h);
277
285
  return h;
278
286
  }
279
287
  if (x instanceof CalcitRef) {
@@ -397,15 +405,28 @@ export let hashFunction = (x) => {
397
405
  base = hashCirru(base, x.value);
398
406
  return base;
399
407
  }
400
- console.warn(`[warn] calcit-js has no method for hashing this: ${x}`);
401
- // currently we use dirty solution here to generate a custom hash
402
- // probably happening in .to-pairs of maps, putting a js object into a set
403
- // better forbid this, use .to-list instead
404
- let hashJsObject = defaultHash_unknown;
408
+ if (x instanceof Uint8Array) {
409
+ // Buffers are values: equal bytes must hash equally, matching native Buffer equality.
410
+ let base = mergeValueHash(defaultHash_buffer, x.length);
411
+ for (let idx = 0; idx < x.length; idx++) {
412
+ base = mergeValueHash(base, x[idx]);
413
+ }
414
+ return base;
415
+ }
416
+ if (typeof x === "object" || typeof x === "function") {
417
+ // Host values compare by identity, so they hash by identity as well.
418
+ let cached = hostValueHashes.get(x);
419
+ if (cached != null) {
420
+ return cached;
421
+ }
422
+ jsObjectHashCounter = jsObjectHashCounter + 1;
423
+ let hashHostValue = mergeValueHash(defaultHash_unknown, jsObjectHashCounter);
424
+ hostValueHashes.set(x, hashHostValue);
425
+ return hashHostValue;
426
+ }
427
+ console.warn(`[warn] calcit-js has no method for hashing this: ${String(x)}`);
405
428
  jsObjectHashCounter = jsObjectHashCounter + 1;
406
- hashJsObject = mergeValueHash(hashJsObject, jsObjectHashCounter);
407
- x[calcit_dirty_hash_key] = hashJsObject;
408
- return hashJsObject;
429
+ return mergeValueHash(defaultHash_unknown, jsObjectHashCounter);
409
430
  };
410
431
  /// traverse Cirru tree to make unique hash
411
432
  let hashCirru = (base, x) => {
@@ -826,6 +847,22 @@ export let _$n__$e_ = (x, y) => {
826
847
  }
827
848
  return false;
828
849
  }
850
+ if (x instanceof Uint8Array) {
851
+ if (!(y instanceof Uint8Array) || x.length !== y.length) {
852
+ return false;
853
+ }
854
+ for (let idx = 0; idx < x.length; idx++) {
855
+ if (x[idx] !== y[idx]) {
856
+ return false;
857
+ }
858
+ }
859
+ return true;
860
+ }
861
+ if (tx === "object") {
862
+ // Host objects (DOM nodes, JS class instances, BufList) compare by identity;
863
+ // identical references were accepted at the top of this function.
864
+ return false;
865
+ }
829
866
  throw new Error("Missing handler for this type");
830
867
  };
831
868
  // overwrite internary comparator of ternary-tree
@@ -87,7 +87,9 @@ import { CalcitSet } from "./js-set.mjs";
87
87
  import { CalcitEnumValue } from "./js-enum-value.mjs";
88
88
  import { CalcitCirruQuote } from "./js-cirru.mjs";
89
89
  export declare let type_of: (x: any) => CalcitTag;
90
- export declare let _$n_trait_$o__$o_new: (name: CalcitValue, methods: CalcitValue) => CalcitTrait;
90
+ /** Check host member shape without invoking methods or claiming their signature is verified. */
91
+ export declare const js_cast: (value: unknown, trait: string, fields: readonly string[], methods: readonly string[]) => object;
92
+ export declare let _$n_trait_$o__$o_new: (name: CalcitValue, methods: CalcitValue, parents?: CalcitValue) => CalcitTrait;
91
93
  export declare let _$n_assert_traits: (value: CalcitValue, traitDef: CalcitValue) => CalcitValue;
92
94
  export declare let defstruct: (name: CalcitValue, ...entries: CalcitValue[]) => CalcitStructDef;
93
95
  export declare let defenum: (name: CalcitValue, ...variants: CalcitValue[]) => CalcitEnumDef;
@@ -186,6 +188,7 @@ export declare let _$n__GT_: (a: number, b: number) => boolean;
186
188
  export declare let _$n__LT_: (a: number, b: number) => boolean;
187
189
  export declare let _$n__: (a: number, b: number) => number;
188
190
  export declare let _$n__SLSH_: (a: number, b: number) => number;
191
+ /** Truncated remainder over safe integers, matching native and WASM; the result is never `-0`. */
189
192
  export declare let _$n_number_$o_rem: (a: number, b: number) => number;
190
193
  export declare let _$n_number_$o_fits_$q_: (value: number, target: CalcitTag) => boolean;
191
194
  export declare let round_$q_: (a: number) => boolean;
@@ -104,6 +104,52 @@ export let type_of = (x) => {
104
104
  }
105
105
  throw new Error(`Unknown data ${x}`);
106
106
  };
107
+ // The intrinsic getter reads typed-array slots across realms, ignoring a forged Symbol.toStringTag.
108
+ const typedArrayTag = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Uint8Array.prototype), Symbol.toStringTag).get;
109
+ /** Check host member shape without invoking methods or claiming their signature is verified. */
110
+ export const js_cast = (value, trait, fields, methods) => {
111
+ const fail = (detail, cause) => {
112
+ const error = new TypeError(`js-cast ${trait}: ${detail}`);
113
+ Object.defineProperty(error, "cause", { value: cause, configurable: true });
114
+ throw error;
115
+ };
116
+ // Calcit objects (including Buffers) are not interchangeable with external handles.
117
+ // Keep the runtime's own value classification authoritative instead of another registry.
118
+ let host;
119
+ try {
120
+ host = value !== null && typeof value === "object"
121
+ && !(ArrayBuffer.isView(value) && typedArrayTag.call(value) === "Uint8Array")
122
+ && !(value instanceof CalcitRecur) && type_of(value).value === "js-object";
123
+ }
124
+ catch (cause) {
125
+ return fail("host classification failed", cause);
126
+ }
127
+ if (!host || value === null || typeof value !== "object")
128
+ return fail("expected a JavaScript host object");
129
+ for (const field of fields) {
130
+ let present;
131
+ try {
132
+ present = field in value;
133
+ }
134
+ catch (cause) {
135
+ return fail(`field ${JSON.stringify(field)} lookup failed`, cause);
136
+ }
137
+ if (!present)
138
+ return fail(`missing field ${JSON.stringify(field)}`);
139
+ }
140
+ for (const method of methods) {
141
+ let member;
142
+ try {
143
+ member = Reflect.get(value, method);
144
+ }
145
+ catch (cause) {
146
+ return fail(`method ${JSON.stringify(method)} lookup failed`, cause);
147
+ }
148
+ if (typeof member !== "function")
149
+ return fail(`method ${JSON.stringify(method)} is not callable`);
150
+ }
151
+ return value;
152
+ };
107
153
  const list_items = (item) => {
108
154
  if (item instanceof CalcitList || item instanceof CalcitSliceList) {
109
155
  return Array.from(item.items());
@@ -161,9 +207,9 @@ const trimDataDefinitionEntries = (entries) => {
161
207
  }
162
208
  return entries.slice(start);
163
209
  };
164
- export let _$n_trait_$o__$o_new = function (name, methods) {
165
- if (arguments.length !== 2)
166
- throw new Error("&trait::new expected 2 arguments");
210
+ export let _$n_trait_$o__$o_new = function (name, methods, parents) {
211
+ if (arguments.length !== 2 && arguments.length !== 3)
212
+ throw new Error("&trait::new expected 2 or 3 arguments");
167
213
  const items = list_items(methods);
168
214
  const methodNames = [];
169
215
  const methodTypes = [];
@@ -175,7 +221,39 @@ export let _$n_trait_$o__$o_new = function (name, methods) {
175
221
  methodNames.push(pair[0]);
176
222
  methodTypes.push(pair[1]);
177
223
  }
178
- return new CalcitTrait(name, methodNames, methodTypes);
224
+ const requires = parents == null ? [] : list_items(parents);
225
+ for (const parent of requires) {
226
+ if (!(parent instanceof CalcitTrait)) {
227
+ throw new Error(`trait ${toString(name, false)} 'requires expects a trait, but received: ${toString(parent, true)}`);
228
+ }
229
+ }
230
+ const traitDef = new CalcitTrait(name, methodNames, methodTypes, requires);
231
+ const owners = new Map();
232
+ for (const reachable of traitDef.reachable()) {
233
+ for (const member of reachable.methods) {
234
+ const owner = owners.get(member.value);
235
+ if (owner != null && owner !== reachable) {
236
+ throw new Error(`member \`${member.value}\` is declared by both ${owner.name.toString()} and ${reachable.name.toString()}; keep it on one trait`);
237
+ }
238
+ owners.set(member.value, reachable);
239
+ }
240
+ }
241
+ return traitDef;
242
+ };
243
+ /** Every attached impl needs impls of all traits its origin requires on the same type. */
244
+ let checkRequiredTraitImpls = (impls, procName) => {
245
+ for (const impl of impls) {
246
+ const origin = impl?.origin;
247
+ if (origin == null)
248
+ continue;
249
+ for (const required of origin.reachable()) {
250
+ if (required === origin)
251
+ continue;
252
+ if (!impls.some((candidate) => candidate?.origin === required)) {
253
+ throw new Error(`${procName}: an impl of trait ${origin.name.toString()} requires an impl of trait ${required.name.toString()} on the same type; attach one before or with it`);
254
+ }
255
+ }
256
+ }
179
257
  };
180
258
  export let _$n_assert_traits = function (value, traitDef) {
181
259
  if (arguments.length !== 2)
@@ -205,6 +283,10 @@ export let _$n_assert_traits = function (value, traitDef) {
205
283
  if (missing.length > 0) {
206
284
  throw new Error(`assert-traits failed: impl ${selected.name.toString()} for trait ${traitDef.name.toString()} is incomplete. Missing: ${missing.join(" ")}`);
207
285
  }
286
+ const unmet = traitDef.reachable().find((required) => required !== traitDef && !impls.some((impl) => impl?.origin === required));
287
+ if (unmet != null) {
288
+ throw new Error(`assert-traits failed: ${toString(value, true)} implements ${traitDef.name.toString()} but not its required trait ${unmet.name.toString()}`);
289
+ }
208
290
  return value;
209
291
  };
210
292
  export let defstruct = (name, ...entries) => {
@@ -597,6 +679,7 @@ export let _$n_enum_$o_impl_traits = function (x, ...traits) {
597
679
  if (!(x instanceof CalcitEnumValue))
598
680
  throw new Error("&enum:impl-traits expects an enum value");
599
681
  const impls = traits.map((trait) => coerce_impl(trait, "&enum:impl-traits"));
682
+ checkRequiredTraitImpls([...(x.enumPrototype?.impls ?? []), ...impls], "&enum:impl-traits");
600
683
  let proto = x.enumPrototype;
601
684
  if (proto == null) {
602
685
  const tagName = x.tag instanceof CalcitTag ? x.tag : newTag("tag");
@@ -733,6 +816,7 @@ export let _$n_struct_$o_impl_traits = function (xs, ...traits) {
733
816
  if (!(xs instanceof CalcitStructValue))
734
817
  throw new Error("&struct:impl-traits expected a struct value");
735
818
  const impls = traits.map((trait) => coerce_impl(trait, "&struct:impl-traits"));
819
+ checkRequiredTraitImpls(xs.structRef.impls.concat(impls), "&struct:impl-traits");
736
820
  const nextStruct = new CalcitStructDef(xs.name, xs.fields, xs.structRef.fieldTypes, xs.structRef.impls.concat(impls), xs.structRef.definitionRef);
737
821
  return new CalcitStructValue(xs.name, xs.fields, xs.values, nextStruct);
738
822
  };
@@ -743,6 +827,7 @@ export let _$n_struct_def_$o_impl_traits = function (xs, ...traits) {
743
827
  throw new Error("&struct-def:impl-traits expected a struct definition");
744
828
  const addedImpls = traits.map((trait) => coerce_impl(trait, "&struct-def:impl-traits"));
745
829
  const baseImpls = xs.impls ?? [];
830
+ checkRequiredTraitImpls(baseImpls.concat(addedImpls), "&struct-def:impl-traits");
746
831
  return new CalcitStructDef(xs.name, xs.fields, xs.fieldTypes, baseImpls.concat(addedImpls), xs.definitionRef);
747
832
  };
748
833
  export let _$n_enum_def_$o_impl_traits = function (xs, ...traits) {
@@ -750,6 +835,7 @@ export let _$n_enum_def_$o_impl_traits = function (xs, ...traits) {
750
835
  throw new Error("&enum-def:impl-traits takes 2+ arguments");
751
836
  const addedImpls = traits.map((trait) => coerce_impl(trait, "&enum-def:impl-traits"));
752
837
  if (xs instanceof CalcitEnumDef) {
838
+ checkRequiredTraitImpls([...(xs.impls ?? []), ...addedImpls], "&enum-def:impl-traits");
753
839
  return xs.withImpls(addedImpls);
754
840
  }
755
841
  throw new Error("&enum-def:impl-traits expected an enum definition");
@@ -1114,8 +1200,15 @@ export let _$n__ = (a, b) => {
1114
1200
  export let _$n__SLSH_ = (a, b) => {
1115
1201
  return a / b;
1116
1202
  };
1203
+ /** Truncated remainder over safe integers, matching native and WASM; the result is never `-0`. */
1117
1204
  export let _$n_number_$o_rem = (a, b) => {
1118
- return a % b;
1205
+ if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b)) {
1206
+ throw new Error(`&number:rem requires safe integers, but received: ${toString(a, false)} ${toString(b, false)}`);
1207
+ }
1208
+ if (b === 0) {
1209
+ throw new Error("&number:rem divisor must not be zero");
1210
+ }
1211
+ return (a % b) + 0;
1119
1212
  };
1120
1213
  export let _$n_number_$o_fits_$q_ = (value, target) => {
1121
1214
  if (!(target instanceof CalcitTag)) {
package/lib/js-cirru.mjs CHANGED
@@ -555,7 +555,6 @@ const extract_cirru_edn_inner = (x, options, preserveSourceEntries) => {
555
555
  return atom(extract_cirru_edn_inner(x[1], options, preserveSourceEntries));
556
556
  }
557
557
  }
558
- console.error(x);
559
558
  throw new Error(`Unexpected data from EDN: ${x}`);
560
559
  };
561
560
  export let extract_cirru_edn = (x, options) => {
@@ -565,6 +564,9 @@ export let extract_cirru_edn_for_typed = (x, options) => {
565
564
  return extract_cirru_edn_inner(x, options, true);
566
565
  };
567
566
  export let format_cirru_edn = (data, useInline = true) => {
567
+ if (typeof useInline !== "boolean") {
568
+ throw new Error("format-cirru-edn requires a boolean inline option");
569
+ }
568
570
  if (data == null) {
569
571
  return "\ndo nil" + "\n";
570
572
  }
@@ -572,7 +574,7 @@ export let format_cirru_edn = (data, useInline = true) => {
572
574
  let quoted = writeCirruCode([[to_cirru_edn(data)]], { useInline: useInline }).trim();
573
575
  return "\ndo " + quoted + "\n";
574
576
  }
575
- if (typeof data === "boolean") {
577
+ if (typeof data === "boolean" || typeof data === "number") {
576
578
  return "\ndo " + to_cirru_edn(data) + "\n";
577
579
  }
578
580
  if (data instanceof CalcitSymbol) {
@@ -4,6 +4,9 @@ export declare class CalcitTrait {
4
4
  name: CalcitTag;
5
5
  methods: CalcitTag[];
6
6
  methodTypes: CalcitValue[];
7
- constructor(name: CalcitValue, methods: CalcitValue[], methodTypes: CalcitValue[]);
7
+ requires: CalcitTrait[];
8
+ constructor(name: CalcitValue, methods: CalcitValue[], methodTypes: CalcitValue[], requires?: CalcitTrait[]);
9
+ /** This trait and every transitively required trait, each origin once. */
10
+ reachable(): CalcitTrait[];
8
11
  toString(disableJsDataWarning?: boolean): string;
9
12
  }
package/lib/js-trait.mjs CHANGED
@@ -1,9 +1,26 @@
1
1
  import { castTag } from "./calcit-data.mjs";
2
2
  export class CalcitTrait {
3
- constructor(name, methods, methodTypes) {
3
+ constructor(name, methods, methodTypes, requires = []) {
4
4
  this.name = castTag(name);
5
5
  this.methods = methods.map(castTag);
6
6
  this.methodTypes = methodTypes;
7
+ this.requires = requires;
8
+ }
9
+ /** This trait and every transitively required trait, each origin once. */
10
+ reachable() {
11
+ const output = [];
12
+ const visit = (current, active) => {
13
+ if (active.includes(current)) {
14
+ throw new Error(`trait requires cycle: ${[...active, current].map((item) => item.name.toString()).join(" -> ")}`);
15
+ }
16
+ if (output.includes(current))
17
+ return;
18
+ for (const required of current.requires)
19
+ visit(required, [...active, current]);
20
+ output.push(current);
21
+ };
22
+ visit(this, []);
23
+ return output;
7
24
  }
8
25
  toString(disableJsDataWarning = false) {
9
26
  const parts = ["(trait ", this.name.toString()];
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.28.0",
3
+ "version": "0.29.0-alpha.2",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "files": [
6
6
  "lib/",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/procs",
3
- "version": "0.28.0",
3
+ "version": "0.29.0-alpha.2",
4
4
  "main": "./lib/calcit.procs.mjs",
5
5
  "files": [
6
6
  "lib/",