@rhythmjs/rhythm 0.0.15 → 0.0.16

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 CHANGED
@@ -66,8 +66,9 @@ Keys prefixed with `#` are dropped from the context. For long-lived resources, c
66
66
  ```ts
67
67
  import { Rhythm, derive } from "@rhythmjs/rhythm";
68
68
 
69
- const authModule = new Rhythm<{ userId: string }, { db: Db }>({ name: "auth" })
70
- .use(derive((ctx) => ({ user: ctx.db.findUser(ctx.userId) })));
69
+ const authModule = new Rhythm<{ userId: string }, { db: Db }>({ name: "auth" }).use(
70
+ derive((ctx) => ({ user: ctx.db.findUser(ctx.userId) })),
71
+ );
71
72
  authModule.context.db = connectToUserDb();
72
73
 
73
74
  const app = new Rhythm<{ userId: string }>()
@@ -80,7 +81,7 @@ The child runs in place: if it ends the chain without calling `next()`, the pare
80
81
  ## API
81
82
 
82
83
  - `new Rhythm<TInput, TStartup>(options?)`: creates a pipeline; `options.name`/`options.type` label errors from `register()`.
83
- - `.use(fn: (ctx, next) => Promise<void> | void)`: add an onion middleware step. `next()` takes no arguments; to extend the context pass a `derive()` middleware.
84
+ - `.use(fn: (ctx, next) => Promise<void> | void, condition?: Condition)`: add an onion middleware step. With a second callback, the step runs only when `condition(ctx)` returns true and otherwise falls through to `next()`; a conditional `derive()` does not extend the context type. `next()` takes no arguments; to extend the context pass a `derive()` middleware.
84
85
  - `derive(fn: (ctx) => TExtra | Promise<TExtra>)`: middleware that merges `fn`'s result into the context and continues; the typed way to add fields.
85
86
  - `.context`: the startup values object, typed by `Rhythm<TInput, TStartup>`. Assign to it before serving; every request context carries it. Inherited by registered modules, never by the parent.
86
87
  - `.register(other: Rhythm, exportValue?)`: mount a child `Rhythm` module; sealed by default, opt in via `exportValue`. Controllers (`RhythmRouter`, `RhythmCli`) are not modules; they mount via `.use()` instead.
package/dist/compose.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import type { DeriveMiddleware, Middleware, NextFn } from "./types";
1
+ import type { Condition, DeriveMiddleware, Middleware, NextFn } from "./types";
2
2
  type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
3
3
  type ContextOf<M> = M extends Middleware<infer C> ? C : never;
4
4
  type ExtraOf<M> = M extends DeriveMiddleware<any, infer E> ? E : {};
5
5
  export type ComposedMiddleware<TContext extends object, TExtra extends object> = ((context: TContext, next?: NextFn<TContext>) => Promise<TContext>) & Middleware<TContext> & {
6
6
  readonly "~derive": TExtra;
7
7
  };
8
+ export declare function gate<TContext extends object>(fn: Middleware<TContext>, condition: Condition<TContext>): Middleware<TContext>;
8
9
  export declare function compose<const TMiddleware extends readonly Middleware<any>[]>(middleware: TMiddleware): ComposedMiddleware<UnionToIntersection<ContextOf<TMiddleware[number]>> & {}, UnionToIntersection<ExtraOf<TMiddleware[number]>> & {}>;
9
10
  export declare function compose<TContext extends object>(middleware: Middleware<TContext>[]): (context: TContext, next?: NextFn<TContext>) => Promise<TContext>;
10
11
  export {};
package/dist/compose.js CHANGED
@@ -1,7 +1,9 @@
1
1
  // @bun
2
2
  import {
3
+ gate2,
3
4
  compose2
4
- } from "./rhythm-d9ycs7zz.js";
5
+ } from "./rhythm-4by5yd5a.js";
5
6
  export {
6
- compose2 as compose
7
+ compose2 as compose,
8
+ gate2 as gate
7
9
  };
@@ -0,0 +1,42 @@
1
+ // @bun
2
+ // src/compose.ts
3
+ var toVoid = () => {};
4
+ function gate2(fn, condition) {
5
+ if (typeof condition !== "function")
6
+ throw new TypeError("condition must be a function!");
7
+ return (ctx, next) => {
8
+ const pass = condition(ctx);
9
+ if (typeof pass === "boolean")
10
+ return pass ? fn(ctx, next) : next().then(toVoid);
11
+ return pass.then((ok) => ok ? fn(ctx, next) : next().then(toVoid));
12
+ };
13
+ }
14
+ function compose2(middleware) {
15
+ if (!Array.isArray(middleware))
16
+ throw new TypeError("Middleware stack must be an array!");
17
+ for (const fn of middleware) {
18
+ if (typeof fn !== "function")
19
+ throw new TypeError("Middleware must be composed of functions!");
20
+ }
21
+ return function(context, next) {
22
+ let index = -1;
23
+ return dispatch(0);
24
+ function dispatch(i) {
25
+ if (i <= index)
26
+ return Promise.reject(new Error("next() called multiple times"));
27
+ index = i;
28
+ const fn = i === middleware.length ? next : middleware[i];
29
+ if (!fn)
30
+ return Promise.resolve(context);
31
+ const dispatchNext = () => dispatch(i + 1);
32
+ try {
33
+ const call = fn;
34
+ return Promise.resolve(call(context, dispatchNext)).then(() => context);
35
+ } catch (err) {
36
+ return Promise.reject(err);
37
+ }
38
+ }
39
+ };
40
+ }
41
+
42
+ export { gate2, compose2 };
@@ -0,0 +1,42 @@
1
+ // @bun
2
+ // src/compose.ts
3
+ var toVoid = () => {};
4
+ function gate2(fn, when) {
5
+ if (typeof when !== "function")
6
+ throw new TypeError("when must be a function!");
7
+ return (ctx, next) => {
8
+ const pass = when(ctx);
9
+ if (typeof pass === "boolean")
10
+ return pass ? fn(ctx, next) : next().then(toVoid);
11
+ return pass.then((ok) => ok ? fn(ctx, next) : next().then(toVoid));
12
+ };
13
+ }
14
+ function compose2(middleware) {
15
+ if (!Array.isArray(middleware))
16
+ throw new TypeError("Middleware stack must be an array!");
17
+ for (const fn of middleware) {
18
+ if (typeof fn !== "function")
19
+ throw new TypeError("Middleware must be composed of functions!");
20
+ }
21
+ return function(context, next) {
22
+ let index = -1;
23
+ return dispatch(0);
24
+ function dispatch(i) {
25
+ if (i <= index)
26
+ return Promise.reject(new Error("next() called multiple times"));
27
+ index = i;
28
+ const fn = i === middleware.length ? next : middleware[i];
29
+ if (!fn)
30
+ return Promise.resolve(context);
31
+ const dispatchNext = () => dispatch(i + 1);
32
+ try {
33
+ const call = fn;
34
+ return Promise.resolve(call(context, dispatchNext)).then(() => context);
35
+ } catch (err) {
36
+ return Promise.reject(err);
37
+ }
38
+ }
39
+ };
40
+ }
41
+
42
+ export { gate2, compose2 };
package/dist/rhythm.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DeriveMiddleware, Middleware, OmitHashKeys } from "./types";
1
+ import type { Condition, DeriveMiddleware, Middleware, OmitHashKeys } from "./types";
2
2
  export interface RhythmOptions {
3
3
  name?: string;
4
4
  type?: string;
@@ -7,17 +7,12 @@ export interface RhythmOptions {
7
7
  export declare function derive<TContext extends object, TExtra extends object>(fn: (ctx: TContext) => TExtra | Promise<TExtra>): DeriveMiddleware<TContext, OmitHashKeys<TExtra>>;
8
8
  export declare class Rhythm<TInput extends object = {}, TStartup extends object = {}, TContext extends object = TInput & TStartup> {
9
9
  #private;
10
- /**
11
- * Startup-time values (a db connection, config, ...). Declare the shape as the second type parameter, assign
12
- * before serving, and every request context carries them. Registered modules inherit their parent's values and
13
- * can add their own; a module's values never reach its parent.
14
- */
15
10
  readonly context: TStartup;
16
11
  parent?: Rhythm<any, any, any>;
17
12
  constructor(options?: RhythmOptions);
18
13
  get sources(): readonly object[];
19
14
  use<TExtra extends object>(fn: DeriveMiddleware<TContext, TExtra>): Rhythm<TInput, TStartup, TContext & TExtra>;
20
- use(fn: Middleware<TContext>): this;
15
+ use(fn: Middleware<TContext>, condition?: Condition<TContext>): this;
21
16
  register<TRegInput extends object, TRegContext extends object, TExported extends object = {}>(other: Rhythm<TRegInput, any, TRegContext> & (TContext extends TRegInput ? unknown : never), exportValue?: (result: TRegContext) => TExported): Rhythm<TInput, TStartup, TContext & TExported>;
22
17
  callback(): (input: TInput) => Promise<TContext>;
23
18
  run(input: TInput): Promise<TContext>;
package/dist/rhythm.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // @bun
2
2
  import {
3
+ gate2,
3
4
  compose2
4
- } from "./rhythm-d9ycs7zz.js";
5
+ } from "./rhythm-4by5yd5a.js";
5
6
  import {
6
7
  withSource2,
7
8
  sourceOf2
@@ -46,10 +47,10 @@ class Rhythm {
46
47
  source.parent = this;
47
48
  this.#sources.push(source);
48
49
  }
49
- use(fn) {
50
+ use(fn, condition) {
50
51
  if (typeof fn !== "function")
51
52
  throw new TypeError("middleware must be a function!");
52
- this.#middleware.push(fn);
53
+ this.#middleware.push(condition ? gate2(fn, condition) : fn);
53
54
  this.#adopt(sourceOf2(fn));
54
55
  return this;
55
56
  }
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type OmitHashKeys<T> = {
2
2
  [K in keyof T as K extends `#${string}` ? never : K]: T[K];
3
3
  };
4
+ export type Condition<TContext extends object> = (ctx: TContext) => boolean | Promise<boolean>;
4
5
  export type NextFn<TContext extends object> = () => Promise<TContext>;
5
6
  export type Middleware<TContext extends object> = (ctx: TContext, next: NextFn<TContext>) => Promise<void> | void;
6
7
  export type DeriveMiddleware<TContext extends object, TExtra extends object> = Middleware<TContext> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhythmjs/rhythm",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
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",