tuple-ts 0.8.4 → 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 +17 -13
- package/dist/index.d.ts +8 -8
- package/dist/index.js +1 -1
- package/dist/intern-cache.js +5 -2
- package/dist/tuple-ts.min.js +1 -0
- package/package.json +14 -13
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
225
|
+
Each call to `struct(...)` creates its own identity context:
|
|
226
226
|
|
|
227
227
|
```ts
|
|
228
|
-
const
|
|
229
|
-
|
|
228
|
+
const Point = struct("x", "y");
|
|
229
|
+
Point(1, 2) === Point(1, 2); // true
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
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(
|
|
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,19 +1,19 @@
|
|
|
1
1
|
import InternCache from "./intern-cache.js";
|
|
2
|
-
type
|
|
3
|
-
type
|
|
2
|
+
type Tuple = readonly unknown[];
|
|
3
|
+
type Labels = readonly string[];
|
|
4
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
|
|
6
|
+
export type TupleFactory<T extends Tuple = Tuple> = {
|
|
7
7
|
<const T_ extends T>(...args: T_): T_;
|
|
8
8
|
typed<T_ extends T>(...exemplar: T_ | []): TupleFactory<Readonly<T_>>;
|
|
9
9
|
};
|
|
10
|
-
type ObjectOf<T extends
|
|
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
|
|
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
|
|
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
19
|
typed<T_ extends T>(...exemplar: T_ | []): StructFactory<Readonly<T_>, L>;
|
|
@@ -22,6 +22,6 @@ export type StructFactory<T extends BaseTuple, L extends BaseLabels> = {
|
|
|
22
22
|
}, L>;
|
|
23
23
|
};
|
|
24
24
|
export declare const _tupleCache: InternCache<number, readonly unknown[], readonly unknown[]>;
|
|
25
|
-
export declare const tuple: TupleFactory<
|
|
26
|
-
export declare const struct: <const L extends readonly string[]>(labels: L) => StructFactory<
|
|
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];
|
package/dist/intern-cache.js
CHANGED
|
@@ -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.
|
|
5
|
-
"description": "
|
|
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
|
-
|
|
33
|
-
|
|
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": "^
|
|
47
|
+
"vitest": "^4.1.12"
|
|
47
48
|
},
|
|
48
49
|
"files": [
|
|
49
50
|
"./dist"
|