tuple-ts 0.5.0 → 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 +287 -38
- package/dist/index.d.ts +75 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -30
- package/dist/intern-cache.d.ts +2 -3
- package/dist/intern-cache.d.ts.map +1 -1
- package/dist/intern-cache.js +5 -6
- 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:
|
|
@@ -20,7 +20,7 @@ record({ x: 1 }) === record({ x: 1 }) // true
|
|
|
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,7 +180,20 @@ 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.
|
|
184
|
+
|
|
185
|
+
### Preserving literal types
|
|
186
|
+
|
|
187
|
+
To preserve literal types rather than widen them, use `as const`:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
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.
|
|
165
197
|
|
|
166
198
|
## Records
|
|
167
199
|
|
|
@@ -206,9 +238,27 @@ users.get(
|
|
|
206
238
|
); // "Alice"
|
|
207
239
|
```
|
|
208
240
|
|
|
241
|
+
### Record identity
|
|
242
|
+
|
|
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
|
+
|
|
209
259
|
### Typed records
|
|
210
260
|
|
|
211
|
-
|
|
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 = record<User
|
|
270
|
+
const User = record.like<User>();
|
|
221
271
|
|
|
222
272
|
const a = User({
|
|
223
273
|
id: "alice",
|
|
@@ -234,11 +284,36 @@ 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
|
+
|
|
237
312
|
## Structs
|
|
238
313
|
|
|
239
|
-
`record` interns whatever
|
|
314
|
+
`record` interns whatever properties you give it. If you want a fixed set of keys to define identity, use `struct`.
|
|
240
315
|
|
|
241
|
-
`struct` takes key names as arguments and returns a factory. The factory accepts any object
|
|
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.
|
|
242
317
|
|
|
243
318
|
```ts
|
|
244
319
|
import { struct } from "tuple-ts";
|
|
@@ -251,20 +326,109 @@ const b = Point({ x: 1, y: 2 });
|
|
|
251
326
|
a === b; // true
|
|
252
327
|
```
|
|
253
328
|
|
|
254
|
-
|
|
329
|
+
### Additional properties are discarded
|
|
330
|
+
|
|
331
|
+
Unlike `record`, a struct factory ignores properties that are not part of its declared structure:
|
|
255
332
|
|
|
256
333
|
```ts
|
|
334
|
+
const Point = struct("x", "y");
|
|
335
|
+
|
|
257
336
|
const a = Point({ x: 1, y: 2 });
|
|
258
|
-
const b = Point({ x: 1, y: 3 });
|
|
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"]
|
|
259
346
|
|
|
260
|
-
|
|
347
|
+
b.z; // TypeScript error
|
|
261
348
|
```
|
|
262
349
|
|
|
263
|
-
|
|
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.
|
|
264
428
|
|
|
265
429
|
## Equality semantics
|
|
266
430
|
|
|
267
|
-
|
|
431
|
+
Values are compared using [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is):
|
|
268
432
|
|
|
269
433
|
```ts
|
|
270
434
|
tuple(NaN) === tuple(NaN); // true
|
|
@@ -290,19 +454,54 @@ tuple({ x: 1 }) === tuple({ x: 1 }); // false
|
|
|
290
454
|
|
|
291
455
|
This is **shallow equality**. `tuple-ts` does not recursively compare arbitrary JavaScript objects.
|
|
292
456
|
|
|
293
|
-
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.
|
|
294
474
|
|
|
295
475
|
## Immutable by design
|
|
296
476
|
|
|
297
|
-
Tuples and structs returned by `tuple-ts` are frozen:
|
|
477
|
+
Tuples, records and structs returned by `tuple-ts` are frozen:
|
|
298
478
|
|
|
299
479
|
```ts
|
|
300
480
|
const point = tuple(10, 20);
|
|
481
|
+
const user = record({ id: 1 });
|
|
482
|
+
const position = struct("x", "y")({ x: 1, y: 2 });
|
|
301
483
|
|
|
302
|
-
Object.isFrozen(point);
|
|
484
|
+
Object.isFrozen(point); // true
|
|
485
|
+
Object.isFrozen(user); // true
|
|
486
|
+
Object.isFrozen(position); // true
|
|
303
487
|
```
|
|
304
488
|
|
|
305
|
-
This is important to the identity semantics: once a value has been interned, its
|
|
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 });
|
|
498
|
+
|
|
499
|
+
details.name = "Bob"; // Allowed
|
|
500
|
+
```
|
|
501
|
+
|
|
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.
|
|
306
505
|
|
|
307
506
|
## Garbage-collection friendly
|
|
308
507
|
|
|
@@ -310,40 +509,86 @@ Interning values creates a natural question: *doesn't the cache grow forever?*
|
|
|
310
509
|
|
|
311
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.
|
|
312
511
|
|
|
313
|
-
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.
|
|
314
513
|
|
|
315
514
|
## TypeScript first
|
|
316
515
|
|
|
317
|
-
`tuple`, `record
|
|
516
|
+
`tuple`, `record` and `struct` are designed to preserve TypeScript's inference.
|
|
318
517
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
42,
|
|
323
|
-
true,
|
|
324
|
-
);
|
|
518
|
+
### Literal inference
|
|
519
|
+
|
|
520
|
+
By default, constructors preserve literal types:
|
|
325
521
|
|
|
522
|
+
```ts
|
|
523
|
+
const t = tuple("hello", 42, true);
|
|
326
524
|
// readonly ["hello", 42, true]
|
|
525
|
+
|
|
526
|
+
const r = record({ x: 1, y: 2 });
|
|
527
|
+
// readonly properties x: 1, y: 2
|
|
327
528
|
```
|
|
328
529
|
|
|
329
|
-
|
|
530
|
+
### Reusable typed factories
|
|
531
|
+
|
|
532
|
+
Use `.like()` to define reusable constructors:
|
|
330
533
|
|
|
331
534
|
```ts
|
|
332
|
-
|
|
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
|
+
});
|
|
333
546
|
```
|
|
334
547
|
|
|
548
|
+
Or specify types explicitly:
|
|
549
|
+
|
|
550
|
+
```ts
|
|
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
|
+
}>();
|
|
562
|
+
```
|
|
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
|
+
|
|
335
572
|
## When should I use it?
|
|
336
573
|
|
|
337
574
|
`tuple-ts` is particularly useful when you want **small immutable values with stable identity**:
|
|
338
575
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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 |
|
|
347
592
|
|
|
348
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**.
|
|
349
594
|
|
|
@@ -351,10 +596,14 @@ If you just need an ordinary mutable array or object, use one. `tuple-ts` is for
|
|
|
351
596
|
|
|
352
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.
|
|
353
598
|
|
|
599
|
+
Property order is ignored for records and structs, but element order matters for tuples.
|
|
600
|
+
|
|
354
601
|
The intern cache uses weak references, so canonical values do not need to be kept alive solely because they have previously been created.
|
|
355
602
|
|
|
603
|
+
Constructed records and structs are copied before interning, so callers cannot alter an interned value by modifying the source object.
|
|
604
|
+
|
|
356
605
|
There are no runtime dependencies.
|
|
357
606
|
|
|
358
607
|
## License
|
|
359
608
|
|
|
360
|
-
ISC
|
|
609
|
+
ISC
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +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 _recordCache: InternCache<number, Record<
|
|
4
|
-
export declare const tuple: <const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 {};
|
|
11
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
|
@@ -14,36 +14,18 @@ function recordEquivalent(r1, r2) {
|
|
|
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
|
}
|
|
26
|
-
|
|
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
|
+
});
|
|
25
|
+
export const _tupleCache = new InternCache(hash.valuesHash, arrayEquivalent);
|
|
27
26
|
export const _recordCache = new InternCache((r) => hash.valuesHash(Object.keys(r)
|
|
28
27
|
.sort()
|
|
29
|
-
.flatMap((k) => [k, r[k]])), recordEquivalent
|
|
30
|
-
export const tuple = (
|
|
31
|
-
export const record = (
|
|
32
|
-
export
|
|
33
|
-
constructor() {
|
|
34
|
-
super("All keys must be distinct.");
|
|
35
|
-
this.name = "DuplicateKeyError";
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
export const struct = (...keys) => {
|
|
39
|
-
keys.forEach((k) => {
|
|
40
|
-
if (typeof k !== "string") {
|
|
41
|
-
throw new TypeError("All keys must be strings.");
|
|
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]])));
|
|
49
|
-
};
|
|
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,12 +1,11 @@
|
|
|
1
1
|
export default class<H, T extends object> {
|
|
2
2
|
private readonly hashFn;
|
|
3
3
|
private readonly matchFn;
|
|
4
|
-
private readonly injestFn;
|
|
5
4
|
private readonly map;
|
|
6
5
|
private cleanupRegistry;
|
|
7
|
-
constructor(hashFn: (value: T) => H, matchFn: (
|
|
6
|
+
constructor(hashFn: (value: T) => H, matchFn: (a: T, b: T) => boolean);
|
|
8
7
|
private remove;
|
|
9
|
-
get<T_ extends T>(
|
|
8
|
+
get<T_ extends T>(obj: T_): Readonly<T_>;
|
|
10
9
|
get size(): number;
|
|
11
10
|
reset(): void;
|
|
12
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;IAOpC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;
|
|
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,8 +1,7 @@
|
|
|
1
1
|
export default class {
|
|
2
|
-
constructor(hashFn, matchFn
|
|
2
|
+
constructor(hashFn, matchFn) {
|
|
3
3
|
this.hashFn = hashFn;
|
|
4
4
|
this.matchFn = matchFn;
|
|
5
|
-
this.injestFn = injestFn;
|
|
6
5
|
this.map = new Map();
|
|
7
6
|
this.cleanupRegistry = new FinalizationRegistry((x) => this.remove(x));
|
|
8
7
|
}
|
|
@@ -16,13 +15,13 @@ export default class {
|
|
|
16
15
|
this.map.delete(hash);
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
|
-
get(
|
|
20
|
-
const hash = this.hashFn(
|
|
18
|
+
get(obj) {
|
|
19
|
+
const hash = this.hashFn(obj);
|
|
21
20
|
let hashBucket = this.map.get(hash);
|
|
22
21
|
if (hashBucket !== undefined) {
|
|
23
22
|
for (const ref of hashBucket) {
|
|
24
23
|
const o = ref.deref();
|
|
25
|
-
if (o && this.matchFn(
|
|
24
|
+
if (o && this.matchFn(obj, o))
|
|
26
25
|
return o;
|
|
27
26
|
}
|
|
28
27
|
}
|
|
@@ -32,7 +31,7 @@ export default class {
|
|
|
32
31
|
this.map.set(hash, hashBucket);
|
|
33
32
|
}
|
|
34
33
|
// Object is not in the cache. Add it and return it.
|
|
35
|
-
|
|
34
|
+
Object.freeze(obj);
|
|
36
35
|
const ref = new WeakRef(obj);
|
|
37
36
|
hashBucket.add(ref);
|
|
38
37
|
this.cleanupRegistry.register(obj, [hash, ref]);
|