@rhythmjs/rhythm 0.0.2 → 0.0.3

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,4 @@
1
+ import { Middleware, NextFn } from "./types.js";
2
+ //#region src/compose.d.ts
3
+ export declare function compose<TContext extends object>(middleware: Middleware<TContext>[]): (context: TContext, next?: NextFn<TContext>) => Promise<TContext>;
4
+ //#endregion
@@ -0,0 +1,24 @@
1
+ //#region src/compose.ts
2
+ function compose(middleware) {
3
+ if (!Array.isArray(middleware)) throw new TypeError("Middleware stack must be an array!");
4
+ for (const fn of middleware) if (typeof fn !== "function") throw new TypeError("Middleware must be composed of functions!");
5
+ return function(context, next) {
6
+ let index = -1;
7
+ return dispatch(0);
8
+ function dispatch(i) {
9
+ if (i <= index) return Promise.reject(/* @__PURE__ */ new Error("next() called multiple times"));
10
+ index = i;
11
+ const fn = i === middleware.length ? next : middleware[i];
12
+ if (!fn) return Promise.resolve(context);
13
+ const dispatchNext = () => dispatch(i + 1);
14
+ try {
15
+ const call = fn;
16
+ return Promise.resolve(call(context, dispatchNext)).then(() => context);
17
+ } catch (err) {
18
+ return Promise.reject(err);
19
+ }
20
+ }
21
+ };
22
+ }
23
+ //#endregion
24
+ export { compose };
package/dist/rhythm.d.ts CHANGED
@@ -1,26 +1,18 @@
1
+ import { DeriveMiddleware, Middleware, OmitHashKeys } from "./types.js";
1
2
  //#region src/rhythm.d.ts
2
- export declare const RhythmMutable: unique symbol;
3
- export type DeepReadonly<T> = T extends ((...args: any[]) => any) ? T : T extends ReadonlyArray<infer U> ? readonly DeepReadonly<U>[] : T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> : T extends Set<infer U> ? ReadonlySet<DeepReadonly<U>> : T extends {
4
- readonly [RhythmMutable]: true;
5
- } ? T : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]>; } : T;
6
- export type OmitHashKeys<T> = { [K in keyof T as K extends `#${string}` ? never : K]: T[K]; };
7
- export type NextFn<TContext extends object> = {
8
- (): Promise<DeepReadonly<TContext>>;
9
- <TExtra extends object>(extra: TExtra): Promise<DeepReadonly<TContext & TExtra>>;
10
- };
11
- export type Middleware<TContext extends object> = (ctx: DeepReadonly<TContext>, next: NextFn<TContext>) => Promise<void> | void;
12
- export declare function compose<TContext extends object>(middleware: Middleware<TContext>[]): (context: TContext, next?: NextFn<TContext>) => Promise<TContext>;
13
3
  export interface RhythmOptions {
14
4
  name?: string;
15
5
  type?: string;
16
6
  [key: string]: unknown;
17
7
  }
