tuple-ts 0.8.3 → 0.9.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/README.md CHANGED
@@ -21,7 +21,7 @@ with optional slot labels:
21
21
  ```ts
22
22
  import { struct } from "tuple-ts";
23
23
 
24
- const Person = struct(["name", "age"]);
24
+ const Person = struct("name", "age");
25
25
 
26
26
  const person1 = Person("Bob", 25);
27
27
 
@@ -146,12 +146,12 @@ mat1 === mat2; // true
146
146
  ## Structs
147
147
 
148
148
  Use `struct()` when you want a tuple with meaningful labels.
149
- Provide a list of labels to create a reusable factory that produces tuples with additional named properties.
149
+ Provide the labels to create a reusable factory that produces tuples with additional named properties.
150
150
 
151
151
  ```ts
152
152
  import { struct } from "tuple-ts";
153
153
 
154
- const Point = struct(["x", "y"]);
154
+ const Point = struct("x", "y");
155
155
 
156
156
  const p = Point(10, 20);
157
157
 
@@ -171,7 +171,7 @@ Object.isFrozen(p); // true
171
171
  `.from()` accepts an object with the declared labels and creates the corresponding positional tuple:
172
172
 
173
173
  ```ts
174
- const Point = struct(["x", "y"]);
174
+ const Point = struct("x", "y");
175
175
 
176
176
  const point1 = Point.from({ x: 10, y: 20 });
177
177
 
@@ -196,7 +196,7 @@ The returned value contains the tuple slots and the schema's named properties, n
196
196
  Just like `tuple`, structs support `.typed()` to specify the struct's value types positionally:
197
197
 
198
198
  ```ts
199
- const Point = struct(["x", "y"]).typed<[number, number]>();
199
+ const Point = struct("x", "y").typed<[number, number]>();
200
200
 
201
201
  Point(10, 20); // OK
202
202
  Point(10, "20"); // TypeScript error
@@ -207,7 +207,7 @@ Use `.typedFrom()` when you want to express the value types by label instead:
207
207
  ```ts
208
208
  type Coordinates = { x: number; y: number };
209
209
 
210
- const Point = struct(["x", "y"]).typedFrom<Coordinates>();
210
+ const Point = struct("x", "y").typedFrom<Coordinates>();
211
211
 
212
212
  Point(10, 20); // OK
213
213
  Point(10, "20"); // TypeScript error
@@ -217,19 +217,19 @@ Point.from({ x: 10, y: 20 }); // OK
217
217
  You can also provide a typed exemplar to `.typedFrom()`:
218
218
 
219
219
  ```ts
220
- const Point = struct(["x", "y"]).typedFrom({ x: 0, y: 0 });
220
+ const Point = struct("x", "y").typedFrom({ x: 0, y: 0 });
221
221
  ```
222
222
 
223
223
  ### Struct schemas and identity
224
224
 
225
- Each call to `struct(labels)` creates its own cache. Factories narrowed from that schema with `.typed()` or `.typedFrom()` share the cache, but separate calls to `struct()` create separate caches, even when given identical labels:
225
+ Each call to `struct(...)` creates its own identity context:
226
226
 
227
227
  ```ts
228
- const PointA = struct(["x", "y"]).typed<[number, number]>();
229
- const PointB = struct(["x", "y"]).typed<[number, number]>();
228
+ const Point = struct("x", "y");
229
+ Point(1, 2) === Point(1, 2); // true
230
230
 
