tuple-ts 0.9.0 → 0.9.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.
package/README.md CHANGED
@@ -45,7 +45,9 @@ person1 === person2; // true
45
45
  - Coordinates and other multi-dimensional values
46
46
  - Named, slot-based values such as points or pairs
47
47
 
48
- `tuple-ts` has zero dependencies and is just a few kilobytes gzipped.
48
+ `tuple-ts` supports tuples containing any values, including only primitives, without sacrificing garbage-collection safety.
49
+
50
+ `tuple-ts` has zero dependencies and is tiny at around 1 KB when minified and gzipped.
49
51
 
50
52
  ## Install
51
53
 
@@ -60,12 +62,13 @@ The values returned by `tuple(...)` are regular frozen JavaScript arrays:
60
62
  ```ts
61
63
  const tuple1 = tuple(1, "foo");
62
64
 
63
- Array.isArray(tuple1); // true
64
- tuple1.length === 2; // true
65
- tuple1[0] === 1; // true
66
- tuple1[1] === "foo"; // true
67
- Object.isFrozen(tuple1); // true
68
- [ ...tuple1 ]; // [1, "foo"]
65
+ Array.isArray(tuple1); // true
66
+ tuple1.length === 2; // true
67
+ tuple1[0] === 1; // true
68
+ tuple1[1] === "foo"; // true
69
+ Object.isFrozen(tuple1); // true
70
+ [ ...tuple1 ]; // [1, "foo"]
71
+ tuple.isInstance(tuple1); // true
69
72
  ```
70
73
 
71
74
  ...with automatically inferred literal types:
@@ -113,6 +116,8 @@ const Point = tuple.typed<[number, number]>();
113
116
 
114
117
  Point(10, 20); // OK
115
118
  Point(10, "20"); // TypeScript error
119
+
120
+ tuple.isInstance(Point(1, 2)); // true;
116
121
  ```
117
122
 
118
123
  You can alternatively provide an exemplar from which the type constraint will be inferred:
@@ -160,10 +165,11 @@ p[1]; // 20
160
165
  p.x; // 10
161
166
  p.y; // 20
162
167
 
163
- Array.isArray(p); // true!
164
- Object.isFrozen(p); // true
165
- [ ...p ]; // [10, 20]
166
- { ...p }; // { 0: 10, 1: 20, x: 10, y: 20 }
168
+ Array.isArray(p); // true!
169
+ Object.isFrozen(p); // true
170
+ Point.isInstance(p); // true
171
+ [ ...p ]; // [10, 20]
172
+ { ...p }; // { 0: 10, 1: 20, x: 10, y: 20 }
167
173
  ```
168
174
 
169
175
  ### Constructing from an object with `.from()`
@@ -196,10 +202,13 @@ The returned value contains the tuple slots and the schema's named properties, n
196
202
  Just like `tuple`, structs support `.typed()` to specify the struct's value types positionally:
197
203
 
198
204
  ```ts
199
- const Point = struct("x", "y").typed<[number, number]>();
205
+ const XY = struct("x", "y");
206
+ const Point = XY.typed<[number, number]>();
200
207
 
201
208
  Point(10, 20); // OK
202
209
  Point(10, "20"); // TypeScript error
210
+
211
+ XY.isInstance(Point(1, 2)); // true!
203
212
  ```
204
213
 
205
214
  Use `.typedFrom()` when you want to express the value types by label instead:
package/dist/index.d.ts CHANGED
@@ -1,27 +1,32 @@
1
1
  import InternCache from "./intern-cache.js";
2
- type Tuple = readonly unknown[];
3
- type Labels = readonly string[];
4
- type NumericKeyOf<T> = keyof T & (number | `${number}`);
5
2
  type ToNumber<K> = K extends `${infer N extends number}` ? N : never;