8
+ export declare function derive<TContext extends object, TExtra extends object>(fn: (ctx: TContext) => TExtra | Promise<TExtra>): DeriveMiddleware<TContext, OmitHashKeys<TExtra>>;
18
9
  export declare class Rhythm<TInput extends object = {}, TContext extends object = TInput, TProviders extends object = {}> {
19
10
  #private;
20
11
  constructor(options?: RhythmOptions);
21
- use<TExtra extends object = {}>(fn: Middleware<TContext>): Rhythm<TInput, TContext & TExtra, TProviders>;
22
- provide<TValue extends object>(factory: (deps: DeepReadonly<TProviders>) => TValue | Promise<TValue>, dispose?: (value: TValue) => void | Promise<void>): Rhythm<TInput, TContext & OmitHashKeys<TValue>, TProviders & OmitHashKeys<TValue>>;
23
- register<TRegInput extends object, TRegContext extends object, TExported extends object = {}>(other: Rhythm<TRegInput, TRegContext, any> & (TContext extends TRegInput ? unknown : never), exportValue?: (result: DeepReadonly<TRegContext>) => TExported): Rhythm<TInput, TContext & TExported, TProviders>;
12
+ use<TExtra extends object>(fn: DeriveMiddleware<TContext, TExtra>): Rhythm<TInput, TContext & TExtra, TProviders>;
13
+ use(fn: Middleware<TContext>): this;
14
+ provide<TValue extends object>(factory: (deps: TProviders) => TValue | Promise<TValue>, dispose?: (value: TValue) => void | Promise<void>): Rhythm<TInput, TContext & OmitHashKeys<TValue>, TProviders & OmitHashKeys<TValue>>;
15
+ register<TRegInput extends object, TRegContext extends object, TExported extends object = {}>(other: Rhythm<TRegInput, TRegContext, any> & (TContext extends TRegInput ? unknown : never), exportValue?: (result: TRegContext) => TExported): Rhythm<TInput, TContext & TExported, TProviders>;
24
16
  setup(): Promise<void>;
25
17
  teardown(): Promise<void>;
26
18
  callback(): (input: TInput) => Promise<TContext>;
package/dist/rhythm.js CHANGED
@@ -1,43 +1,30 @@
1
+ import { compose } from "./compose.js";
1
2
  //#region src/rhythm.ts
2
- const RhythmMutable = Symbol("RhythmMutable");
3
- function compose(middleware) {
4
- if (!Array.isArray(middleware)) throw new TypeError("Middleware stack must be an array!");
5
- for (const fn of middleware) if (typeof fn !== "function") throw new TypeError("Middleware must be composed of functions!");
6
- return function(context, next) {
7
- let index = -1;
8
- return dispatch(0);
9
- function dispatch(i) {
10
- if (i <= index) return Promise.reject(/* @__PURE__ */ new Error("next() called multiple times"));
11
- index = i;
12
- const fn = i === middleware.length ? next : middleware[i];
13
- if (!fn) return Promise.resolve(context);
14
- const dispatchNext = ((extra) => {
15
- if (extra) Object.assign(context, extra);
16
- return dispatch(i + 1);
17
- });
18
- try {
19
- const call = fn;
20
- return Promise.resolve(call(context, dispatchNext)).then(() => context);
21
- } catch (err) {
22
- return Promise.reject(err);
23
- }
24
- }
3
+ function publicEntries(value) {
4
+ const exported = {};
5
+ for (const [key, val] of Object.entries(value)) if (!key.startsWith("#")) exported[key] = val;
6
+ return exported;
7
+ }
8
+ function derive(fn) {
9
+ if (typeof fn !== "function") throw new TypeError("derive factory must be a function!");
10
+ const middleware = async (ctx, next) => {
11
+ const value = await fn(ctx);
12
+ Object.assign(ctx, publicEntries(value));
13
+ await next();
25
14
  };
15
+ return middleware;
26
16
  }
27
17
  var Rhythm = class {
28
18
  #middleware = [];
29
19
  #options;
30
20
  #providers = [];
31
21
  #setupPromise = null;
32
- #composed = null;
33
- #callbackFn = null;
34
22
  constructor(options = {}) {
35
23
  this.#options = options;
36
24
  }
37
25
  use(fn) {
38
26
  if (typeof fn !== "function") throw new TypeError("middleware must be a function!");
39
27
  this.#middleware.push(fn);
40
- this.#invalidateCallback();
41
28
  return this;
42
29
  }
43
30
  provide(factory, dispose) {
@@ -47,11 +34,9 @@ var Rhythm = class {
47
34
  };
48
35
  this.#providers.push(entry);
49
36
  this.#middleware.push(async (ctx, next) => {
50
- const exported = {};
51
- for (const [key, value] of Object.entries(entry.resolved)) if (!key.startsWith("#")) exported[key] = value;
52
- await next(exported);
37
+ Object.assign(ctx, publicEntries(entry.resolved));
38
+ await next();
53
39
  });
54
- this.#invalidateCallback();
55
40
  return this;
56
41
  }
57
42
  register(other, exportValue) {
@@ -71,15 +56,11 @@ var Rhythm = class {
71
56
  const { type = "module", name = "anonymous" } = module.#options;
72
57
  throw new Error(`registered ${type} "${name}" failed`, { cause });
73
58
  }
74
- await next(exportValue ? exportValue(result) : {});
59
+ if (exportValue) Object.assign(ctx, exportValue(result));
60
+ await next();
75
61
  });
76
- this.#invalidateCallback();
77
62
  return this;
78
63
  }