231
- PointA(1, 2) === PointA(1, 2); // true
232
- PointA(1, 2) === PointB(1, 2); // false
231
+ const Coord = struct("x", "y");
232
+ Coord(1, 2) !== Point(1, 2); // true
233
233
  ```
234
234
 
235
235
  Struct identity is therefore scoped to the schema factory. Ordinary `tuple()` values use a separate, shared tuple cache and do not share identity with struct values, even when their indexed elements are equal.
@@ -262,7 +262,7 @@ myMap.set(tuple(10, 20), "visited");
262
262
  myMap.has(tuple(10, 20)); // true
263
263
  myMap.get(tuple(10, 20)); // "visited"
264
264
 
265
- const Point = struct(["x", "y"]);
265
+ const Point = struct("x", "y");
266
266
  const mySet = new Set();
267
267
  mySet.add(Point(1, 2));
268
268
 
@@ -302,6 +302,10 @@ a === b; // true
302
302
 
303
303
  The cache uses `WeakRef` to avoid keeping interned objects alive solely because they are cached. A `FinalizationRegistry` is used to clean up cache entries after objects are garbage-collected. This allows unreferenced values to be reclaimed without the cache retaining them indefinitely.
304
304
 
305
+ ## Compatibility
306
+
307
+ `tuple-ts` supports NodeJS version 20 or newer.
308
+
305
309
  ## License
306
310
 
307
311
  ISC
package/dist/index.d.ts CHANGED
@@ -1,27 +1,27 @@
1
1
  import InternCache from "./intern-cache.js";
2
- type BaseTuple = readonly unknown[];
3
- type BaseLabels = readonly string[];
4
- type NumericKeyOf<T> = keyof T & `${number}`;
2
+ type Tuple = readonly unknown[];
3
+ type Labels = readonly string[];
4
+ type NumericKeyOf<T> = keyof T & (number | `${number}`);
5
5
  type ToNumber<K> = K extends `${infer N extends number}` ? N : never;
6
- export type TupleFactory<T extends BaseTuple = BaseTuple> = {
6
+ export type TupleFactory<T extends Tuple = Tuple> = {
7
7
  <const T_ extends T>(...args: T_): T_;
8
- typed<T_ extends T>(...exemplar: T_ | []): TupleFactory<T_>;
8
+ typed<T_ extends T>(...exemplar: T_ | []): TupleFactory<Readonly<T_>>;
9
9
  };
10
- type ObjectOf<T extends BaseTuple, L extends BaseLabels> = {
10
+ type ObjectOf<T extends Tuple, L extends Labels> = {
11
11
  [I in NumericKeyOf<L> as L[I]]: T[ToNumber<I>];
12
12
  };
13
- type TupleFromObject<T extends BaseTuple, L extends BaseLabels, O extends ObjectOf<T, L>> = T & {
13
+ type TupleFromObject<T extends Tuple, L extends Labels, O extends ObjectOf<T, L>> = T & {
14
14
  [I in keyof L]: O[L[I]];
15
15
  };
16
- export type StructFactory<T extends BaseTuple, L extends BaseLabels> = {
16
+ export type StructFactory<T extends Tuple, L extends Labels> = {
17
17
  <const T_ extends T>(...args: T_): Readonly<T_ & ObjectOf<T_, L>>;
18
18
  from<const O extends ObjectOf<T, L>, const T_ extends TupleFromObject<T, L, O>>(arg: O): Readonly<ObjectOf<T_, L> & T_>;
19
- typed<T_ extends T>(...exemplar: T_ | []): StructFactory<T_, L>;
19
+ typed<T_ extends T>(...exemplar: T_ | []): StructFactory<Readonly<T_>, L>;
20
20
  typedFrom<O extends ObjectOf<T, L>>(...args: [exemplar: O] | []): StructFactory<{
21
21
  [I in keyof L]: O[L[I]];
22
22
  }, L>;
23
23
  };
24
24
  export declare const _tupleCache: InternCache<number, readonly unknown[], readonly unknown[]>;
25
- export declare const tuple: TupleFactory<BaseTuple>;
26
- export declare const struct: <const L extends readonly string[]>(labels: L) => StructFactory<BaseTuple, L>;
25
+ export declare const tuple: TupleFactory<Tuple>;
26
+ export declare const struct: <const L extends readonly string[]>(...labels: L) => StructFactory<Tuple, L>;
27
27
  export {};
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ const defineStruct = (cache, labels) => {
31
31
  };
32
32
  export const _tupleCache = new InternCache(hash.valuesHash, arrayEquivalent, Object.freeze);
33
33
  export const tuple = defineTuple();
34
- export const struct = (labels) => {
34
+ export const struct = (...labels) => {
35
35
  const cacheInjestFn = (tuple) => {
36
36
  labels.forEach((label, index) => {
37
37
  tuple[label] = tuple[index];
@@ -1,10 +1,13 @@
1
1
  export default class {
2
+ hashFn;
3
+ matchFn;
4
+ injestFn;
5
+ map = new Map();
6
+ cleanupRegistry = new FinalizationRegistry((x) => this.remove(x));
2
7
  constructor(hashFn, matchFn, injestFn) {
3
8
  this.hashFn = hashFn;
4
9
  this.matchFn = matchFn;
5
10
  this.injestFn = injestFn;
6
- this.map = new Map();
7
- this.cleanupRegistry = new FinalizationRegistry((x) => this.remove(x));
8
11
  }
9
12
  remove([hash, valueRef]) {
10
13
  const hashBucket = this.map.get(hash);
@@ -0,0 +1 @@
1
+ var u=class{constructor(e,n,t){this.hashFn=e;this.matchFn=n;this.injestFn=t}hashFn;matchFn;injestFn;map=new Map;cleanupRegistry=new FinalizationRegistry(e=>this.remove(e));remove([e,n]){let t=this.map.get(e);t&&(t.delete(n),t.size===0&&this.map.delete(e))}get(e){let n=this.hashFn(e),t=this.map.get(n);if(t!==void 0)for(let s of t){let T=s.deref();if(T&&this.matchFn(e,T))return T}else t=new Set,this.map.set(n,t);let r=this.injestFn(e),o=new WeakRef(r);return t.add(o),this.cleanupRegistry.register(r,[n,o],this),r}get size(){return Array.from(this.map.entries(),([e,n])=>n.size).reduce((e,n)=>e+n,0)}reset(){this.cleanupRegistry.unregister(this),this.map.clear()}};var g=0xffffffffn,m=new Float64Array(1),p=new Uint32Array(m.buffer);function j(e,n=0){let t=n,r=0;for(let o of e){r++;let s=Math.imul(o,3432918353);s=s<<15|s>>>17,s=Math.imul(s,461845907),t^=s,t=t<<13|t>>>19,t=Math.imul(t,5)+3864292196|0}return t^=r*4,t>>>0}function k(e=Date.now()){let n=e>>>0;return()=>(n=n*1664525+1013904223>>>0,n)}var a=k(),h=a(),F=a(),w=a(),R=a(),H=a(),I=a(),N=a(),K=a(),y=new WeakMap,M=new WeakMap;function l(e,n){if(n===null)return h;let t=e.get(n);if(t!==void 0)return t;let r=a();return e.set(n,r),r}function b(e){let n=Array(Math.ceil(e.length/2)+1);n[0]=e.length;for(let t=0;t<e.length/2;t+=1){let r=e.charCodeAt(2*t),o=e.charCodeAt(2*t+1);n[t+1]=o<<16|r}return n}function S(e){return m[0]=Number.isNaN(e)?NaN:e,[p[0],p[1]]}function V(e){let n=e<0n,t=n?-e:e,r=[n?1:0];for(;t!==0n;)r.push(Number(t&g)),t>>=32n;return r}function A(e){switch(typeof e){case"string":return i(b(e),N);case"number":return i(S(e),H);case"bigint":return i(V(e),I);case"boolean":return e?R:w;case"symbol":let n=Symbol.keyFor(e);return n===void 0?l(M,e):i(b(n),K);case"undefined":return F;case"object":return e===null?h:l(y,e);case"function":return l(y,e)}}var d=(e,n)=>i(Array.from(e,A),n),i=j;function O(e,n){if(n.length!==e.length)return!1;for(let t=0;t<e.length;t++)if(!Object.is(n[t],e[t]))return!1;return!0}var x=()=>Object.assign((...t)=>W.get(t),{typed:(...t)=>x()}),f=(e,n)=>{let t=(...c)=>e.get(c);return Object.assign((...c)=>t(...c),{from:c=>{let _=n.map(L=>c[L]);return t(..._)},typed:(...c)=>f(e,n),typedFrom:(...c)=>f(e,n)})},W=new u(d,O,Object.freeze),v=x(),E=(...e)=>{let n=r=>(e.forEach((o,s)=>{r[o]=r[s]}),Object.freeze(r)),t=new u(d,O,n);return f(t,e)};export{W as _tupleCache,E as struct,v as tuple};
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "tuple-ts",
4
- "version": "0.8.3",
5
- "description": "Tuples and structs for TypeScript and JavaScript with equality and identity semantics.",
4
+ "version": "0.9.0",
5
+ "description": "TypeScript-first tuples and structs with value-based identity",
6
6
  "keywords": [
7
7
  "tuple",
8
8
  "struct",
@@ -19,31 +19,32 @@
19
19
  ],
20
20
  "repository": {
21
21
  "type": "git",
22
- "url": "https://github.com/geobrad/tuple-ts.git"
22
+ "url": "git+https://github.com/geobrad/tuple-ts.git"
23
23
  },
24
24
  "bugs": {
25
25
  "url": "https://github.com/geobrad/tuple-ts/issues"
26
26
  },
27
27
  "homepage": "https://github.com/geobrad/tuple-ts",
28
- "main": "./dist/index.js",
29
- "types": "./dist/index.d.ts",
30
28
  "exports": {
31
- ".": {
32
- "types": "./dist/index.d.ts",
33
- "import": "./dist/index.js"
34
- }
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "engines": {
33
+ "node": ">=20.0.0"
35
34
  },
36
35
  "scripts": {
37
- "build": "tsc",
38
- "test": "tsc --noEmit && vitest run src/",
36
+ "build": "rm -r dist/ && tsc && esbuild src/index.ts --bundle --minify --format=esm --platform=browser --target=es2023 --outfile=dist/tuple-ts.min.js",
37
+ "test": "tsc -p tsconfig.test.json --noEmit && vitest run src/",
39
38
  "prepare": "npm run build",
40
39
  "prepublishOnly": "npm test"
41
40
  },
42
- "author": "George Bradley",
41
+ "author": "George Bradley <george@gbrad.com> (https://gbrad.com/)",
43
42
  "license": "ISC",
44
43
  "devDependencies": {
44
+ "@types/node": "^26.6.5",
45
+ "esbuild": "^0.28.2",
45
46
  "typescript": "^7.0.2",
46
- "vitest": "^5.0.5"
47
+ "vitest": "^4.1.12"
47
48
  },
48
49
  "files": [
49
50
  "./dist"