tuple-ts 0.4.7 → 0.6.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 +319 -42
- package/dist/index.d.ts +75 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -12
- package/dist/intern-cache.d.ts +4 -4
- package/dist/intern-cache.d.ts.map +1 -1
- package/dist/intern-cache.js +5 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# tuple-ts
|
|
2
2
|
|
|
3
|
-
**Immutable tuples and structs with value-based identity for JavaScript and TypeScript.**
|
|
3
|
+
**Immutable tuples, records and structs with value-based identity for JavaScript and TypeScript.**
|
|
4
4
|
|
|
5
5
|
JavaScript normally compares arrays and objects by reference:
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
[1, 2] === [1, 2] // false
|
|
9
9
|
|
|
10
|
-
{ x: 1 } === { x: 1 } // false
|
|
10
|
+
({ x: 1 }) === ({ x: 1 }) // false
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
`tuple-ts` gives you a different model:
|
|
@@ -15,12 +15,12 @@ JavaScript normally compares arrays and objects by reference:
|
|
|
15
15
|
```ts
|
|
16
16
|
tuple(1, 2) === tuple(1, 2) // true
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
record({ x: 1 }) === record({ x: 1 }) // true
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Equal values are **interned to the same object**.
|
|
22
22
|
|
|
23
|
-
That makes tuples and structs immutable, naturally usable as composite `Map` keys and `Set` values, and particularly pleasant to use in functional TypeScript.
|
|
23
|
+
That makes tuples, records and structs immutable, naturally usable as composite `Map` keys and `Set` values, and particularly pleasant to use in functional TypeScript.
|
|
24
24
|
|
|
25
25
|
**Zero dependencies · Less than 1 KB gzipped**
|
|
26
26
|
|
|
@@ -148,12 +148,31 @@ a === c; // false
|
|
|
148
148
|
|
|
149
149
|
## Typed tuples
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
Use `tuple.like()` to create a reusable constructor for a particular tuple type.
|
|
152
|
+
|
|
153
|
+
### Inferring the type from an exemplar
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const Point = tuple.like(0, 0);
|
|
157
|
+
|
|
158
|
+
const a = Point(10, 20);
|
|
159
|
+
const b = Point(10, 20);
|
|
160
|
+
|
|
161
|
+
a === b; // true
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The exemplar determines the TypeScript type expected by the resulting factory. Its values are not used as defaults.
|
|
165
|
+
|
|
166
|
+
By default, TypeScript widens the exemplar's literal types, so `tuple.like(0, 0)` produces a factory accepting two numbers.
|
|
167
|
+
|
|
168
|
+
### Explicit type arguments
|
|
169
|
+
|
|
170
|
+
Alternatively, specify the type directly:
|
|
152
171
|
|
|
153
172
|
```ts
|
|
154
173
|
type Point = [x: number, y: number];
|
|
155
174
|
|
|
156
|
-
const Point = tuple<Point
|
|
175
|
+
const Point = tuple.like<Point>();
|
|
157
176
|
|
|
158
177
|
const a = Point(10, 20);
|
|
159
178
|
const b = Point(10, 20);
|
|
@@ -161,21 +180,34 @@ const b = Point(10, 20);
|
|
|
161
180
|
a === b; // true
|
|
162
181
|
```
|
|
163
182
|
|
|
164
|
-
|
|
183
|
+
No exemplar is required when the type argument is provided explicitly.
|
|
165
184
|
|
|
166
|
-
|
|
185
|
+
### Preserving literal types
|
|
167
186
|
|
|
168
|
-
|
|
187
|
+
To preserve literal types rather than widen them, use `as const`:
|
|
169
188
|
|
|
170
189
|
```ts
|
|
171
|
-
|
|
190
|
+
const Direction = tuple.like("north" as const, 1 as const);
|
|
191
|
+
|
|
192
|
+
Direction("north", 1); // OK
|
|
193
|
+
Direction("south", 1); // TypeScript error
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
In all cases, the resulting tuples are frozen and interned using the same shared cache as ordinary tuples.
|
|
197
|
+
|
|
198
|
+
## Records
|
|
199
|
+
|
|
200
|
+
`record` provides the same value-based identity semantics for objects:
|
|
172
201
|
|
|
173
|
-
|
|
202
|
+
```ts
|
|
203
|
+
import { record } from "tuple-ts";
|
|
204
|
+
|
|
205
|
+
const a = record({
|
|
174
206
|
id: 1,
|
|
175
207
|
role: "admin",
|
|
176
208
|
});
|
|
177
209
|
|
|
178
|
-
const b =
|
|
210
|
+
const b = record({
|
|
179
211
|
role: "admin",
|
|
180
212
|
id: 1,
|
|
181
213
|
});
|
|
@@ -185,7 +217,7 @@ a === b; // true
|
|
|
185
217
|
|
|
186
218
|
Property order does not matter.
|
|
187
219
|
|
|
188
|
-
|
|
220
|
+
Records are frozen:
|
|
189
221
|
|
|
190
222
|
```ts
|
|
191
223
|
Object.isFrozen(a); // true
|
|
@@ -197,18 +229,36 @@ And they work naturally as `Map` keys:
|
|
|
197
229
|
const users = new Map();
|
|
198
230
|
|
|
199
231
|
users.set(
|
|
200
|
-
|
|
232
|
+
record({ id: 42, role: "admin" }),
|
|
201
233
|
"Alice",
|
|
202
234
|
);
|
|
203
235
|
|
|
204
236
|
users.get(
|
|
205
|
-
|
|
237
|
+
record({ role: "admin", id: 42 }),
|
|
206
238
|
); // "Alice"
|
|
207
239
|
```
|
|
208
240
|
|
|
209
|
-
###
|
|
241
|
+
### Record identity
|
|
210
242
|
|
|
211
|
-
|
|
243
|
+
A record's identity is determined by its own, enumerable, string-keyed properties and their values.
|
|
244
|
+
|
|
245
|
+
Two records are equivalent if they contain exactly the same keys, with equivalent values, regardless of property order.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
record({ x: 1, y: 2 }) ===
|
|
249
|
+
record({ y: 2, x: 1 }); // true
|
|
250
|
+
|
|
251
|
+
record({ x: 1 }) ===
|
|
252
|
+
record({ x: 1, y: 2 }); // false
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Symbol-keyed, inherited and non-enumerable properties are not copied into records.
|
|
256
|
+
|
|
257
|
+
Numeric property keys are supported and follow JavaScript's normal conversion to string keys.
|
|
258
|
+
|
|
259
|
+
### Typed records
|
|
260
|
+
|
|
261
|
+
Use `record.like()` to create a reusable typed record constructor:
|
|
212
262
|
|
|
213
263
|
```ts
|
|
214
264
|
interface User {
|
|
@@ -217,7 +267,7 @@ interface User {
|
|
|
217
267
|
updatedAt: number;
|
|
218
268
|
}
|
|
219
269
|
|
|
220
|
-
const User =
|
|
270
|
+
const User = record.like<User>();
|
|
221
271
|
|
|
222
272
|
const a = User({
|
|
223
273
|
id: "alice",
|
|
@@ -234,9 +284,151 @@ const b = User({
|
|
|
234
284
|
a === b; // true
|
|
235
285
|
```
|
|
236
286
|
|
|
287
|
+
Alternatively, infer the type from an exemplar:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const User = record.like({
|
|
291
|
+
id: "",
|
|
292
|
+
name: "",
|
|
293
|
+
updatedAt: 0,
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
As with `tuple.like()`, the exemplar defines the type; it does not supply default values.
|
|
298
|
+
|
|
299
|
+
The resulting factory requires the properties established by `like()`, but may also accept additional properties.
|
|
300
|
+
|
|
301
|
+
Unlike `struct`, `record` preserves those additional properties, and they contribute to identity.
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
const User = record.like({ id: "" });
|
|
305
|
+
|
|
306
|
+
const a = User({ id: "alice" });
|
|
307
|
+
const b = User({ id: "alice", role: "admin" });
|
|
308
|
+
|
|
309
|
+
a === b; // false
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Structs
|
|
313
|
+
|
|
314
|
+
`record` interns whatever properties you give it. If you want a fixed set of keys to define identity, use `struct`.
|
|
315
|
+
|
|
316
|
+
`struct` takes key names as arguments and returns a factory. The factory accepts any object with at least those keys, extracts **exactly the declared keys**, and interns the result.
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import { struct } from "tuple-ts";
|
|
320
|
+
|
|
321
|
+
const Point = struct("x", "y");
|
|
322
|
+
|
|
323
|
+
const a = Point({ x: 1, y: 2 });
|
|
324
|
+
const b = Point({ x: 1, y: 2 });
|
|
325
|
+
|
|
326
|
+
a === b; // true
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Additional properties are discarded
|
|
330
|
+
|
|
331
|
+
Unlike `record`, a struct factory ignores properties that are not part of its declared structure:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const Point = struct("x", "y");
|
|
335
|
+
|
|
336
|
+
const a = Point({ x: 1, y: 2 });
|
|
337
|
+
const b = Point({ x: 1, y: 2, z: 3 });
|
|
338
|
+
|
|
339
|
+
a === b; // true
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The resulting objects contain only `x` and `y`:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
Object.keys(b); // ["x", "y"]
|
|
346
|
+
|
|
347
|
+
b.z; // TypeScript error
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
This makes structs useful for projecting larger objects into canonical values with a precisely defined shape.
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
const Position = struct("x", "y");
|
|
354
|
+
|
|
355
|
+
const location = {
|
|
356
|
+
x: 10,
|
|
357
|
+
y: 20,
|
|
358
|
+
altitude: 100,
|
|
359
|
+
timestamp: Date.now(),
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const position = Position(location);
|
|
363
|
+
// { x: 10, y: 20 }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The factory is deliberately permissive about additional input properties, but strict about which properties appear in its output.
|
|
367
|
+
|
|
368
|
+
### Typed structs
|
|
369
|
+
|
|
370
|
+
Use `struct(...keys).like()` to constrain the types of the declared properties.
|
|
371
|
+
|
|
372
|
+
The type can be inferred from an exemplar:
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
const Point = struct("x", "y").like({
|
|
376
|
+
x: 0,
|
|
377
|
+
y: 0,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
Point({ x: 10, y: 20 }); // OK
|
|
381
|
+
Point({ x: "10", y: 20 }); // TypeScript error
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Or provided explicitly:
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
type Coordinates = {
|
|
388
|
+
x: number;
|
|
389
|
+
y: number;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const Point = struct("x", "y").like<Coordinates>();
|
|
393
|
+
|
|
394
|
+
Point({ x: 10, y: 20 }); // OK
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
As with the other factories, `like()` defines types, not default values.
|
|
398
|
+
|
|
399
|
+
### Strict definitions, flexible construction
|
|
400
|
+
|
|
401
|
+
There is an intentional distinction between defining a struct and constructing one.
|
|
402
|
+
|
|
403
|
+
A `like()` exemplar must contain only the keys declared by `struct`:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
const Point = struct("x", "y");
|
|
407
|
+
|
|
408
|
+
Point.like({
|
|
409
|
+
x: 0,
|
|
410
|
+
y: 0,
|
|
411
|
+
z: 0, // TypeScript error: unexpected property
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
But the resulting factory accepts extra properties and discards them:
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
const Point = struct("x", "y").like({
|
|
419
|
+
x: 0,
|
|
420
|
+
y: 0,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
Point({ x: 10, y: 20, z: 30 }); // OK
|
|
424
|
+
// { x: 10, y: 20 }
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
This keeps struct definitions precise without making callers manually remove unrelated properties from their input objects.
|
|
428
|
+
|
|
237
429
|
## Equality semantics
|
|
238
430
|
|
|
239
|
-
|
|
431
|
+
Values are compared using [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is):
|
|
240
432
|
|
|
241
433
|
```ts
|
|
242
434
|
tuple(NaN) === tuple(NaN); // true
|
|
@@ -262,19 +454,54 @@ tuple({ x: 1 }) === tuple({ x: 1 }); // false
|
|
|
262
454
|
|
|
263
455
|
This is **shallow equality**. `tuple-ts` does not recursively compare arbitrary JavaScript objects.
|
|
264
456
|
|
|
265
|
-
Nested tuples and structs work particularly well because they are themselves canonical values
|
|
457
|
+
Nested tuples, records and structs work particularly well because they are themselves canonical values:
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
const a = record({
|
|
461
|
+
position: tuple(10, 20),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const b = record({
|
|
465
|
+
position: tuple(10, 20),
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
a === b; // true
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
The outer records compare equal because the inner tuples have already been interned to the same object.
|
|
472
|
+
|
|
473
|
+
All three constructors share this model of identity.
|
|
266
474
|
|
|
267
475
|
## Immutable by design
|
|
268
476
|
|
|
269
|
-
Tuples and structs returned by `tuple-ts` are frozen:
|
|
477
|
+
Tuples, records and structs returned by `tuple-ts` are frozen:
|
|
270
478
|
|
|
271
479
|
```ts
|
|
272
480
|
const point = tuple(10, 20);
|
|
481
|
+
const user = record({ id: 1 });
|
|
482
|
+
const position = struct("x", "y")({ x: 1, y: 2 });
|
|
483
|
+
|
|
484
|
+
Object.isFrozen(point); // true
|
|
485
|
+
Object.isFrozen(user); // true
|
|
486
|
+
Object.isFrozen(position); // true
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
This is important to the identity semantics: once a value has been interned, its own properties cannot subsequently change while it is being used as a `Map` key or `Set` member.
|
|
490
|
+
|
|
491
|
+
Freezing is shallow. If an interned value contains a reference to an ordinary mutable object, that nested object is not automatically frozen.
|
|
492
|
+
|
|
493
|
+
For example:
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
const details = { name: "Alice" };
|
|
497
|
+
const user = record({ details });
|
|
273
498
|
|
|
274
|
-
|
|
499
|
+
details.name = "Bob"; // Allowed
|
|
275
500
|
```
|
|
276
501
|
|
|
277
|
-
|
|
502
|
+
The reference to `details` remains unchanged, so the interned record retains its identity.
|
|
503
|
+
|
|
504
|
+
If you need immutable nested values, construct them using `tuple`, `record` or `struct` as well.
|
|
278
505
|
|
|
279
506
|
## Garbage-collection friendly
|
|
280
507
|
|
|
@@ -282,40 +509,86 @@ Interning values creates a natural question: *doesn't the cache grow forever?*
|
|
|
282
509
|
|
|
283
510
|
`tuple-ts` uses `WeakRef` and `FinalizationRegistry` internally. Cached values are weakly referenced, allowing the JavaScript garbage collector to reclaim values that are no longer referenced by the application.
|
|
284
511
|
|
|
285
|
-
In other words, you don't have to manually maintain a global cache of every tuple you've ever created.
|
|
512
|
+
In other words, you don't have to manually maintain a global cache of every tuple, record or struct you've ever created.
|
|
286
513
|
|
|
287
514
|
## TypeScript first
|
|
288
515
|
|
|
289
|
-
`tuple` and `struct` are designed to preserve TypeScript's inference
|
|
516
|
+
`tuple`, `record` and `struct` are designed to preserve TypeScript's inference.
|
|
290
517
|
|
|
291
|
-
|
|
292
|
-
const value = tuple(
|
|
293
|
-
"hello",
|
|
294
|
-
42,
|
|
295
|
-
true,
|
|
296
|
-
);
|
|
518
|
+
### Literal inference
|
|
297
519
|
|
|
520
|
+
By default, constructors preserve literal types:
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
const t = tuple("hello", 42, true);
|
|
298
524
|
// readonly ["hello", 42, true]
|
|
525
|
+
|
|
526
|
+
const r = record({ x: 1, y: 2 });
|
|
527
|
+
// readonly properties x: 1, y: 2
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Reusable typed factories
|
|
531
|
+
|
|
532
|
+
Use `.like()` to define reusable constructors:
|
|
533
|
+
|
|
534
|
+
```ts
|
|
535
|
+
const Pair = tuple.like("", 0);
|
|
536
|
+
|
|
537
|
+
const User = record.like({
|
|
538
|
+
id: "",
|
|
539
|
+
name: "",
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const Point = struct("x", "y").like({
|
|
543
|
+
x: 0,
|
|
544
|
+
y: 0,
|
|
545
|
+
});
|
|
299
546
|
```
|
|
300
547
|
|
|
301
|
-
|
|
548
|
+
Or specify types explicitly:
|
|
302
549
|
|
|
303
550
|
```ts
|
|
304
|
-
|
|
551
|
+
const Pair = tuple.like<[string, number]>();
|
|
552
|
+
|
|
553
|
+
const User = record.like<{
|
|
554
|
+
id: string;
|
|
555
|
+
name: string;
|
|
556
|
+
}>();
|
|
557
|
+
|
|
558
|
+
const Point = struct("x", "y").like<{
|
|
559
|
+
x: number;
|
|
560
|
+
y: number;
|
|
561
|
+
}>();
|
|
305
562
|
```
|
|
306
563
|
|
|
564
|
+
In either form, TypeScript checks arguments to the resulting factories.
|
|
565
|
+
|
|
566
|
+
### Readonly results
|
|
567
|
+
|
|
568
|
+
All constructors return readonly types, reflecting the frozen runtime objects.
|
|
569
|
+
|
|
570
|
+
The readonly typing is shallow, just like `Object.freeze()`.
|
|
571
|
+
|
|
307
572
|
## When should I use it?
|
|
308
573
|
|
|
309
574
|
`tuple-ts` is particularly useful when you want **small immutable values with stable identity**:
|
|
310
575
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
576
|
+
- Composite `Map` keys
|
|
577
|
+
- `Set` membership
|
|
578
|
+
- Memoization keys
|
|
579
|
+
- Graph/search states
|
|
580
|
+
- Coordinates and other multi-dimensional values
|
|
581
|
+
- Functional programming
|
|
582
|
+
- Immutable application state
|
|
583
|
+
- Canonical representations of frequently repeated values
|
|
584
|
+
|
|
585
|
+
Choose between the three constructors according to your data:
|
|
586
|
+
|
|
587
|
+
| Constructor | Shape | Identity |
|
|
588
|
+
|---|---|---|
|
|
589
|
+
| `tuple` | Ordered sequence | Position and value of each element |
|
|
590
|
+
| `record` | Arbitrary object properties | Complete set of keys and values |
|
|
591
|
+
| `struct` | Fixed declared keys | Values of the declared keys only |
|
|
319
592
|
|
|
320
593
|
If you just need an ordinary mutable array or object, use one. `tuple-ts` is for when you want the thing to behave more like a **value**.
|
|
321
594
|
|
|
@@ -323,10 +596,14 @@ If you just need an ordinary mutable array or object, use one. `tuple-ts` is for
|
|
|
323
596
|
|
|
324
597
|
The implementation uses hashing to locate candidate values and then performs an exact shallow equality check before reusing an existing value. Hash collisions therefore do not determine equality.
|
|
325
598
|
|
|
599
|
+
Property order is ignored for records and structs, but element order matters for tuples.
|
|
600
|
+
|
|
326
601
|
The intern cache uses weak references, so canonical values do not need to be kept alive solely because they have previously been created.
|
|
327
602
|
|
|
603
|
+
Constructed records and structs are copied before interning, so callers cannot alter an interned value by modifying the source object.
|
|
604
|
+
|
|
328
605
|
There are no runtime dependencies.
|
|
329
606
|
|
|
330
607
|
## License
|
|
331
608
|
|
|
332
|
-
ISC
|
|
609
|
+
ISC
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
1
|
import InternCache from "./intern-cache";
|
|
2
|
+
type NoExtraKeys<T, K extends PropertyKey> = T & Record<Exclude<keyof T, K>, never>;
|
|
3
|
+
type BaseKey = string | number;
|
|
4
|
+
type BaseTuple = readonly unknown[];
|
|
5
|
+
type BaseRecord<K extends BaseKey> = Record<K, unknown>;
|
|
2
6
|
export declare const _tupleCache: InternCache<number, readonly unknown[]>;
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const tuple: <const
|
|
5
|
-
|
|
7
|
+
export declare const _recordCache: InternCache<number, Record<BaseKey, unknown>>;
|
|
8
|
+
export declare const tuple: (<const T_ extends BaseTuple>(...t: T_) => Readonly<T_>) & {
|
|
9
|
+
like: <T_ extends BaseTuple>(...exemplar: [] | T_) => (<const T__1 extends T_>(...t: T__1) => Readonly<T__1>) & {
|
|
10
|
+
like: <T__1 extends T_>(...exemplar: [] | T__1) => (<const T__2 extends T__1>(...t: T__2) => Readonly<T__2>) & {
|
|
11
|
+
like: <T__2 extends T__1>(...exemplar: [] | T__2) => (<const T__3 extends T__2>(...t: T__3) => Readonly<T__3>) & {
|
|
12
|
+
like: <T__3 extends T__2>(...exemplar: [] | T__3) => (<const T__4 extends T__3>(...t: T__4) => Readonly<T__4>) & {
|
|
13
|
+
like: <T__4 extends T__3>(...exemplar: [] | T__4) => (<const T__5 extends T__4>(...t: T__5) => Readonly<T__5>) & {
|
|
14
|
+
like: <T__5 extends T__4>(...exemplar: [] | T__5) => (<const T__6 extends T__5>(...t: T__6) => Readonly<T__6>) & {
|
|
15
|
+
like: <T__6 extends T__5>(...exemplar: [] | T__6) => (<const T__7 extends T__6>(...t: T__7) => Readonly<T__7>) & {
|
|
16
|
+
like: <T__7 extends T__6>(...exemplar: [] | T__7) => (<const T__8 extends T__7>(...t: T__8) => Readonly<T__8>) & {
|
|
17
|
+
like: <T__8 extends T__7>(...exemplar: [] | T__8) => (<const T__9 extends T__8>(...t: T__9) => Readonly<T__9>) & {
|
|
18
|
+
like: <T__9 extends T__8>(...exemplar: [] | T__9) => (<const T__10 extends T__9>(...t: T__10) => Readonly<T__10>) & {
|
|
19
|
+
like: <T__10 extends T__9>(...exemplar: [] | T__10) => (<const T__11 extends T__10>(...t: T__11) => Readonly<T__11>) & /*elided*/ any;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare const record: (<const R_ extends BaseRecord<BaseKey>>(r: R_) => Readonly<Pick<R_, Extract<keyof R_, BaseKey>>>) & {
|
|
32
|
+
like: <R_ extends BaseRecord<BaseKey>>(exemplar?: NoExtraKeys<R_, BaseKey> | undefined) => (<const R__1 extends R_>(r: R__1) => Readonly<Pick<R__1, Extract<keyof R__1, BaseKey>>>) & {
|
|
33
|
+
like: <R__1 extends R_>(exemplar?: NoExtraKeys<R__1, BaseKey> | undefined) => (<const R__2 extends R__1>(r: R__2) => Readonly<Pick<R__2, Extract<keyof R__2, BaseKey>>>) & {
|
|
34
|
+
like: <R__2 extends R__1>(exemplar?: NoExtraKeys<R__2, BaseKey> | undefined) => (<const R__3 extends R__2>(r: R__3) => Readonly<Pick<R__3, Extract<keyof R__3, BaseKey>>>) & {
|
|
35
|
+
like: <R__3 extends R__2>(exemplar?: NoExtraKeys<R__3, BaseKey> | undefined) => (<const R__4 extends R__3>(r: R__4) => Readonly<Pick<R__4, Extract<keyof R__4, BaseKey>>>) & {
|
|
36
|
+
like: <R__4 extends R__3>(exemplar?: NoExtraKeys<R__4, BaseKey> | undefined) => (<const R__5 extends R__4>(r: R__5) => Readonly<Pick<R__5, Extract<keyof R__5, BaseKey>>>) & {
|
|
37
|
+
like: <R__5 extends R__4>(exemplar?: NoExtraKeys<R__5, BaseKey> | undefined) => (<const R__6 extends R__5>(r: R__6) => Readonly<Pick<R__6, Extract<keyof R__6, BaseKey>>>) & {
|
|
38
|
+
like: <R__6 extends R__5>(exemplar?: NoExtraKeys<R__6, BaseKey> | undefined) => (<const R__7 extends R__6>(r: R__7) => Readonly<Pick<R__7, Extract<keyof R__7, BaseKey>>>) & {
|
|
39
|
+
like: <R__7 extends R__6>(exemplar?: NoExtraKeys<R__7, BaseKey> | undefined) => (<const R__8 extends R__7>(r: R__8) => Readonly<Pick<R__8, Extract<keyof R__8, BaseKey>>>) & {
|
|
40
|
+
like: <R__8 extends R__7>(exemplar?: NoExtraKeys<R__8, BaseKey> | undefined) => (<const R__9 extends R__8>(r: R__9) => Readonly<Pick<R__9, Extract<keyof R__9, BaseKey>>>) & {
|
|
41
|
+
like: <R__9 extends R__8>(exemplar?: NoExtraKeys<R__9, BaseKey> | undefined) => (<const R__10 extends R__9>(r: R__10) => Readonly<Pick<R__10, Extract<keyof R__10, BaseKey>>>) & {
|
|
42
|
+
like: <R__10 extends R__9>(exemplar?: NoExtraKeys<R__10, BaseKey> | undefined) => (<const R__11 extends R__10>(r: R__11) => Readonly<Pick<R__11, Extract<keyof R__11, BaseKey>>>) & /*elided*/ any;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
export declare const struct: <const K extends string>(...keys: readonly K[]) => (<const R_ extends BaseRecord<K>>(r: R_) => Readonly<Pick<R_, Extract<keyof R_, K>>>) & {
|
|
55
|
+
like: <R_ extends BaseRecord<K>>(exemplar?: NoExtraKeys<R_, K> | undefined) => (<const R__1 extends R_>(r: R__1) => Readonly<Pick<R__1, Extract<keyof R__1, K>>>) & {
|
|
56
|
+
like: <R__1 extends R_>(exemplar?: NoExtraKeys<R__1, K> | undefined) => (<const R__2 extends R__1>(r: R__2) => Readonly<Pick<R__2, Extract<keyof R__2, K>>>) & {
|
|
57
|
+
like: <R__2 extends R__1>(exemplar?: NoExtraKeys<R__2, K> | undefined) => (<const R__3 extends R__2>(r: R__3) => Readonly<Pick<R__3, Extract<keyof R__3, K>>>) & {
|
|
58
|
+
like: <R__3 extends R__2>(exemplar?: NoExtraKeys<R__3, K> | undefined) => (<const R__4 extends R__3>(r: R__4) => Readonly<Pick<R__4, Extract<keyof R__4, K>>>) & {
|
|
59
|
+
like: <R__4 extends R__3>(exemplar?: NoExtraKeys<R__4, K> | undefined) => (<const R__5 extends R__4>(r: R__5) => Readonly<Pick<R__5, Extract<keyof R__5, K>>>) & {
|
|
60
|
+
like: <R__5 extends R__4>(exemplar?: NoExtraKeys<R__5, K> | undefined) => (<const R__6 extends R__5>(r: R__6) => Readonly<Pick<R__6, Extract<keyof R__6, K>>>) & {
|
|
61
|
+
like: <R__6 extends R__5>(exemplar?: NoExtraKeys<R__6, K> | undefined) => (<const R__7 extends R__6>(r: R__7) => Readonly<Pick<R__7, Extract<keyof R__7, K>>>) & {
|
|
62
|
+
like: <R__7 extends R__6>(exemplar?: NoExtraKeys<R__7, K> | undefined) => (<const R__8 extends R__7>(r: R__8) => Readonly<Pick<R__8, Extract<keyof R__8, K>>>) & {
|
|
63
|
+
like: <R__8 extends R__7>(exemplar?: NoExtraKeys<R__8, K> | undefined) => (<const R__9 extends R__8>(r: R__9) => Readonly<Pick<R__9, Extract<keyof R__9, K>>>) & {
|
|
64
|
+
like: <R__9 extends R__8>(exemplar?: NoExtraKeys<R__9, K> | undefined) => (<const R__10 extends R__9>(r: R__10) => Readonly<Pick<R__10, Extract<keyof R__10, K>>>) & {
|
|
65
|
+
like: <R__10 extends R__9>(exemplar?: NoExtraKeys<R__10, K> | undefined) => (<const R__11 extends R__10>(r: R__11) => Readonly<Pick<R__11, Extract<keyof R__11, K>>>) & /*elided*/ any;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export {};
|
|
6
78
|
//# 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,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,IAAI,CAAC,GAC5C,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAErC,KAAK,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAC/B,KAAK,SAAS,GAAG,SAAS,OAAO,EAAE,CAAC;AACpC,KAAK,UAAU,CAAC,CAAC,SAAS,OAAO,IAAI,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AA+CxD,eAAO,MAAM,WAAW,yCAAoD,CAAC;AAE7E,eAAO,MAAM,YAAY,+CAQxB,CAAC;AAEF,eAAO,MAAM,KAAK,UAhCP,EAAE;WAEA,EAAE;;;;;;;;;;;;;;;;;;;;;CA8BmB,CAAC;AAEnC,eAAO,MAAM,MAAM,UAxBR,EAAE;WAKA,EAAE;;;;;;;;;;;;;;;;;;;;;CAmBqB,CAAC;AAErC,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,EAAE,aA1BzD,EAAE;WAKA,EAAE;;;;;;;;;;;;;;;;;;;;;CAsBK,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,21 +9,23 @@ function arrayEquivalent(a1, a2) {
|
|
|
9
9
|
}
|
|
10
10
|
return true;
|
|
11
11
|
}
|
|
12
|
-
function
|
|
12
|
+
function recordEquivalent(r1, r2) {
|
|
13
13
|
const r1Keys = Object.keys(r1);
|
|
14
14
|
const r2Keys = Object.keys(r2);
|
|
15
15
|
if (r2Keys.length !== r1Keys.length)
|
|
16
16
|
return false;
|
|
17
|
-
|
|
18
|
-
const k = r1Keys[i];
|
|
19
|
-
if (!(k in r2))
|
|
20
|
-
return false;
|
|
21
|
-
if (!Object.is(r2[k], r1[k]))
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
return true;
|
|
17
|
+
return r1Keys.every(k => Object.hasOwn(r2, k) && Object.is(r1[k], r2[k]));
|
|
25
18
|
}
|
|
19
|
+
const defineTuple = () => Object.assign((...t) => _tupleCache.get(t), {
|
|
20
|
+
like: (...exemplar) => defineTuple(),
|
|
21
|
+
});
|
|
22
|
+
const defineRecord = (keys) => Object.assign((r) => _recordCache.get(Object.fromEntries(keys ? keys.map((k) => [k, r[k]]) : Object.entries(r))), {
|
|
23
|
+
like: (exemplar) => defineRecord(keys),
|
|
24
|
+
});
|
|
26
25
|
export const _tupleCache = new InternCache(hash.valuesHash, arrayEquivalent);
|
|
27
|
-
export const
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
export const _recordCache = new InternCache((r) => hash.valuesHash(Object.keys(r)
|
|
27
|
+
.sort()
|
|
28
|
+
.flatMap((k) => [k, r[k]])), recordEquivalent);
|
|
29
|
+
export const tuple = defineTuple();
|
|
30
|
+
export const record = defineRecord();
|
|
31
|
+
export const struct = (...keys) => defineRecord(keys);
|
package/dist/intern-cache.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export default class<H, T extends object> {
|
|
2
|
-
private readonly map;
|
|
3
2
|
private readonly hashFn;
|
|
4
|
-
private readonly
|
|
3
|
+
private readonly matchFn;
|
|
4
|
+
private readonly map;
|
|
5
5
|
private cleanupRegistry;
|
|
6
|
-
constructor(hashFn: (
|
|
6
|
+
constructor(hashFn: (value: T) => H, matchFn: (a: T, b: T) => boolean);
|
|
7
7
|
private remove;
|
|
8
|
-
get<
|
|
8
|
+
get<T_ extends T>(obj: T_): Readonly<T_>;
|
|
9
9
|
get size(): number;
|
|
10
10
|
reset(): void;
|
|
11
11
|
}
|
|
@@ -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,SAAS,MAAM;IAOpC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAP1B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAsC;IAC1D,OAAO,CAAC,eAAe,CAErB;gBAGiB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EACvB,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO;IAGnD,OAAO,CAAC,MAAM;IAWd,GAAG,CAAC,EAAE,SAAS,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;IAsBxC,IAAI,IAAI,IAAI,MAAM,CAKjB;IAED,KAAK,IAAI,IAAI;CAId"}
|
package/dist/intern-cache.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export default class {
|
|
2
|
-
constructor(hashFn,
|
|
2
|
+
constructor(hashFn, matchFn) {
|
|
3
|
+
this.hashFn = hashFn;
|
|
4
|
+
this.matchFn = matchFn;
|
|
3
5
|
this.map = new Map();
|
|
4
6
|
this.cleanupRegistry = new FinalizationRegistry((x) => this.remove(x));
|
|
5
|
-
this.hashFn = hashFn;
|
|
6
|
-
this.identityFn = identityFn;
|
|
7
7
|
}
|
|
8
8
|
remove([hash, ref]) {
|
|
9
9
|
const hashBucket = this.map.get(hash);
|
|
@@ -21,7 +21,7 @@ export default class {
|
|
|
21
21
|
if (hashBucket !== undefined) {
|
|
22
22
|
for (const ref of hashBucket) {
|
|
23
23
|
const o = ref.deref();
|
|
24
|
-
if (o && this.
|
|
24
|
+
if (o && this.matchFn(obj, o))
|
|
25
25
|
return o;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -31,6 +31,7 @@ export default class {
|
|
|
31
31
|
this.map.set(hash, hashBucket);
|
|
32
32
|
}
|
|
33
33
|
// Object is not in the cache. Add it and return it.
|
|
34
|
+
Object.freeze(obj);
|
|
34
35
|
const ref = new WeakRef(obj);
|
|
35
36
|
hashBucket.add(ref);
|
|
36
37
|
this.cleanupRegistry.register(obj, [hash, ref]);
|