79
- #invalidateCallback() {
80
- this.#composed = null;
81
- this.#callbackFn = null;
82
- }
83
64
  setup() {
84
65
  if (!this.#setupPromise) this.#setupPromise = this.#resolveProviders().catch((err) => {
85
66
  this.#setupPromise = null;
@@ -91,33 +72,34 @@ var Rhythm = class {
91
72
  const resolved = {};
92
73
  for (const entry of this.#providers) {
93
74
  entry.resolved = await entry.factory(resolved);
94
- for (const [key, value] of Object.entries(entry.resolved)) if (!key.startsWith("#")) resolved[key] = value;
75
+ Object.assign(resolved, publicEntries(entry.resolved));
95
76
  }
96
77
  }
97
78
  async teardown() {
98
- for (const entry of [...this.#providers].reverse()) await entry.dispose?.(entry.resolved);
79
+ for (const entry of [...this.#providers].reverse()) {
80
+ if (entry.resolved === void 0) continue;
81
+ await entry.dispose?.(entry.resolved);
82
+ entry.resolved = void 0;
83
+ }
84
+ this.#setupPromise = null;
99
85
  }
100
86
  callback() {
101
- if (!this.#callbackFn) {
102
- if (!this.#composed) this.#composed = compose(this.#middleware);
103
- const fn = this.#composed;
104
- this.#callbackFn = async (input) => {
105
- await this.setup();
106
- return fn({ ...input });
107
- };
108
- }
109
- return this.#callbackFn;
87
+ const fn = compose([...this.#middleware]);
88
+ return async (input) => {
89
+ await this.setup();
90
+ return fn({ ...input });
91
+ };
110
92
  }
111
93
  run(input) {
112
94
  return this.callback()(input);
113
95
  }
114
96
  middleware() {
97
+ const fn = compose([...this.#middleware]);
115
98
  return async (ctx, next) => {
116
99
  await this.setup();
117
- if (!this.#composed) this.#composed = compose(this.#middleware);
118
- await this.#composed(ctx, next);
100
+ await fn(ctx, next);
119
101
  };
120
102
  }
121
103
  };
122
104
  //#endregion
123
- export { Rhythm, RhythmMutable, compose };
105
+ export { Rhythm, derive };
@@ -0,0 +1,8 @@
1
+ //#region src/types.d.ts
2
+ export type OmitHashKeys<T> = { [K in keyof T as K extends `#${string}` ? never : K]: T[K]; };
3
+ export type NextFn<TContext extends object> = () => Promise<TContext>;
4
+ export type Middleware<TContext extends object> = (ctx: TContext, next: NextFn<TContext>) => Promise<void> | void;
5
+ export type DeriveMiddleware<TContext extends object, TExtra extends object> = Middleware<TContext> & {
6
+ readonly "~derive": TExtra;
7
+ };
8
+ //#endregion
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhythmjs/rhythm",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "A minimal, type-safe onion-middleware and provider composition kernel.",
5
5
  "homepage": "https://rhythm.js.org/rhythm",
6
6
  "license": "ISC",
@@ -19,6 +19,14 @@
19
19
  "types": "./dist/rhythm.d.ts",
20
20
  "default": "./dist/rhythm.js"
21
21
  },
22
+ "./compose": {
23
+ "types": "./dist/compose.d.ts",
24
+ "default": "./dist/compose.js"
25
+ },
26
+ "./types": {
27
+ "types": "./dist/types.d.ts",
28
+ "default": "./dist/types.js"
29
+ },
22
30
  "./package.json": "./package.json"
23
31
  },
24
32
  "publishConfig": {