tuple-ts 0.5.0 → 0.7.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 +136 -164
- package/dist/index.d.ts +42 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -37
- package/dist/intern-cache.d.ts +4 -4
- package/dist/intern-cache.d.ts.map +1 -1
- package/dist/intern-cache.js +8 -8
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
# tuple-ts
|
|
2
2
|
|
|
3
|
-
**Immutable tuples and
|
|
3
|
+
**Immutable interned tuples and named tuples for JavaScript and TypeScript.**
|
|
4
4
|
|
|
5
|
-
JavaScript normally compares arrays
|
|
5
|
+
JavaScript normally compares arrays by reference:
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
|
-
[1, 2] === [1, 2] // false
|
|
9
|
-
|
|
10
|
-
{ x: 1 } === { x: 1 } // false
|
|
8
|
+
[1, 2] === [1, 2]; // false
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
`tuple-ts` gives
|
|
11
|
+
`tuple-ts` gives tuples value-based identity through *interning*:
|
|
14
12
|
|
|
15
13
|
```ts
|
|
16
|
-
|
|
14
|
+
import { tuple } from "tuple-ts";
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
tuple(1, 2) === tuple(1, 2); // true
|
|
19
17
|
```
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
Equivalent tuples created through the same tuple cache are canonicalised to the same object. Values are frozen, so their contents cannot change after interning. This makes tuples useful as composite `Map` keys, `Set` members, memoization keys, and immutable values in functional TypeScript.
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
`tuple-ts` also supports **named tuples** through `struct()`: values remain arrays with positional slots, while the declared labels are available as named properties.
|
|
24
22
|
|
|
25
23
|
**Zero dependencies · Less than 1 KB gzipped**
|
|
26
24
|
|
|
@@ -32,7 +30,7 @@ npm install tuple-ts
|
|
|
32
30
|
|
|
33
31
|
## Tuples
|
|
34
32
|
|
|
35
|
-
Create
|
|
33
|
+
Create a tuple with narrow TypeScript inference:
|
|
36
34
|
|
|
37
35
|
```ts
|
|
38
36
|
import { tuple } from "tuple-ts";
|
|
@@ -40,11 +38,12 @@ import { tuple } from "tuple-ts";
|
|
|
40
38
|
const point = tuple(10, 20);
|
|
41
39
|
// ^? readonly [10, 20]
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
point[0]; // 10
|
|
42
|
+
point[1]; // 20
|
|
43
|
+
Object.isFrozen(point); // true
|
|
45
44
|
```
|
|
46
45
|
|
|
47
|
-
Calling `tuple` again with the same values
|
|
46
|
+
Calling `tuple()` again with the same values returns the same canonical object:
|
|
48
47
|
|
|
49
48
|
```ts
|
|
50
49
|
const a = tuple(10, 20);
|
|
@@ -54,14 +53,14 @@ a === b; // true
|
|
|
54
53
|
Object.is(a, b); // true
|
|
55
54
|
```
|
|
56
55
|
|
|
57
|
-
Different values
|
|
56
|
+
Different values or a different element order produce different tuples:
|
|
58
57
|
|
|
59
58
|
```ts
|
|
60
59
|
tuple(10, 20) === tuple(10, 21); // false
|
|
61
60
|
tuple(10, 20) === tuple(20, 10); // false
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
Tuples are ordinary frozen arrays
|
|
63
|
+
Tuples are ordinary frozen arrays:
|
|
65
64
|
|
|
66
65
|
```ts
|
|
67
66
|
const t = tuple("hello", 42, true);
|
|
@@ -69,20 +68,48 @@ const t = tuple("hello", 42, true);
|
|
|
69
68
|
t.length; // 3
|
|
70
69
|
t[0]; // "hello"
|
|
71
70
|
[...t]; // ["hello", 42, true]
|
|
71
|
+
Array.isArray(t); // true
|
|
72
72
|
Object.isFrozen(t); // true
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### Constraining a tuple factory with `.narrow()`
|
|
76
|
+
|
|
77
|
+
Use `.narrow()` to make a reusable factory whose arguments must satisfy a tuple type.
|
|
78
|
+
|
|
79
|
+
Specify the type explicitly:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const Point = tuple.narrow<[number, number]>();
|
|
83
|
+
|
|
84
|
+
const a = Point(10, 20);
|
|
85
|
+
const b = Point(10, 20);
|
|
86
|
+
|
|
87
|
+
a === b; // true
|
|
88
|
+
Point(10, "20"); // TypeScript error
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
You can also refine an existing fixed-length tuple factory using an exemplar. The exemplar is used to infer the constraint, not as a default value:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const Pair = tuple.narrow<[unknown, unknown]>();
|
|
95
|
+
const NumberPair = Pair.narrow([0, 0]);
|
|
96
|
+
|
|
97
|
+
NumberPair(10, 20); // OK
|
|
98
|
+
NumberPair(10, "20"); // TypeScript error
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
In this example, the parent factory establishes the two-slot shape, and the exemplar narrows the element types to numbers. `.narrow()` is a TypeScript constraint; it does not create a new cache. Narrowed factories continue to use the shared tuple cache, so equal tuple values are interned together regardless of which narrowed factory constructs them.
|
|
102
|
+
|
|
75
103
|
## Why is this useful?
|
|
76
104
|
|
|
77
105
|
### Composite `Map` keys
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
Without interning, separately constructed arrays don't work as value-based keys:
|
|
80
108
|
|
|
81
109
|
```ts
|
|
82
110
|
const map = new Map();
|
|
83
111
|
|
|
84
112
|
map.set([10, 20], "visited");
|
|
85
|
-
|
|
86
113
|
map.get([10, 20]); // undefined
|
|
87
114
|
```
|
|
88
115
|
|
|
@@ -92,18 +119,13 @@ With `tuple-ts`:
|
|
|
92
119
|
const map = new Map();
|
|
93
120
|
|
|
94
121
|
map.set(tuple(10, 20), "visited");
|
|
95
|
-
|
|
96
122
|
map.get(tuple(10, 20)); // "visited"
|
|
97
123
|
```
|
|
98
124
|
|
|
99
|
-
The tuple
|
|
100
|
-
|
|
101
|
-
This is particularly useful for things like coordinates, memoization keys, graph nodes, dynamic-programming states, and other composite values.
|
|
125
|
+
The tuple used for lookup is the same canonical object as the tuple used as the key. This is useful for coordinates, memoization keys, graph nodes, dynamic-programming states, and other composite values.
|
|
102
126
|
|
|
103
127
|
### `Set` of values
|
|
104
128
|
|
|
105
|
-
The same applies to `Set`:
|
|
106
|
-
|
|
107
129
|
```ts
|
|
108
130
|
const visited = new Set();
|
|
109
131
|
|
|
@@ -117,25 +139,16 @@ No serialization, stringification, or custom hashing is required.
|
|
|
117
139
|
|
|
118
140
|
## Nested tuples
|
|
119
141
|
|
|
120
|
-
Tuples can
|
|
142
|
+
Tuples can contain other interned tuples:
|
|
121
143
|
|
|
122
144
|
```ts
|
|
123
|
-
const a = tuple(
|
|
124
|
-
|
|
125
|
-
tuple(1, 0),
|
|
126
|
-
tuple(0, 1),
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const b = tuple(
|
|
130
|
-
"matrix",
|
|
131
|
-
tuple(1, 0),
|
|
132
|
-
tuple(0, 1),
|
|
133
|
-
);
|
|
145
|
+
const a = tuple("matrix", tuple(1, 0), tuple(0, 1));
|
|
146
|
+
const b = tuple("matrix", tuple(1, 0), tuple(0, 1));
|
|
134
147
|
|
|
135
148
|
a === b; // true
|
|
136
149
|
```
|
|
137
150
|
|
|
138
|
-
Because
|
|
151
|
+
Because the inner tuples are themselves canonicalised, the outer tuples can be interned as well.
|
|
139
152
|
|
|
140
153
|
```ts
|
|
141
154
|
const a = tuple("a", tuple("b", "c"));
|
|
@@ -146,215 +159,174 @@ a === b; // true
|
|
|
146
159
|
a === c; // false
|
|
147
160
|
```
|
|
148
161
|
|
|
149
|
-
##
|
|
162
|
+
## Named tuples with `struct()`
|
|
150
163
|
|
|
151
|
-
|
|
164
|
+
Use `struct()` when you want positional tuple storage with meaningful labels. Pass a readonly label list to create a reusable factory:
|
|
152
165
|
|
|
153
166
|
```ts
|
|
154
|
-
|
|
167
|
+
import { struct } from "tuple-ts";
|
|
155
168
|
|
|
156
|
-
const Point =
|
|
169
|
+
const Point = struct(["x", "y"]).narrow<[number, number]>();
|
|
157
170
|
|
|
158
|
-
const
|
|
159
|
-
const b = Point(10, 20);
|
|
171
|
+
const p = Point(10, 20);
|
|
160
172
|
|
|
161
|
-
|
|
173
|
+
p[0]; // 10
|
|
174
|
+
p[1]; // 20
|
|
175
|
+
p.x; // 10
|
|
176
|
+
p.y; // 20
|
|
177
|
+
|
|
178
|
+
Array.isArray(p); // true
|
|
179
|
+
Object.isFrozen(p); // true
|
|
162
180
|
```
|
|
163
181
|
|
|
164
|
-
The
|
|
182
|
+
The label order corresponds to the tuple slot order: the first label names slot `0`, the second names slot `1`, and so on. The named properties are added to the array when it is interned. They are own, enumerable properties and are frozen along with the array.
|
|
165
183
|
|
|
166
|
-
|
|
184
|
+
### Constructing from a named object with `.from()`
|
|
167
185
|
|
|
168
|
-
`
|
|
186
|
+
`.from()` accepts an object with the declared labels and creates the corresponding positional tuple:
|
|
169
187
|
|
|
170
188
|
```ts
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const a = record({
|
|
174
|
-
id: 1,
|
|
175
|
-
role: "admin",
|
|
176
|
-
});
|
|
189
|
+
const Point = struct(["x", "y"]).narrow<[number, number]>();
|
|
177
190
|
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
id: 1,
|
|
181
|
-
});
|
|
191
|
+
const a = Point.from({ x: 10, y: 20 });
|
|
192
|
+
const b = Point.from({ y: 20, x: 10 });
|
|
182
193
|
|
|
183
194
|
a === b; // true
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
Property order does not matter.
|
|
187
195
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
```ts
|
|
191
|
-
Object.isFrozen(a); // true
|
|
196
|
+
a[0]; // 10
|
|
197
|
+
a.x; // 10
|
|
192
198
|
```
|
|
193
199
|
|
|
194
|
-
|
|
200
|
+
The values are read in the schema's label order, regardless of the input object's property order. Extra properties on the input object are ignored:
|
|
195
201
|
|
|
196
202
|
```ts
|
|
197
|
-
const
|
|
203
|
+
const p = Point.from({ x: 10, y: 20, altitude: 100 });
|
|
198
204
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
"Alice",
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
users.get(
|
|
205
|
-
record({ role: "admin", id: 42 }),
|
|
206
|
-
); // "Alice"
|
|
205
|
+
p.x; // 10
|
|
206
|
+
p.y; // 20
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
The returned value contains the tuple slots and the schema's named properties, not the unrelated input properties.
|
|
210
|
+
|
|
211
|
+
### Constraining a struct factory
|
|
210
212
|
|
|
211
|
-
|
|
213
|
+
Use `.narrow()` to specify the tuple's positional types:
|
|
212
214
|
|
|
213
215
|
```ts
|
|
214
|
-
|
|
215
|
-
id: string;
|
|
216
|
-
name: string;
|
|
217
|
-
updatedAt: number;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const User = record<User>;
|
|
221
|
-
|
|
222
|
-
const a = User({
|
|
223
|
-
id: "alice",
|
|
224
|
-
name: "Alice",
|
|
225
|
-
updatedAt: 1000,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const b = User({
|
|
229
|
-
name: "Alice",
|
|
230
|
-
id: "alice",
|
|
231
|
-
updatedAt: 1000,
|
|
232
|
-
});
|
|
216
|
+
const Point = struct(["x", "y"]).narrow<[number, number]>();
|
|
233
217
|
|
|
234
|
-
|
|
218
|
+
Point(10, 20); // OK
|
|
219
|
+
Point(10, "20"); // TypeScript error
|
|
235
220
|
```
|
|
236
221
|
|
|
237
|
-
|
|
222
|
+
Use `.narrowFrom()` when you want to express the value types by label instead:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
type Coordinates = { x: number; y: number };
|
|
238
226
|
|
|
239
|
-
|
|
227
|
+
const Point = struct(["x", "y"]).narrowFrom<Coordinates>();
|
|
228
|
+
|
|
229
|
+
Point(10, 20); // OK
|
|
230
|
+
Point(10, "20"); // TypeScript error
|
|
231
|
+
Point.from({ x: 10, y: 20 }); // OK
|
|
232
|
+
```
|
|
240
233
|
|
|
241
|
-
|
|
234
|
+
You can also provide a typed exemplar to `.narrowFrom()`:
|
|
242
235
|
|
|
243
236
|
```ts
|
|
244
|
-
|
|
237
|
+
const coordinateExample: Coordinates = { x: 0, y: 0 };
|
|
238
|
+
const Point = struct(["x", "y"]).narrowFrom(coordinateExample);
|
|
239
|
+
```
|
|
245
240
|
|
|
246
|
-
const
|
|
241
|
+
The exemplar establishes types; its values are not used as defaults. If you pass an inline `const` exemplar whose properties are literal values, those literal types may be preserved. Give the exemplar a widened type, or use an explicit generic argument, when you want types such as `number` rather than the literal type `0`.
|
|
247
242
|
|
|
248
|
-
|
|
249
|
-
const b = Point({ x: 1, y: 2 });
|
|
243
|
+
Both `.narrow()` and `.narrowFrom()` preserve the cache associated with the original `struct()` schema. They refine the factory's TypeScript constraints rather than creating a new interning domain.
|
|
250
244
|
|
|
251
|
-
|
|
252
|
-
```
|
|
245
|
+
### Struct schemas and identity
|
|
253
246
|
|
|
254
|
-
|
|
247
|
+
Each call to `struct(labels)` creates a schema with its own intern cache. Factories narrowed from that schema share the cache, but separate calls to `struct()` create separate caches, even when given identical labels:
|
|
255
248
|
|
|
256
249
|
```ts
|
|
257
|
-
const
|
|
258
|
-
const
|
|
250
|
+
const PointA = struct(["x", "y"]).narrow<[number, number]>();
|
|
251
|
+
const PointB = struct(["x", "y"]).narrow<[number, number]>();
|
|
259
252
|
|
|
260
|
-
|
|
253
|
+
PointA(1, 2) === PointA(1, 2); // true
|
|
254
|
+
PointA(1, 2) === PointB(1, 2); // false
|
|
261
255
|
```
|
|
262
256
|
|
|
263
|
-
|
|
257
|
+
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.
|
|
258
|
+
|
|
259
|
+
### Choosing labels
|
|
260
|
+
|
|
261
|
+
Use distinct labels, and avoid labels that collide with array properties or indices. In particular, `"length"`, numeric-looking labels such as `"0"`, and method names such as `"map"` can conflict with normal array behaviour. The current implementation does not validate label names or duplicate labels, so callers should ensure labels are unique and suitable for named properties.
|
|
264
262
|
|
|
265
263
|
## Equality semantics
|
|
266
264
|
|
|
267
|
-
|
|
265
|
+
Tuple elements are compared shallowly using `Object.is`:
|
|
268
266
|
|
|
269
267
|
```ts
|
|
270
268
|
tuple(NaN) === tuple(NaN); // true
|
|
271
|
-
|
|
272
269
|
tuple(+0) === tuple(-0); // false
|
|
273
270
|
```
|
|
274
271
|
|
|
275
|
-
Object
|
|
276
|
-
|
|
277
|
-
The same object is the same value:
|
|
272
|
+
Object-valued elements retain JavaScript's normal reference identity:
|
|
278
273
|
|
|
279
274
|
```ts
|
|
280
275
|
const value = { x: 1 };
|
|
281
|
-
|
|
282
276
|
tuple(value) === tuple(value); // true
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Two separately created objects are different values:
|
|
286
277
|
|
|
287
|
-
```ts
|
|
288
278
|
tuple({ x: 1 }) === tuple({ x: 1 }); // false
|
|
289
279
|
```
|
|
290
280
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
Nested tuples and structs work particularly well because they are themselves canonical values.
|
|
294
|
-
|
|
295
|
-
## Immutable by design
|
|
296
|
-
|
|
297
|
-
Tuples and structs returned by `tuple-ts` are frozen:
|
|
281
|
+
`tuple-ts` does not recursively compare arbitrary JavaScript objects. Nested tuples work well because they are themselves interned:
|
|
298
282
|
|
|
299
283
|
```ts
|
|
300
|
-
const
|
|
284
|
+
const a = tuple(tuple(1, 2));
|
|
285
|
+
const b = tuple(tuple(1, 2));
|
|
301
286
|
|
|
302
|
-
|
|
287
|
+
a === b; // true
|
|
303
288
|
```
|
|
304
289
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
## Garbage-collection friendly
|
|
308
|
-
|
|
309
|
-
Interning values creates a natural question: *doesn't the cache grow forever?*
|
|
290
|
+
Struct caches use the same shallow slot-value comparison, but each struct schema has its own cache. Within a schema, the label properties are determined by the schema; the indexed slot values determine which canonical tuple is returned.
|
|
310
291
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
In other words, you don't have to manually maintain a global cache of every tuple you've ever created.
|
|
314
|
-
|
|
315
|
-
## TypeScript first
|
|
292
|
+
## Immutable by design
|
|
316
293
|
|
|
317
|
-
|
|
294
|
+
Tuples and structs returned by `tuple-ts` are frozen. Freezing is shallow, just like `Object.freeze()`:
|
|
318
295
|
|
|
319
296
|
```ts
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
42,
|
|
323
|
-
true,
|
|
324
|
-
);
|
|
297
|
+
const details = { name: "Alice" };
|
|
298
|
+
const value = tuple(details);
|
|
325
299
|
|
|
326
|
-
|
|
300
|
+
details.name = "Bob"; // Allowed: the nested object itself is not frozen
|
|
327
301
|
```
|
|
328
302
|
|
|
329
|
-
The
|
|
303
|
+
The tuple's reference to `details` is unchanged, so its interned identity remains the same. If you need immutable nested values, construct those values with `tuple()` or `struct()` too.
|
|
330
304
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
305
|
+
## Garbage-collection friendly
|
|
306
|
+
|
|
307
|
+
Interning raises a natural question: *doesn't the cache grow forever?* The cache implementation uses weak references and finalization support so that values no longer referenced by the application can be reclaimed by the JavaScript garbage collector. Values are not intended to be kept alive forever simply because they were once constructed.
|
|
334
308
|
|
|
335
309
|
## When should I use it?
|
|
336
310
|
|
|
337
|
-
`tuple-ts` is
|
|
311
|
+
`tuple-ts` is useful when you want small immutable values with stable identity:
|
|
338
312
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
313
|
+
- Composite `Map` keys
|
|
314
|
+
- `Set` membership
|
|
315
|
+
- Memoization keys
|
|
316
|
+
- Graph and search states
|
|
317
|
+
- Coordinates and other multi-dimensional values
|
|
318
|
+
- Functional programming
|
|
319
|
+
- Immutable application state
|
|
320
|
+
- Named, slot-based values such as points or pairs
|
|
347
321
|
|
|
348
|
-
|
|
322
|
+
Use `tuple()` for positional values and `struct()` when the same kind of value benefits from stable field labels.
|
|
349
323
|
|
|
350
324
|
## Design
|
|
351
325
|
|
|
352
|
-
The
|
|
353
|
-
|
|
354
|
-
The intern cache uses weak references, so canonical values do not need to be kept alive solely because they have previously been created.
|
|
326
|
+
The intern cache uses hashing to find candidates, then checks exact shallow equality before reusing an existing value. Hash collisions therefore do not determine equality. Tuple element order matters. Struct label order defines the mapping between slots and names, and each schema has its own cache.
|
|
355
327
|
|
|
356
328
|
There are no runtime dependencies.
|
|
357
329
|
|
|
358
330
|
## License
|
|
359
331
|
|
|
360
|
-
ISC
|
|
332
|
+
ISC
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
import InternCache from "./intern-cache";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
type BaseTuple = readonly unknown[];
|
|
3
|
+
type BaseLabels = readonly string[];
|
|
4
|
+
type ObjectOf<T extends BaseTuple, L extends BaseLabels> = {
|
|
5
|
+
[I in Extract<keyof L, `${number}`> as L[I]]: T[I extends `${infer N extends number}` ? N : never];
|
|
6
|
+
};
|
|
7
|
+
type StructOf<T extends BaseTuple, L extends BaseLabels> = Readonly<T & ObjectOf<T, L>>;
|
|
8
|
+
type TupleFromObject<T extends BaseTuple, L extends BaseLabels, O extends ObjectOf<T, L>> = T & {
|
|
9
|
+
[I in keyof L]: O[L[I]];
|
|
10
|
+
};
|
|
11
|
+
type StructFactory<T extends BaseTuple, L extends BaseLabels> = (<const T_ extends T>(...args: T_) => StructOf<T_, L>) & {
|
|
12
|
+
from: <const O extends ObjectOf<T, L>>(arg: O) => Readonly<O & TupleFromObject<T, L, O>>;
|
|
13
|
+
narrow: <const T_ extends T>() => StructFactory<T_, L>;
|
|
14
|
+
narrowFrom: <const O extends ObjectOf<T, L>>(..._args: [exemplar: O] | []) => StructFactory<{
|
|
15
|
+
[I in keyof L]: O[L[I]];
|
|
16
|
+
}, L>;
|
|
17
|
+
};
|
|
18
|
+
export declare const _tupleCache: InternCache<number, readonly unknown[], readonly unknown[]>;
|
|
19
|
+
export declare const tuple: (<const T_ extends BaseTuple>(...args: T_) => T_) & {
|
|
20
|
+
narrow: <T_ extends BaseTuple>(..._args: [] | [exemplar: T_]) => (<const T__1 extends T_>(...args: T__1) => T__1) & {
|
|
21
|
+
narrow: <T__1 extends T_>(..._args: [] | [exemplar: T__1]) => (<const T__2 extends T__1>(...args: T__2) => T__2) & {
|
|
22
|
+
narrow: <T__2 extends T__1>(..._args: [] | [exemplar: T__2]) => (<const T__3 extends T__2>(...args: T__3) => T__3) & {
|
|
23
|
+
narrow: <T__3 extends T__2>(..._args: [] | [exemplar: T__3]) => (<const T__4 extends T__3>(...args: T__4) => T__4) & {
|
|
24
|
+
narrow: <T__4 extends T__3>(..._args: [] | [exemplar: T__4]) => (<const T__5 extends T__4>(...args: T__5) => T__5) & {
|
|
25
|
+
narrow: <T__5 extends T__4>(..._args: [] | [exemplar: T__5]) => (<const T__6 extends T__5>(...args: T__6) => T__6) & {
|
|
26
|
+
narrow: <T__6 extends T__5>(..._args: [] | [exemplar: T__6]) => (<const T__7 extends T__6>(...args: T__7) => T__7) & {
|
|
27
|
+
narrow: <T__7 extends T__6>(..._args: [] | [exemplar: T__7]) => (<const T__8 extends T__7>(...args: T__8) => T__8) & {
|
|
28
|
+
narrow: <T__8 extends T__7>(..._args: [] | [exemplar: T__8]) => (<const T__9 extends T__8>(...args: T__9) => T__9) & {
|
|
29
|
+
narrow: <T__9 extends T__8>(..._args: [] | [exemplar: T__9]) => (<const T__10 extends T__9>(...args: T__10) => T__10) & {
|
|
30
|
+
narrow: <T__10 extends T__9>(..._args: [] | [exemplar: T__10]) => (<const T__11 extends T__10>(...args: T__11) => T__11) & /*elided*/ any;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export declare const struct: <const L extends readonly string[]>(labels: L) => StructFactory<BaseTuple, L>;
|
|
43
|
+
export {};
|
|
11
44
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,gBAAgB,CAAC;AAGzC,KAAK,SAAS,GAAG,SAAS,OAAO,EAAE,CAAC;AACpC,KAAK,UAAU,GAAG,SAAS,MAAM,EAAE,CAAC;AAqBpC,KAAK,QAAQ,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,IAAI;KACxD,CAAC,IAAI,OAAO,CACX,MAAM,CAAC,EACP,GAAG,MAAM,EAAE,CACZ,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;CACjE,CAAC;AACF,KAAK,QAAQ,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,IAAI,QAAQ,CACjE,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CACnB,CAAC;AACF,KAAK,eAAe,CAClB,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,UAAU,EACpB,CAAC,SAAS,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,IACtB,CAAC,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAEpC,KAAK,aAAa,CAAC,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,IAAI,CAAC,CAC/D,KAAK,CAAC,EAAE,SAAS,CAAC,EAElB,GAAG,IAAI,EAAE,EAAE,KACR,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG;IACtB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EACnC,GAAG,EAAE,CAAC,KACH,QAAQ,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,OAAO,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACvD,UAAU,EAAE,CACV,KAAK,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAG9B,GAAG,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,KACzB,aAAa,CAAC;SAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAAE,EAAE,CAAC,CAAC,CAAC;CACpD,CAAC;AA4CF,eAAO,MAAM,WAAW,6DAIvB,CAAC;AAEF,eAAO,MAAM,KAAK,UAvFS,EAAE;aAEX,EAAE;;;;;;;;;;;;;;;;;;;;;CAqFyB,CAAC;AAE9C,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,CAAC,SAAS,SAAS,MAAM,EAAE,EAAE,QAAQ,CAAC,gCAelE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,41 +9,36 @@ function arrayEquivalent(a1, a2) {
|
|
|
9
9
|
}
|
|
10
10
|
return true;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (new Set(keys).size !== keys.length) {
|
|
45
|
-
throw new DuplicateKeyError();
|
|
46
|
-
}
|
|
47
|
-
const cache = new InternCache((s) => hash.valuesHash(keys.map((k) => s[k])), (s1, s2) => keys.every((k) => Object.is(s1[k], s2[k])), (r) => Object.fromEntries(keys.map((k) => [k, r[k]])));
|
|
48
|
-
return (s) => cache.get(Object.fromEntries(keys.map((k) => [k, s[k]])));
|
|
12
|
+
const defineTuple = () => {
|
|
13
|
+
const construct = (...args) => _tupleCache.get(args);
|
|
14
|
+
const narrow = (..._args) => defineTuple();
|
|
15
|
+
return Object.assign(construct, { narrow });
|
|
16
|
+
};
|
|
17
|
+
const defineStruct = (cache, labels) => {
|
|
18
|
+
const construct = (...args) => {
|
|
19
|
+
return cache.get(args);
|
|
20
|
+
};
|
|
21
|
+
const fromTuple = (...args) => {
|
|
22
|
+
return construct(...args);
|
|
23
|
+
};
|
|
24
|
+
const fromObject = (arg) => {
|
|
25
|
+
const args = labels.map((label) => arg[label]);
|
|
26
|
+
return construct(...args);
|
|
27
|
+
};
|
|
28
|
+
const narrow = (..._args) => defineStruct(cache, labels);
|
|
29
|
+
const narrowFrom = (..._args) => defineStruct(cache, labels);
|
|
30
|
+
return Object.assign(fromTuple, { from: fromObject, narrow, narrowFrom });
|
|
31
|
+
};
|
|
32
|
+
export const _tupleCache = new InternCache(hash.valuesHash, arrayEquivalent, Object.freeze);
|
|
33
|
+
export const tuple = defineTuple();
|
|
34
|
+
export const struct = (labels) => {
|
|
35
|
+
const cacheInjestFn = (tuple) => {
|
|
36
|
+
const labeledTuple = tuple;
|
|
37
|
+
labels.forEach((label, index) => {
|
|
38
|
+
labeledTuple[label] = tuple[index];
|
|
39
|
+
});
|
|
40
|
+
return Object.freeze(labeledTuple);
|
|
41
|
+
};
|
|
42
|
+
const cache = new InternCache(hash.valuesHash, arrayEquivalent, cacheInjestFn);
|
|
43
|
+
return defineStruct(cache, labels);
|
|
49
44
|
};
|
package/dist/intern-cache.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export default class<H,
|
|
1
|
+
export default class<H, K, V extends object> {
|
|
2
2
|
private readonly hashFn;
|
|
3
3
|
private readonly matchFn;
|
|
4
4
|
private readonly injestFn;
|
|
5
5
|
private readonly map;
|
|
6
|
-
private cleanupRegistry;
|
|
7
|
-
constructor(hashFn: (
|
|
6
|
+
private readonly cleanupRegistry;
|
|
7
|
+
constructor(hashFn: (key: K) => H, matchFn: (key: K, value: V) => boolean, injestFn: (key: K) => V);
|
|
8
8
|
private remove;
|
|
9
|
-
get<
|
|
9
|
+
get<K_ extends K, V_ extends V>(key: K_): V_;
|
|
10
10
|
get size(): number;
|
|
11
11
|
reset(): void;
|
|
12
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intern-cache.d.ts","sourceRoot":"","sources":["../src/intern-cache.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAO,CAAC,EAAE,CAAC,SAAS,MAAM;
|
|
1
|
+
{"version":3,"file":"intern-cache.d.ts","sourceRoot":"","sources":["../src/intern-cache.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM;IAOvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAsC;IAC1D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAE9B;gBAGiB,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EACrB,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,EACtC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;IAG1C,OAAO,CAAC,MAAM;IAWd,GAAG,CAAC,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE;IAsB5C,IAAI,IAAI,IAAI,MAAM,CAKjB;IAED,KAAK,IAAI,IAAI;CAId"}
|
package/dist/intern-cache.js
CHANGED
|
@@ -6,12 +6,12 @@ export default class {
|
|
|
6
6
|
this.map = new Map();
|
|
7
7
|
this.cleanupRegistry = new FinalizationRegistry((x) => this.remove(x));
|
|
8
8
|
}
|
|
9
|
-
remove([hash,
|
|
9
|
+
remove([hash, valueRef]) {
|
|
10
10
|
const hashBucket = this.map.get(hash);
|
|
11
11
|
if (!hashBucket) {
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
|
-
hashBucket.delete(
|
|
14
|
+
hashBucket.delete(valueRef);
|
|
15
15
|
if (hashBucket.size === 0) {
|
|
16
16
|
this.map.delete(hash);
|
|
17
17
|
}
|
|
@@ -32,17 +32,17 @@ export default class {
|
|
|
32
32
|
this.map.set(hash, hashBucket);
|
|
33
33
|
}
|
|
34
34
|
// Object is not in the cache. Add it and return it.
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
hashBucket.add(
|
|
38
|
-
this.cleanupRegistry.register(
|
|
39
|
-
return
|
|
35
|
+
const value = this.injestFn(key);
|
|
36
|
+
const valueRef = new WeakRef(value);
|
|
37
|
+
hashBucket.add(valueRef);
|
|
38
|
+
this.cleanupRegistry.register(value, [hash, valueRef], this);
|
|
39
|
+
return value;
|
|
40
40
|
}
|
|
41
41
|
get size() {
|
|
42
42
|
return Array.from(this.map.entries(), ([_, b]) => b.size).reduce((acc, x) => acc + x, 0);
|
|
43
43
|
}
|
|
44
44
|
reset() {
|
|
45
|
+
this.cleanupRegistry.unregister(this);
|
|
45
46
|
this.map.clear();
|
|
46
|
-
this.cleanupRegistry = new FinalizationRegistry((x) => this.remove(x));
|
|
47
47
|
}
|
|
48
48
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "tuple-ts",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"description": "Tuples and structs for TypeScript and JavaScript with equality and identity semantics.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tuple",
|
|
8
8
|
"struct",
|
|
9
|
+
"labeled",
|
|
10
|
+
"labelled",
|
|
11
|
+
"labels",
|
|
12
|
+
"names",
|
|
13
|
+
"namedtuple",
|
|
14
|
+
"named-tuple",
|
|
15
|
+
"python",
|
|
9
16
|
"equality",
|
|
10
17
|
"identity",
|
|
11
18
|
"shallow-equality",
|