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