@mark1russell7/collections 0.3.0 → 0.4.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.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * This module holds the one fold of a stack of layers. A layer wraps the next thing, so a stack
3
+ * of layers wraps a base. The next thing can be a collection, a function or any other value.
4
+ *
5
+ * This file has no imports. A package that needs only the fold loads this one file.
6
+ *
7
+ * @example
8
+ * const parse = compose(
9
+ * (next: (text: string) => number) => (text: string) => next(text.trim()),
10
+ * (next: (text: string) => number) => (text: string) => next(text) * 2
11
+ * )(Number);
12
+ * parse(" 21 "); // 42
13
+ */
14
+ /**
15
+ * A layer takes the next thing and gives a wrapped thing.
16
+ *
17
+ * @template Inner The type that the layer wraps
18
+ * @template Outer The type that the layer gives
19
+ */
20
+ type Layer<Inner = any, Outer = Inner> = (next: Inner) => Outer;
21
+ /** This type gives the type that a layer wraps. */
22
+ type InnerOf<L> = L extends ((next: infer I) => any) ? I : never;
23
+ /** This type gives the type that a layer gives. */
24
+ type OuterOf<L> = L extends ((next: any) => infer O) ? O : never;
25
+ /**
26
+ * This type checks each neighbor pair of a list of layers. The result of the inner neighbor must
27
+ * fit the input of the outer neighbor. At the first bad pair, the compiler marks that argument.
28
+ *
29
+ * The type is tail recursive, so the compiler accepts a chain of 100 layers.
30
+ */
31
+ type Chain<L extends readonly unknown[], Done extends readonly unknown[] = readonly []> = L extends readonly [infer A, infer B, ...infer R] ? [OuterOf<B>] extends [InnerOf<A>] ? Chain<readonly [B, ...R], readonly [...Done, A]> : readonly [...Done, A, Layer<any, InnerOf<A>>, ...R] : readonly [...Done, ...L];
32
+ /**
33
+ * This type gives the function that {@link compose} returns. Its input is the input of the last
34
+ * layer, and its result is the result of the first layer. No layers give the identity function.
35
+ * A list that the compiler cannot see as a tuple gives the types of one element.
36
+ */
37
+ type Composed<L extends readonly unknown[]> = L extends readonly [] ? <B>(base: B) => B : L extends readonly [infer First, ...unknown[]] ? L extends readonly [...unknown[], infer Last] ? (base: InnerOf<Last>) => OuterOf<First> : never : L extends readonly (infer E)[] ? (base: InnerOf<E>) => OuterOf<E> : never;
38
+ /** This type gives the intersection of the results of a list of layers. */
39
+ type Outers<L extends readonly unknown[], Sum = unknown> = L extends readonly [infer First, ...infer Rest] ? Outers<Rest, Sum & OuterOf<First>> : Sum;
40
+ /**
41
+ * This type gives the function for layers that keep each capability of the layers under them.
42
+ * Its result has the result of each layer and the base. Use it only when that law holds.
43
+ */
44
+ type Stacked<L extends readonly unknown[]> = L extends readonly [...unknown[], infer Last] ? (base: InnerOf<Last>) => Outers<L> & InnerOf<Last> : Composed<L>;
45
+ /**
46
+ * This function folds layers into one function that wraps a base. The first layer is the outermost.
47
+ *
48
+ * The fold holds four laws:
49
+ * - Identity: `compose()(b)` is `b`.
50
+ * - One layer: `compose(f)(b)` is `f(b)`.
51
+ * - Associativity: `compose(f, compose(g, h))` equals `compose(compose(f, g), h)`.
52
+ * - Order: `compose(f, g)(b)` is `f(g(b))`.
53
+ *
54
+ * @param layers The layers, from the outermost to the innermost
55
+ * @returns A function that wraps a base in the layers
56
+ */
57
+ declare function compose<const L extends readonly Layer[]>(...layers: L & Chain<L>): Composed<L>;
58
+ export { OuterOf as a, compose as c, Layer as i, Composed as n, Outers as o, InnerOf as r, Stacked as s, Chain as t };
59
+ //# sourceMappingURL=layers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.ts","names":[],"sources":["../src/core/layers.ts"],"mappings":";;;;;;;;;;;;;;;;;;;KAoBY,MAAM,aAAa,QAAQ,UAAU,MAAM,UAAU;;KAGrD,QAAQ,KAAK,YAAW,YAAY,aAAY;;KAGhD,QAAQ,KAAK,YAAW,oBAAoB,KAAI;;;;;;;KAQhD,MAAM,8BAA8B,iDAC9C,0BAA0B,SAAS,YAAY,MAC1C,QAAQ,aAAa,QAAQ,MAC5B,gBAAgB,MAAM,iBAAiB,MAAM,mBAChC,MAAM,GAAG,WAAW,QAAQ,QAAQ,kBACtC,SAAS;;;;;;KAOhB,SAAS,gCACnB,yBACK,GAAG,MAAM,MAAM,IAChB,0BAA0B,uBACxB,wCAAwC,SACrC,MAAM,QAAQ,UAAU,QAAQ,iBAEnC,0BAA0B,QACvB,MAAM,QAAQ,OAAO,QAAQ;;KAI5B,OAAO,8BAA8B,iBAC/C,0BAA0B,gBAAgB,QAAQ,OAAO,MAAM,MAAM,QAAQ,UAAU;;;;;KAM7E,QAAQ,gCAClB,wCAAwC,SACnC,MAAM,QAAQ,UAAU,OAAO,KAAK,QAAQ,QAC7C,SAAS;;;;;;;;;;;;;iBAcC,cAAc,mBAAmB,YAAY,QAAQ,IAAI,MAAM,KAAK,SAAS"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/collections",
4
- "version": "0.3.0",
4
+ "version": "0.4.0",
5
5
  "description": "Collections with no dependencies: lists, maps, queues, a summary tree, channels, lazy operations and dependency graphs.",