3
+ type Labels = readonly string[];
4
+ type ValidLabels<L extends Labels> = L[number] & keyof (readonly unknown[]) extends never ? L : never;
5
+ type Tuple = readonly unknown[];
6
+ type TupleOfLabels<L extends Labels> = {
7
+ [I in keyof L]: unknown;
8
+ };
9
+ type ObjectOf<L extends Labels, T extends TupleOfLabels<L>> = {
10
+ [I in Extract<keyof L, `${number}`> as L[I]]: T[ToNumber<I>];
11
+ };
12
+ type TupleFromObject<L extends Labels, O extends object> = {
13
+ [I in keyof L]: O[L[I] & keyof O];
14
+ };
6
15
  export type TupleFactory<T extends Tuple = Tuple> = {
7
16
  <const T_ extends T>(...args: T_): T_;
17
+ isInstance: (value: unknown) => boolean;
8
18
  typed<T_ extends T>(...exemplar: T_ | []): TupleFactory<Readonly<T_>>;
9
19
  };
10
- type ObjectOf<T extends Tuple, L extends Labels> = {
11
- [I in NumericKeyOf<L> as L[I]]: T[ToNumber<I>];
12
- };
13
- type TupleFromObject<T extends Tuple, L extends Labels, O extends ObjectOf<T, L>> = T & {
14
- [I in keyof L]: O[L[I]];
15
- };
16
- export type StructFactory<T extends Tuple, L extends Labels> = {
17
- <const T_ extends T>(...args: T_): Readonly<T_ & ObjectOf<T_, L>>;
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<Readonly<T_>, L>;
20
- typedFrom<O extends ObjectOf<T, L>>(...args: [exemplar: O] | []): StructFactory<{
20
+ export type StructFactory<L extends Labels, T extends TupleOfLabels<L>> = {
21
+ <const T_ extends T>(...args: T_): Readonly<T_ & ObjectOf<L, T_>>;
22
+ isInstance: (value: unknown) => boolean;
23
+ from<const O extends ObjectOf<L, T>, const T_ extends T & TupleFromObject<L, O>>(arg: O): Readonly<ObjectOf<L, T_> & T_>;
24
+ typed<T_ extends T>(...exemplar: T_ | []): StructFactory<L, Readonly<T_>>;
25
+ typedFrom<O extends ObjectOf<L, T>>(...args: [exemplar: O] | []): StructFactory<L, {
21
26
  [I in keyof L]: O[L[I]];
22
- }, L>;
27
+ }>;
23
28
  };
24
- export declare const _tupleCache: InternCache<number, readonly unknown[], readonly unknown[]>;
29
+ export declare const _tupleCache: InternCache<number, Tuple, Tuple>;
25
30
  export declare const tuple: TupleFactory<Tuple>;
26
- export declare const struct: <const L extends readonly string[]>(...labels: L) => StructFactory<Tuple, L>;
31
+ export declare const struct: <const L extends readonly string[]>(...labels: ValidLabels<L>) => StructFactory<ValidLabels<L>, TupleOfLabels<ValidLabels<L>>>;
27
32
  export {};
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import InternCache from "./intern-cache.js";
2
2
  import * as hash from "./hash.js";
3
- function arrayEquivalent(a1, a2) {
3
+ function tuplesEquivalent(a1, a2) {
4
4
  if (a2.length !== a1.length)
5
5
  return false;
6
6
  for (let i = 0; i < a1.length; i++) {
@@ -9,14 +9,20 @@ function arrayEquivalent(a1, a2) {
9
9
  }
10
10
  return true;
11
11
  }
12
- const defineTuple = () => {
13
- const construct = (...args) => _tupleCache.get(args);
14
- const typed = (..._exemplar) => defineTuple();
15
- return Object.assign(construct, { typed });
12
+ const defineTuple = (brand) => {
13
+ const construct = (...args) => _tupleCache.getOrSet(args);
14
+ const typed = (..._exemplar) => defineTuple(brand);
15
+ const isInstance = (value) => {
16
+ return value?.[tupleBrand] === true;
17
+ };
18
+ return Object.assign(construct, { typed, isInstance });
16
19
  };
17
- const defineStruct = (cache, labels) => {
20
+ const defineStruct = (cache, labels, brand) => {
21
+ const isInstance = (value) => {
22
+ return value?.[brand] === true;
23
+ };
18
24
  const construct = (...args) => {
19
- return cache.get(args);
25
+ return cache.getOrSet(args);
20
26
  };
21
27
  const fromTuple = (...args) => {
22
28
  return construct(...args);
@@ -25,19 +31,25 @@ const defineStruct = (cache, labels) => {
25
31
  const args = labels.map((label) => arg[label]);
26
32
  return construct(...args);
27
33
  };
28
- const typed = (..._exemplar) => defineStruct(cache, labels);
29
- const typedFrom = (..._args) => defineStruct(cache, labels);
30
- return Object.assign(fromTuple, { from: fromObject, typed, typedFrom });
34
+ const typed = (..._exemplar) => defineStruct(cache, labels, brand);
35
+ const typedFrom = (..._args) => defineStruct(cache, labels, brand);
36
+ return Object.assign(fromTuple, { from: fromObject, typed, typedFrom, isInstance });
31
37
  };
32
- export const _tupleCache = new InternCache(hash.valuesHash, arrayEquivalent, Object.freeze);
33
- export const tuple = defineTuple();
38
+ const tupleBrand = Symbol();
39
+ export const _tupleCache = new InternCache(hash.valuesHash, tuplesEquivalent, (tuple) => {
40
+ Object.defineProperty(tuple, tupleBrand, { value: true });
41
+ return Object.freeze(tuple);
42
+ });
43
+ export const tuple = defineTuple(tupleBrand);
34
44
  export const struct = (...labels) => {
45
+ const brand = Symbol();
35
46
  const cacheInjestFn = (tuple) => {
36
47
  labels.forEach((label, index) => {
37
48
  tuple[label] = tuple[index];
38
49
  });
50
+ Object.defineProperty(tuple, brand, { value: true });
39
51
  return Object.freeze(tuple);
40
52
  };
41
- const cache = new InternCache(hash.valuesHash, arrayEquivalent, cacheInjestFn);
42
- return defineStruct(cache, labels);
53
+ const cache = new InternCache(hash.valuesHash, tuplesEquivalent, cacheInjestFn);
54
+ return defineStruct(cache, labels, brand);
43
55
  };
@@ -1,4 +1,5 @@
1
- export default class<H, K, V extends object> {
1
+ type Value = symbol | object;
2
+ export default class<H, K, V extends Value> {
2
3
  private readonly hashFn;
3
4
  private readonly matchFn;
4
5
  private readonly injestFn;
@@ -6,7 +7,8 @@ export default class<H, K, V extends object> {
6
7
  private readonly cleanupRegistry;
7
8
  constructor(hashFn: (key: K) => H, matchFn: (key: K, value: V) => boolean, injestFn: (key: K) => V);
8
9
  private remove;
9
- get<K_ extends K, V_ extends V>(key: K_): V_;
10
+ getOrSet<K_ extends K, V_ extends V>(key: K_): V_;
10
11
  get size(): number;
11
12
  reset(): void;
12
13
  }
14
+ export {};
@@ -19,7 +19,7 @@ export default class {
19
19
  this.map.delete(hash);
20
20
  }
21
21
  }
22
- get(key) {
22
+ getOrSet(key) {
23
23
  const hash = this.hashFn(key);
24
24
  let hashBucket = this.map.get(hash);
25
25
  if (hashBucket !== undefined) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "tuple-ts",
4
- "version": "0.9.0",
4
+ "version": "0.9.2",
5
5
  "description": "TypeScript-first tuples and structs with value-based identity",
6
6
  "keywords": [
7
7
  "tuple",
@@ -33,7 +33,7 @@
33
33
  "node": ">=20.0.0"
34
34
  },
35
35
  "scripts": {
36
- "build": "rm -r dist/ && tsc && esbuild src/index.ts --bundle --minify --format=esm --platform=browser --target=es2023 --outfile=dist/tuple-ts.min.js",
36
+ "build": "rm -r dist/ && tsc",
37
37
  "test": "tsc -p tsconfig.test.json --noEmit && vitest run src/",
38
38
  "prepare": "npm run build",
39
39
  "prepublishOnly": "npm test"
@@ -42,7 +42,6 @@
42
42
  "license": "ISC",
43
43
  "devDependencies": {
44
44
  "@types/node": "^26.6.5",
45
- "esbuild": "^0.28.2",
46
45
  "typescript": "^7.0.2",
47
46
  "vitest": "^4.1.12"
48
47
  },
@@ -1 +0,0 @@
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};