6
6
  "keywords": [
7
7
  "collections",
@@ -43,6 +43,10 @@
43
43
  "types": "./build/graph/index.d.ts",
44
44
  "default": "./build/graph/index.js"
45
45
  },
46
+ "./layers": {
47
+ "types": "./build/core/layers.d.ts",
48
+ "default": "./build/core/layers.js"
49
+ },
46
50
  "./testing": {
47
51
  "types": "./build/testing/index.d.ts",
48
52
  "default": "./build/testing/index.js"
@@ -64,6 +68,9 @@
64
68
  },
65
69
  "devDependencies": {
66
70
  "@mark1russell7/cue": "^2.0.0",
71
+ "@mark1russell7/rigor-bench": "^0.2.5",
72
+ "@mark1russell7/rigor-generators": "^0.1.5",
73
+ "@mark1russell7/rigor-statistics": "^0.1.8",
67
74
  "@types/node": "^26.6.5",
68
75
  "esbuild": "^0.28.2",
69
76
  "fast-check": "^4.10.2",
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @example
8
8
  * const list = compose(
9
- * boundedList({ capacity: 100, policy: 'drop-oldest' })
9
+ * boundedList<number>({ capacity: 100, policy: 'drop-oldest' })
10
10
  * )(arrayList<number>())
11
11
  *
12
12
  * // Add 101 elements - first element will be dropped
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @example
8
8
  * const list = compose(
9
- * eventedList()
9
+ * eventedList<number>()
10
10
  * )(arrayList<number>())
11
11
  *
12
12
  * list.on('add', ({ value, index }) => {
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @example
8
8
  * const cache = compose(
9
- * lruMap({ capacity: 100 })
9
+ * lruMap<string, Data>({ capacity: 100 })
10
10
  * )(hashMap<string, Data>())
11
11
  *
12
12
  * cache.set('key1', data1) // Accessed
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @example
8
8
  * const list = compose(
9
- * safeList()
9
+ * safeList<number>()
10
10
  * )(arrayList<number>())
11
11
  *
12
12
  * const value = list.safe.get(10) // Option<number> instead of throwing
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @example
8
8
  * const cache = compose(
9
- * ttlMap({ ttl: 60000 }) // 60 seconds
9
+ * ttlMap<string, Data>({ ttl: 60000 }) // 60 seconds
10
10
  * )(hashMap<string, Data>())
11
11
  *
12
12
  * cache.set('key', data)
@@ -0,0 +1,86 @@
1
+ /**
2
+ * This module holds the one fold of a stack of layers. A layer wraps the next thing, so a stack
3
+ * of layers wraps a base. The next thing can be a collection, a function or any other value.
4
+ *
5
+ * This file has no imports. A package that needs only the fold loads this one file.
6
+ *
7
+ * @example
8
+ * const parse = compose(
9
+ * (next: (text: string) => number) => (text: string) => next(text.trim()),
10
+ * (next: (text: string) => number) => (text: string) => next(text) * 2
11
+ * )(Number);
12
+ * parse(" 21 "); // 42
13
+ */
14
+
15
+ /**
16
+ * A layer takes the next thing and gives a wrapped thing.
17
+ *
18
+ * @template Inner The type that the layer wraps
19
+ * @template Outer The type that the layer gives
20
+ */
21
+ export type Layer<Inner = any, Outer = Inner> = (next: Inner) => Outer;
22
+
23
+ /** This type gives the type that a layer wraps. */
24
+ export type InnerOf<L> = L extends (next: infer I) => any ? I : never;
25
+
26
+ /** This type gives the type that a layer gives. */
27
+ export type OuterOf<L> = L extends (next: any) => infer O ? O : never;
28
+
29
+ /**
30
+ * This type checks each neighbor pair of a list of layers. The result of the inner neighbor must
31
+ * fit the input of the outer neighbor. At the first bad pair, the compiler marks that argument.
32
+ *
33
+ * The type is tail recursive, so the compiler accepts a chain of 100 layers.
34
+ */
35
+ export type Chain<L extends readonly unknown[], Done extends readonly unknown[] = readonly []> =
36
+ L extends readonly [infer A, infer B, ...infer R]
37
+ ? [OuterOf<B>] extends [InnerOf<A>]
38
+ ? Chain<readonly [B, ...R], readonly [...Done, A]>
39
+ : readonly [...Done, A, Layer<any, InnerOf<A>>, ...R]
40
+ : readonly [...Done, ...L];
41
+
42
+ /**
43
+ * This type gives the function that {@link compose} returns. Its input is the input of the last
44
+ * layer, and its result is the result of the first layer. No layers give the identity function.
45
+ * A list that the compiler cannot see as a tuple gives the types of one element.
46
+ */
47
+ export type Composed<L extends readonly unknown[]> =
48
+ L extends readonly []
49
+ ? <B>(base: B) => B
50
+ : L extends readonly [infer First, ...unknown[]]
51
+ ? L extends readonly [...unknown[], infer Last]
52
+ ? (base: InnerOf<Last>) => OuterOf<First>
53
+ : never
54
+ : L extends readonly (infer E)[]
55
+ ? (base: InnerOf<E>) => OuterOf<E>
56
+ : never;
57
+
58
+ /** This type gives the intersection of the results of a list of layers. */
59
+ export type Outers<L extends readonly unknown[], Sum = unknown> =
60
+ L extends readonly [infer First, ...infer Rest] ? Outers<Rest, Sum & OuterOf<First>> : Sum;
61
+
62
+ /**
63
+ * This type gives the function for layers that keep each capability of the layers under them.
64
+ * Its result has the result of each layer and the base. Use it only when that law holds.
65
+ */
66
+ export type Stacked<L extends readonly unknown[]> =
67
+ L extends readonly [...unknown[], infer Last]
68
+ ? (base: InnerOf<Last>) => Outers<L> & InnerOf<Last>
69
+ : Composed<L>;
70
+
71
+ /**
72
+ * This function folds layers into one function that wraps a base. The first layer is the outermost.
73
+ *
74
+ * The fold holds four laws:
75
+ * - Identity: `compose()(b)` is `b`.
76
+ * - One layer: `compose(f)(b)` is `f(b)`.
77
+ * - Associativity: `compose(f, compose(g, h))` equals `compose(compose(f, g), h)`.
78
+ * - Order: `compose(f, g)(b)` is `f(g(b))`.
79
+ *
80
+ * @param layers The layers, from the outermost to the innermost
81
+ * @returns A function that wraps a base in the layers
82
+ */
83
+ export function compose<const L extends readonly Layer[]>(...layers: L & Chain<L>): Composed<L> {
84
+ const list: readonly Layer[] = layers;
85
+ return ((base: unknown) => list.reduceRight((acc, layer) => layer(acc), base)) as Composed<L>;
86
+ }
@@ -9,13 +9,14 @@
9
9
  *
10
10
  * @example
11
11
  * const list = compose(
12
- * safeList(),
13
- * eventedList(),
14
- * boundedList({ capacity: 100 })
12
+ * safeList<number>(),
13
+ * eventedList<number>(),
14
+ * boundedList<number>({ capacity: 100 })
15
15
  * )(arrayList<number>())
16
16
  */
17
17
 
18
18
  import type { Collection } from "../interfaces/collection.js";
19
+ import { compose as fold, type Chain, type Layer, type Stacked } from "./layers.js";
19
20
 
20
21
  /**
21
22
  * A middleware is a function that takes a collection and gives
@@ -52,9 +53,9 @@ export type Middleware<In = any, Out extends In = In> = (next: In) => Out;
52
53
  * // 3. bounded - enforces capacity limits
53
54
  * // 4. base - the underlying ArrayList
54
55
  * const enhancer = compose(
55
- * safeList(),
56
- * eventedList(),
57
- * boundedList({ capacity: 100 })
56
+ * safeList<number>(),
57
+ * eventedList<number>(),
58
+ * boundedList<number>({ capacity: 100 })
58
59
  * )
59
60
  * const list = enhancer(arrayList<number>())
60
61
  * // Type: List<number> & SafeCollection & EventedCollection & BoundedCollection
@@ -62,49 +63,8 @@ export type Middleware<In = any, Out extends In = In> = (next: In) => Out;
62
63
  * @param layers The middleware layers to compose
63
64
  * @returns A function that applies all layers to a base collection
64
65
  */
65
-
66
- // Overload for 1 middleware
67
- export function compose<C, R1 extends C>(
68
- m1: Middleware<C, R1>
69
- ): (base: C) => R1;
70
-
71
- // Overload for 2 middleware
72
- export function compose<C, R1 extends C, R2 extends R1>(
73
- m1: Middleware<R1, R2>,
74
- m2: Middleware<C, R1>
75
- ): (base: C) => R2;
76
-
77
- // Overload for 3 middleware
78
- export function compose<C, R1 extends C, R2 extends R1, R3 extends R2>(
79
- m1: Middleware<R2, R3>,
80
- m2: Middleware<R1, R2>,
81
- m3: Middleware<C, R1>
82
- ): (base: C) => R3;
83
-
84
- // Overload for 4 middleware
85
- export function compose<C, R1 extends C, R2 extends R1, R3 extends R2, R4 extends R3>(
86
- m1: Middleware<R3, R4>,
87
- m2: Middleware<R2, R3>,
88
- m3: Middleware<R1, R2>,
89
- m4: Middleware<C, R1>
90
- ): (base: C) => R4;
91
-
92
- // Overload for 5 middleware
93
- export function compose<C, R1 extends C, R2 extends R1, R3 extends R2, R4 extends R3, R5 extends R4>(
94
- m1: Middleware<R4, R5>,
95
- m2: Middleware<R3, R4>,
96
- m3: Middleware<R2, R3>,
97
- m4: Middleware<R1, R2>,
98
- m5: Middleware<C, R1>
99
- ): (base: C) => R5;
100
-
101
- // General fallback for any number of middleware
102
- export function compose<C>(...layers: Middleware<any, any>[]): (base: C) => any;
103
-
104
- // Implementation
105
- export function compose(...layers: Middleware<any, any>[]) {
106
- return (base: any): any =>
107
- layers.reduceRight((acc, layer) => layer(acc), base);
66
+ export function compose<const L extends readonly Layer[]>(...layers: L & Chain<L>): Stacked<L> {
67
+ return fold(...(layers as readonly Layer[])) as unknown as Stacked<L>;
108
68
  }
109
69
 
110
70
  /**
@@ -361,53 +321,11 @@ export const enhanceMethod = <C extends object, K extends keyof C & string>(
361
321
  *
362
322
  * @example
363
323
  * const standardBehaviors = bundle(
364
- * boundedList({ capacity: 1000 }),
365
- * eventedList(),
366
- * safeList()
324
+ * boundedList<number>({ capacity: 1000 }),
325
+ * eventedList<number>(),
326
+ * safeList<number>()
367
327
  * )
368
328
  *
369
329
  * const list = compose(standardBehaviors)(arrayList<number>())
370
330
  */
371
-
372
- // Overload for 1 middleware
373
- export function bundle<C, R1 extends C>(
374
- m1: Middleware<C, R1>
375
- ): Middleware<C, R1>;
376
-
377
- // Overload for 2 middleware
378
- export function bundle<C, R1 extends C, R2 extends R1>(
379
- m1: Middleware<R1, R2>,
380
- m2: Middleware<C, R1>
381
- ): Middleware<C, R2>;
382
-
383
- // Overload for 3 middleware
384
- export function bundle<C, R1 extends C, R2 extends R1, R3 extends R2>(
385
- m1: Middleware<R2, R3>,
386
- m2: Middleware<R1, R2>,
387
- m3: Middleware<C, R1>
388
- ): Middleware<C, R3>;
389
-
390
- // Overload for 4 middleware
391
- export function bundle<C, R1 extends C, R2 extends R1, R3 extends R2, R4 extends R3>(
392
- m1: Middleware<R3, R4>,
393
- m2: Middleware<R2, R3>,
394
- m3: Middleware<R1, R2>,
395
- m4: Middleware<C, R1>
396
- ): Middleware<C, R4>;
397
-
398
- // Overload for 5 middleware
399
- export function bundle<C, R1 extends C, R2 extends R1, R3 extends R2, R4 extends R3, R5 extends R4>(
400
- m1: Middleware<R4, R5>,
401
- m2: Middleware<R3, R4>,
402
- m3: Middleware<R2, R3>,
403
- m4: Middleware<R1, R2>,
404
- m5: Middleware<C, R1>
405
- ): Middleware<C, R5>;
406
-
407
- // General fallback for any number of middleware
408
- export function bundle<C>(...middleware: Middleware<any, any>[]): Middleware<C, any>;
409
-
410
- // Implementation
411
- export function bundle(...middleware: Middleware<any, any>[]): Middleware<any, any> {
412
- return (next: any): any => compose(...middleware)(next);
413
- }
331
+ export const bundle: typeof compose = compose;