nanoeffects 0.13.0 → 0.15.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.
Files changed (39) hide show
  1. package/README.md +2 -1
  2. package/dist/actions.d.ts +2 -2
  3. package/dist/context-types-qXxr_Grf.d.ts +14 -0
  4. package/dist/context.d.ts +25 -0
  5. package/dist/context.js +1 -0
  6. package/dist/dev.d.ts +1 -1
  7. package/dist/effects.d.ts +1 -1
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +1 -1
  10. package/dist/{module-woawvR28.d.ts → module-COv9ZSzS.d.ts} +1 -1
  11. package/dist/notrace/actions.d.ts +1 -1
  12. package/dist/notrace/context-types-DQYBFJG4.d.ts +14 -0
  13. package/dist/notrace/effects.d.ts +1 -1
  14. package/dist/notrace/index.d.ts +2 -2
  15. package/dist/notrace/index.js +1 -1
  16. package/dist/notrace/{plugin-Cw45_-Bo.d.ts → plugin-W4pZY7mM.d.ts} +1 -1
  17. package/dist/observable.d.ts +29 -0
  18. package/dist/observable.js +1 -1
  19. package/dist/oop.d.ts +48 -0
  20. package/dist/oop.js +1 -1
  21. package/dist/{plugin-BrTsPg2Y.d.ts → plugin-15-kkZp0.d.ts} +1 -1
  22. package/dist/scope.d.ts +1 -1
  23. package/dist/{types-bzocBVYc.d.ts → types-C7ukMwE-.d.ts} +1 -1
  24. package/docs/cancel-stale-requests.md +6 -2
  25. package/docs/context.md +3 -2
  26. package/docs/debounce-and-abort.md +1 -3
  27. package/docs/effects.md +10 -3
  28. package/docs/observable.md +50 -0
  29. package/docs/oop.md +126 -4
  30. package/docs/package.md +1 -0
  31. package/docs/plugins.md +12 -4
  32. package/docs/stores.md +4 -2
  33. package/docs/tracing.md +34 -8
  34. package/docs/typescript.md +2 -1
  35. package/llms-full.txt +249 -29
  36. package/llms.txt +2 -2
  37. package/package.json +12 -3
  38. package/dist/context-plugin-Cphk8jfq.d.ts +0 -34
  39. package/dist/notrace/context-plugin-BQa-wtbj.d.ts +0 -34
package/README.md CHANGED
@@ -87,8 +87,9 @@ Start with the [Guide](docs/guide.md) (bus → policies → effects → modules)
87
87
  | Retry, timeout, wait for events | [Effects](docs/effects.md) |
88
88
  | Domain logic off the UI | [Actions, commands, modules](docs/actions.md) |
89
89
  | Redux / Zustand / MobX | [Stores](docs/stores.md) |
90
+ | See what ran and why (console, Redux DevTools) | [Tracing](docs/tracing.md) |
90
91
 
91
- [Full index, package entries, tracing](docs/README.md) · [Examples](examples/README.md) · [Why nanoeffects exists](docs/motivation.md)
92
+ [Full index and package entries](docs/README.md) · [Examples](examples/README.md) · [Why nanoeffects exists](docs/motivation.md)
92
93
 
93
94
  - **Tiny.** `createBus` is about 1.4 kB gzip (1.3 kB brotli); other entries load only when imported.
94
95
  - **Cancellation first.** Aborting a task aborts its children and drops its result.
package/dist/actions.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { B as EventMap, G as Task, T as EventKey, U as PayloadOf, c as WithScope, h as Bus, n as Free, o as Scope, r as FreeActionCreator, t as ActionCreator } from "./action-creator-CQlFgQm4.js";
2
- import { r as Scoped, t as FieldsOfScope } from "./types-bzocBVYc.js";
3
- import { a as createModule, c as CarryingCalls, d as commandsPlugin, f as createCommand, l as MadeCommand, n as ModuleApi, o as modulePlugin, r as ModuleContext, s as Binding, t as Module, u as bindHandler } from "./module-woawvR28.js";
2
+ import { r as Scoped, t as FieldsOfScope } from "./types-C7ukMwE-.js";
3
+ import { a as createModule, c as CarryingCalls, d as commandsPlugin, f as createCommand, l as MadeCommand, n as ModuleApi, o as modulePlugin, r as ModuleContext, s as Binding, t as Module, u as bindHandler } from "./module-COv9ZSzS.js";
4
4
  import { t as OriginOptions } from "./origin-key-CxK0SkeG.js";
5
5
  //#region src/actions/action.d.ts
6
6
  type ScopedCreator<P, T extends string, F> = [P] extends [Free] ? FreeActionCreator<T, F> & WithScope<F> : ActionCreator<P, T, PayloadOf<P>, F> & WithScope<F>;
@@ -0,0 +1,14 @@
1
+ import { B as EventMap, x as ContextOf } from "./action-creator-CQlFgQm4.js";
2
+ //#region src/core/context-types.d.ts
3
+ /** What a function that makes the fields of a task gets to see. */
4
+ type ContextTask = {
5
+ type: string;
6
+ payload?: unknown;
7
+ };
8
+ /**
9
+ * `unknown` when none of the `fields` is already a context field of the bus,
10
+ * else `never`: the call does not compile. Used as `bus: Bus<E> & NoClash<…>`.
11
+ */
12
+ type NoClash<Fields extends object, E extends EventMap> = [keyof Fields & keyof ContextOf<E>] extends [never] ? unknown : never;
13
+ //#endregion
14
+ export { NoClash as n, ContextTask as t };
@@ -0,0 +1,25 @@
1
+ import { B as EventMap, N as WithContext, h as Bus } from "./action-creator-CQlFgQm4.js";
2
+ import { n as NoClash, t as ContextTask } from "./context-types-qXxr_Grf.js";
3
+ //#region src/context/plugin.d.ts
4
+ /**
5
+ * Puts `fields` on the context of every task of the bus: a store, a client, a
6
+ * logger. The types come from the value, so the event map needs no
7
+ * `WithContext`. Returns the same bus, typed with the fields. A function gets
8
+ * `{ type, payload }` of the task and returns its fields.
9
+ *
10
+ * Several uses merge with each other, with the `context` option of `createBus`
11
+ * and with `effectsPlugin`. A name that the bus already has is a type error. The
12
+ * fields of the library (`signal`, `emit`, `run`, …) win, and a field that the
13
+ * context already has is not replaced.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * type Events = { "user/loaded": User };
18
+ *
19
+ * const bus = createBus<Events>().use(contextPlugin({ store }));
20
+ * bus.on("user/loaded", (user, { store }) => store.put(user)); // ctx.store: Store
21
+ * ```
22
+ */
23
+ export declare const contextPlugin: <Fields extends object>(fields: Fields | ((task: ContextTask) => Fields)) => <E extends EventMap>(bus: Bus<E> & NoClash<Fields, E>) => Bus<E & WithContext<Fields>>;
24
+ //#endregion
25
+ export type { ContextTask, NoClash };
@@ -0,0 +1 @@
1
+ const e=e=>typeof e==`function`,t=e=>({type:Reflect.get(e,`type`),payload:Reflect.get(e,`payload`)}),n=n=>r=>{let i=r.addContext;return r.addContext=r=>{i?.(r);let a=e(n)?n(t(r)):n;for(let[e,t]of Object.entries(a))e in r||Reflect.set(r,e,t)},r};export{n as contextPlugin};
package/dist/dev.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { B as EventMap, Y as UnsubscribeCallback, h as Bus } from "./action-creator-CQlFgQm4.js";
2
- import { i as WithApp, n as ModuleApi } from "./module-woawvR28.js";
2
+ import { i as WithApp, n as ModuleApi } from "./module-COv9ZSzS.js";
3
3
  //#region src/dev/types.d.ts
4
4
  /** The parts of what `modulePlugin` returned that the diagnostics report. */
5
5
  type ReportedApp<E extends EventMap> = Pick<ModuleApi<E>, "mount" | "unmount" | "isMounted">;
package/dist/effects.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { B as EventMap, G as Task, u as Effect } from "./action-creator-CQlFgQm4.js";
2
- import { a as RaceStarter, c as all, d as delay, i as take, l as EffectContext, n as effectsPlugin, o as race, r as TakeOptions, s as allSettled, u as Starter } from "./plugin-BrTsPg2Y.js";
2
+ import { a as RaceStarter, c as all, d as delay, i as take, l as EffectContext, n as effectsPlugin, o as race, r as TakeOptions, s as allSettled, u as Starter } from "./plugin-15-kkZp0.js";
3
3
  //#region src/utils/onAbort.d.ts
4
4
  export declare const onAbort: (ctx: {
5
5
  signal?: AbortSignal | undefined;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { A as HandlerRef, B as EventMap, C as Emit, D as EventPayload, E as EventLike, F as commandCall, G as Task, I as createBus, J as TraceLogger, K as Trace, L as Tracer, M as On, N as WithContext, O as EventsOf, P as WithType, R as anyEventType, S as ContextOption, T as EventKey, U as PayloadOf, V as Keys, W as Phase, Y as UnsubscribeCallback, _ as Command, b as Context, g as BusOptions, h as Bus, i as WithPayload, j as Off, k as Handler, m as AnyEvent, p as Action, q as TraceEvent, v as CommandCall, w as Emittable, x as ContextOf, y as Commands, z as EffectName } from "./action-creator-CQlFgQm4.js";
2
- import { n as NoClash, r as contextPlugin, t as ContextTask } from "./context-plugin-Cphk8jfq.js";
3
- export { type Action, type AnyEvent, type Bus, type BusOptions, type Command, type CommandCall, type Commands, type Context, type ContextOf, type ContextOption, type ContextTask, type EffectName, type Emit, type Emittable, type EventKey, type EventLike, type EventMap, type EventPayload, type EventsOf, type Handler, type HandlerRef, type Keys, type NoClash, type Off, type On, type PayloadOf, type Phase, type Task, type Trace, type TraceEvent, type TraceLogger, type Tracer, type UnsubscribeCallback, type WithContext, type WithPayload, type WithType, anyEventType, commandCall, contextPlugin, createBus };
2
+ import { n as NoClash, t as ContextTask } from "./context-types-qXxr_Grf.js";
3
+ export { type Action, type AnyEvent, type Bus, type BusOptions, type Command, type CommandCall, type Commands, type Context, type ContextOf, type ContextOption, type ContextTask, type EffectName, type Emit, type Emittable, type EventKey, type EventLike, type EventMap, type EventPayload, type EventsOf, type Handler, type HandlerRef, type Keys, type NoClash, type Off, type On, type PayloadOf, type Phase, type Task, type Trace, type TraceEvent, type TraceLogger, type Tracer, type UnsubscribeCallback, type WithContext, type WithPayload, type WithType, anyEventType, commandCall, createBus };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a as e,r as t,t as n}from"./bus-Cdw5ajKF.js";const r=e=>typeof e==`function`,i=e=>({type:Reflect.get(e,`type`),payload:Reflect.get(e,`payload`)}),a=e=>t=>{let n=t.addContext;return t.addContext=t=>{n?.(t);let a=r(e)?e(i(t)):e;for(let[e,n]of Object.entries(a))e in t||Reflect.set(t,e,n)},t};export{e as anyEventType,n as commandCall,a as contextPlugin,t as createBus};
1
+ export{a as anyEventType,t as commandCall,r as createBus}from"./bus-Cdw5ajKF.js";
@@ -1,5 +1,5 @@
1
1
  import { B as EventMap, G as Task, U as PayloadOf, Y as UnsubscribeCallback, _ as Command, b as Context, c as WithScope, h as Bus, o as Scope, v as CommandCall, y as Commands } from "./action-creator-CQlFgQm4.js";
2
- import { n as ScopeSlots, r as Scoped, t as FieldsOfScope } from "./types-bzocBVYc.js";
2
+ import { n as ScopeSlots, r as Scoped, t as FieldsOfScope } from "./types-C7ukMwE-.js";
3
3
  //#region src/actions/command.d.ts
4
4
  /** What `createCommand` returns: a command that also tells whether an action is of its type. */
5
5
  type MadeCommand<P, R, T extends string> = Command<P, R> & {
@@ -1,5 +1,5 @@
1
1
  import { B as PayloadOf, C as EventKey, H as Task, K as UnsubscribeCallback, R as EventMap, _ as Commands, a as Scope, g as CommandCall, h as Command, n as Free, o as WithFields, p as Bus, r as FreeActionCreator, s as WithScope, t as ActionCreator, u as Effects, v as Context } from "./action-creator-Ls3Gy3jk.js";
2
- import { t as BoundOf } from "./plugin-Cw45_-Bo.js";
2
+ import { t as BoundOf } from "./plugin-W4pZY7mM.js";
3
3
  import { t as OriginOptions } from "./origin-key-CxK0SkeG.js";
4
4
  //#region src/scope/types.d.ts
5
5
  /**
@@ -0,0 +1,14 @@
1
+ import { R as EventMap, y as ContextOf } from "./action-creator-Ls3Gy3jk.js";
2
+ //#region src/core/context-types.d.ts
3
+ /** What a function that makes the fields of a task gets to see. */
4
+ type ContextTask = {
5
+ type: string;
6
+ payload?: unknown;
7
+ };
8
+ /**
9
+ * `unknown` when none of the `fields` is already a context field of the bus,
10
+ * else `never`: the call does not compile. Used as `bus: Bus<E> & NoClash<…>`.
11
+ */
12
+ type NoClash<Fields extends object, E extends EventMap> = [keyof Fields & keyof ContextOf<E>] extends [never] ? unknown : never;
13
+ //#endregion
14
+ export { NoClash as n, ContextTask as t };
@@ -1,5 +1,5 @@
1
1
  import { H as Task, R as EventMap, l as Effect } from "./action-creator-Ls3Gy3jk.js";
2
- import { a as RaceStarter, c as all, d as delay, i as take, l as EffectContext, n as effectsPlugin, o as race, r as TakeOptions, s as allSettled, u as Starter } from "./plugin-Cw45_-Bo.js";
2
+ import { a as RaceStarter, c as all, d as delay, i as take, l as EffectContext, n as effectsPlugin, o as race, r as TakeOptions, s as allSettled, u as Starter } from "./plugin-W4pZY7mM.js";
3
3
  //#region src/utils/onAbort.d.ts
4
4
  export declare const onAbort: (ctx: {
5
5
  signal?: AbortSignal | undefined;
@@ -1,3 +1,3 @@
1
1
  import { A as On, B as PayloadOf, C as EventKey, D as Handler, E as EventsOf, F as Tracer, G as TraceLogger, H as Task, I as anyEventType, K as UnsubscribeCallback, L as EffectName, M as WithType, N as commandCall, O as HandlerRef, P as createBus, R as EventMap, S as Emittable, T as EventPayload, U as Trace, V as Phase, W as TraceEvent, _ as Commands, b as ContextOption, d as Action, f as AnyEvent, g as CommandCall, h as Command, i as WithPayload, j as WithContext, k as Off, m as BusOptions, p as Bus, v as Context, w as EventLike, x as Emit, y as ContextOf, z as Keys } from "./action-creator-Ls3Gy3jk.js";
2
- import { n as NoClash, r as contextPlugin, t as ContextTask } from "./context-plugin-BQa-wtbj.js";
3
- export { type Action, type AnyEvent, type Bus, type BusOptions, type Command, type CommandCall, type Commands, type Context, type ContextOf, type ContextOption, type ContextTask, type EffectName, type Emit, type Emittable, type EventKey, type EventLike, type EventMap, type EventPayload, type EventsOf, type Handler, type HandlerRef, type Keys, type NoClash, type Off, type On, type PayloadOf, type Phase, type Task, type Trace, type TraceEvent, type TraceLogger, type Tracer, type UnsubscribeCallback, type WithContext, type WithPayload, type WithType, anyEventType, commandCall, contextPlugin, createBus };
2
+ import { n as NoClash, t as ContextTask } from "./context-types-DQYBFJG4.js";
3
+ export { type Action, type AnyEvent, type Bus, type BusOptions, type Command, type CommandCall, type Commands, type Context, type ContextOf, type ContextOption, type ContextTask, type EffectName, type Emit, type Emittable, type EventKey, type EventLike, type EventMap, type EventPayload, type EventsOf, type Handler, type HandlerRef, type Keys, type NoClash, type Off, type On, type PayloadOf, type Phase, type Task, type Trace, type TraceEvent, type TraceLogger, type Tracer, type UnsubscribeCallback, type WithContext, type WithPayload, type WithType, anyEventType, commandCall, createBus };
@@ -1 +1 @@
1
- import{i as e,n as t,t as n}from"./bus-G2AdGObY.js";const r=e=>typeof e==`function`,i=e=>({type:Reflect.get(e,`type`),payload:Reflect.get(e,`payload`)}),a=e=>t=>{let n=t.addContext;return t.addContext=t=>{n?.(t);let a=r(e)?e(i(t)):e;for(let[e,n]of Object.entries(a))e in t||Reflect.set(t,e,n)},t};export{e as anyEventType,n as commandCall,a as contextPlugin,t as createBus};
1
+ export{i as anyEventType,t as commandCall,n as createBus}from"./bus-G2AdGObY.js";
@@ -1,5 +1,5 @@
1
1
  import { R as EventMap, c as BoundForm, j as WithContext, p as Bus, u as Effects, v as Context, z as Keys } from "./action-creator-Ls3Gy3jk.js";
2
- import { n as NoClash } from "./context-plugin-BQa-wtbj.js";
2
+ import { n as NoClash } from "./context-types-DQYBFJG4.js";
3
3
  //#region src/utils/delay.d.ts
4
4
  /**
5
5
  * Resolves after `ms`, or rejects early if `ctx.signal` aborts. `ms` can be a
@@ -17,4 +17,33 @@ export interface Subscribable<T> {
17
17
  export type Observable<T> = Subscribable<T> & {};
18
18
  export type Observe<E extends EventMap> = <K extends Keys<E>>(event: K) => Observable<E[K]>;
19
19
  export declare const createObserve: <E extends EventMap>(bus: Bus<E>) => Observe<E>;
20
+ /**
21
+ * What a `Subject` reads and writes: the `on` and `emit` of a bus. An instance
22
+ * of a class made by `extendBus` or `Emitter` fits as well, and its `on` keeps
23
+ * the subscription for `dispose()`.
24
+ */
25
+ export type Source<E extends EventMap> = Pick<Bus<E>, "on" | "emit">;
26
+ /**
27
+ * One event of a source as an Observable that can also push a payload back:
28
+ * `subscribe` is `source.on(event, …)` and `next` is `source.emit(event, payload)`.
29
+ * It never completes, fails or replays, and it can be extended.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const typed = new Subject(bus, "typed"); // Subject — the event "typed" of the bus as a stream
34
+ * const subscription = typed.subscribe((text) => console.log(text));
35
+ * typed.next("hello"); // the same as bus.emit("typed", "hello")
36
+ * subscription.unsubscribe();
37
+ * ```
38
+ */
39
+ export declare class Subject<E extends EventMap, K extends Keys<E>> implements Observable<E[K]> {
40
+ [$$observable]: () => Subscribable<E[K]>;
41
+ readonly source: Source<E>;
42
+ readonly event: K;
43
+ constructor(source: Source<E>, event: K);
44
+ subscribe(observer: Observer<E[K]>): {
45
+ unsubscribe(): void;
46
+ };
47
+ next(payload: E[K]): void;
48
+ }
20
49
  //#endregion
@@ -1 +1 @@
1
- const e=typeof Symbol==`function`&&Symbol.observable||`@@observable`,t=t=>n=>({subscribe:e=>({unsubscribe:t.on(n,t=>{typeof e==`function`?e(t):e.next?.(t)})}),[e](){return this}});export{e as $$observable,t as createObserve};
1
+ const e=typeof Symbol==`function`&&Symbol.observable||`@@observable`,t=e=>t=>{typeof e==`function`?e(t):e.next?.(t)},n=n=>r=>({subscribe:e=>({unsubscribe:n.on(r,t(e))}),[e](){return this}});var r=class{source;event;constructor(e,t){this.source=e,this.event=t,this.next=this.next.bind(this)}subscribe(e){return{unsubscribe:this.source.on(this.event,t(e))}}next(e){let t=this.source.emit;Reflect.apply(t,this.source,[this.event,e])}[e](){return this}};export{e as $$observable,r as Subject,n as createObserve};
package/dist/oop.d.ts CHANGED
@@ -87,7 +87,51 @@ export declare function Once<P>(event: EventOf<P>, options: ListenerOptions): Me
87
87
  export declare function Once(event: string, wrap?: Wrap, options?: ListenerOptions): MethodDecorator<never>;
88
88
  export declare function Once(event: string, options: ListenerOptions): MethodDecorator<never>;
89
89
  //#endregion
90
+ //#region src/oop/emitter.d.ts
91
+ /**
92
+ * A base class whose every instance is an independent emitter: it has `on`,
93
+ * `once`, `off`, `emit`, `dispose` and its own `bus`, typed by the event map
94
+ * `E`. Events of one instance never reach another instance. A subscription
95
+ * made through `this.on` or `this.once`, a method decorated with `@On` or
96
+ * `@Once`, and a `Subject` made over `this` all end with `dispose()`.
97
+ *
98
+ * `on`, `once`, `off` and `emit` are bound to the instance, so they can be
99
+ * taken off it (`const { emit } = video`). A subclass replaces one with an
100
+ * arrow property; TypeScript does not let it redeclare one as a method.
101
+ *
102
+ * It has no methods of plugins, because its bus is made inside. For a bus that
103
+ * instances share, or for plugins, use `extendBus(bus)`.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * type VideoEvents = { "video/time": number };
108
+ *
109
+ * class VideoService extends Emitter<VideoEvents> {
110
+ * constructor() {
111
+ * super();
112
+ * this.on("video/time", (seconds) => console.log(seconds)); // ends with dispose()
113
+ * }
114
+ * }
115
+ *
116
+ * const video = new VideoService();
117
+ * video.emit("video/time", 12);
118
+ * video.dispose(); // its subscriptions are gone
119
+ * ```
120
+ */
121
+ export declare class Emitter<E extends EventMap = EventMap> {
122
+ on: Bus<E>["on"];
123
+ once: Bus<E>["once"];
124
+ off: Bus<E>["off"];
125
+ emit: Bus<E>["emit"];
126
+ [Symbol.dispose]: () => void;
127
+ constructor();
128
+ get bus(): Bus<E>;
129
+ /** Removes every subscription made through `on` and `once` of this instance. */
130
+ dispose(): void;
131
+ }
132
+ //#endregion
90
133
  //#region src/oop/extend.d.ts
134
+ declare class Empty {}
91
135
  /**
92
136
  * Makes a class from `Base` whose instances have the methods of a bus, with the
93
137
  * subscriptions made through an instance ended by `dispose()`. `new Extended(…)`
@@ -100,6 +144,9 @@ export declare function Once(event: string, options: ListenerOptions): MethodDec
100
144
  * shared by its instances. The methods of the plugins used on `bus` before this
101
145
  * call are on the instances too; the ones of plugins used later are not.
102
146
  *
147
+ * `extendBus(bus)` with no class returns a class over an empty base, to extend
148
+ * as it is: `class Service extends extendBus(bus) {}`.
149
+ *
103
150
  * @example
104
151
  * ```ts
105
152
  * class SearchServiceBase {
@@ -116,6 +163,7 @@ export declare function Once(event: string, options: ListenerOptions): MethodDec
116
163
  */
117
164
  export declare function extendBus<C extends Constructor>(Base: C): Extended<C, Bus<EventMap>>;
118
165
  export declare function extendBus<B extends object, C extends Constructor>(bus: B, Base: C): Extended<C, B>;
166
+ export declare function extendBus<B extends object>(bus: B): Extended<typeof Empty, B>;
119
167
  //#endregion
120
168
  //#region src/oop/subscriptions.d.ts
121
169
  /**
package/dist/oop.js CHANGED
@@ -1 +1 @@
1
- import{r as e}from"./bus-Cdw5ajKF.js";const t=/* @__PURE__ */ new WeakMap,n=e=>typeof e==`function`,r=e=>Reflect.ownKeys(e).flatMap(t=>{let r=Reflect.getOwnPropertyDescriptor(e,t)?.value;return n(r)?[{key:t,value:r}]:[]}),i=/* @__PURE__ */ new WeakMap,a=e=>{let n=i.get(e);if(n)return n;let a=/* @__PURE__ */ new Set,o=[];for(let n=e;n&&n!==Object.prototype;n=Reflect.getPrototypeOf(n))for(let{key:e,value:i}of r(n)){if(e===`constructor`||a.has(e))continue;a.add(e);let n=t.get(i);n&&o.push({key:e,method:i,spec:n})}return i.set(e,o),o},o=/* @__PURE__ */ new WeakMap,s=e=>typeof e==`object`&&!!e||typeof e==`function`,c=e=>{let t=o.get(e);if(t)return t;let n=/* @__PURE__ */ new Set;return o.set(e,n),n},l=e=>{if(typeof e==`string`)return e;if(s(e)){let t=Reflect.get(e,`type`);return typeof t==`string`?t:e}return e},u=(e,t,n)=>Reflect.apply(Reflect.get(e,t),e,n),d=(e,t,n,r)=>{let[i,a,...o]=r,s=c(e),l={event:i,handler:a,off:()=>{}},d=u(t,n,[i,n===`once`?(e,t)=>(s.delete(l),typeof a==`function`?Reflect.apply(a,null,[e,t]):void 0):a,...o]),f=typeof d==`function`?()=>d():()=>{};return l.off=f,s.add(l),()=>{s.delete(l),f()}},f=(e,t,n)=>{let[r,i]=n,a=c(e);r===void 0&&a.clear();for(let e of[...a])Object.is(l(e.event),l(r))&&(i===void 0||e.handler===i)&&(a.delete(e),e.off());return u(t,`off`,n)},p=e=>{if(!s(e))return;let t=o.get(e);if(t){o.delete(e);for(let e of t)e.off()}},m=/* @__PURE__ */ new Set([`on`,`once`,`off`,`emit`,`ctx`,`commands`,`trace`,`addContext`,`applyScope`,`fitScope`,`app`,`use`]),h=Reflect.get(Symbol,`dispose`),g=e=>typeof e==`function`,_=(e,t,n,r)=>{n in t||Object.defineProperty(e,n,{configurable:!0,enumerable:!1,...r})},v=/* @__PURE__ */ new WeakMap,y=(e,t)=>{let n=v.get(e);if(n)return n;let r={on:(...n)=>d(e,t,`on`,n),once:(...n)=>d(e,t,`once`,n),off:(...n)=>f(e,t,n)};return v.set(e,r),r},b=(e,t)=>(...n)=>Reflect.apply(Reflect.get(e,t),e,n),x=(e,t)=>{for(let{method:n,spec:r}of a(Reflect.getPrototypeOf(e)??{})){let i=(t,r)=>Reflect.apply(n,e,[t,r]),a=r.wrap?r.wrap(i):i,o=r.once?`once`:`on`;d(e,t,o,[r.event,a,r.options])}},S=(e,t,n)=>{for(let r of[`on`,`once`,`off`])_(e,t,r,{get(){return y(this,n)[r]}});_(e,t,`emit`,{value:b(n,`emit`)});for(let[r,i]of Object.entries(n))typeof i==`function`&&!m.has(r)&&_(e,t,r,{value:b(n,r)});let r=function(){p(this)};_(e,t,`dispose`,{value:r}),h&&_(e,t,h,{value:r}),_(e,t,`bus`,{get:()=>n})},C=(e,t)=>{let n=function(...r){let i=Reflect.construct(t,r,new.target??n);return x(i,e),i};return Object.setPrototypeOf(n,t),n.prototype=Object.create(t.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),Object.defineProperty(n,"name",{value:t.name,configurable:!0}),`bus`in t||Object.defineProperty(n,"bus",{value:e,configurable:!0}),S(n.prototype,t.prototype,e),n};function w(t,n){if(g(t))return C(e(),t);if(!n)throw TypeError(`extendBus: pass the class to extend`);return C(t,n)}function T(e){return t=>e?w(e,t):w(t)}const E=e=>{let[t,n,r]=e;return typeof n==`string`||typeof n==`symbol`?Reflect.get(Object(r),`value`):t},D=(e,n,r,i)=>((...a)=>{let o=E(a);if(typeof o==`function`){let a=typeof n==`function`?n:void 0,s=typeof n==`function`?r:n??r;t.set(o,{event:e,wrap:a,once:i,options:s})}});function O(e,t,n){return D(e,t,n,!1)}function k(e,t,n){return D(e,t,n,!0)}export{O as On,k as Once,T as WithBus,p as disposeBus,w as extendBus};
1
+ import{r as e}from"./bus-Cdw5ajKF.js";const t=/* @__PURE__ */ new WeakMap,n=e=>typeof e==`function`,r=e=>Reflect.ownKeys(e).flatMap(t=>{let r=Reflect.getOwnPropertyDescriptor(e,t)?.value;return n(r)?[{key:t,value:r}]:[]}),i=/* @__PURE__ */ new WeakMap,a=e=>{let n=i.get(e);if(n)return n;let a=/* @__PURE__ */ new Set,o=[];for(let n=e;n&&n!==Object.prototype;n=Reflect.getPrototypeOf(n))for(let{key:e,value:i}of r(n)){if(e===`constructor`||a.has(e))continue;a.add(e);let n=t.get(i);n&&o.push({key:e,method:i,spec:n})}return i.set(e,o),o},o=/* @__PURE__ */ new WeakMap,s=e=>typeof e==`object`&&!!e||typeof e==`function`,c=e=>{let t=o.get(e);if(t)return t;let n=/* @__PURE__ */ new Set;return o.set(e,n),n},l=e=>{if(typeof e==`string`)return e;if(s(e)){let t=Reflect.get(e,`type`);return typeof t==`string`?t:e}return e},u=(e,t,n)=>Reflect.apply(Reflect.get(e,t),e,n),d=(e,t,n,r)=>{let[i,a,...o]=r,s=c(e),l={event:i,handler:a,off:()=>{}},d=u(t,n,[i,n===`once`?(e,t)=>(s.delete(l),typeof a==`function`?Reflect.apply(a,null,[e,t]):void 0):a,...o]),f=typeof d==`function`?()=>d():()=>{};return l.off=f,s.add(l),()=>{s.delete(l),f()}},f=(e,t,n)=>{let[r,i]=n,a=c(e);r===void 0&&a.clear();for(let e of[...a])Object.is(l(e.event),l(r))&&(i===void 0||e.handler===i)&&(a.delete(e),e.off());return u(t,`off`,n)},p=e=>{if(!s(e))return;let t=o.get(e);if(t){o.delete(e);for(let e of t)e.off()}},m=/* @__PURE__ */ new Set([`on`,`once`,`off`,`emit`,`ctx`,`commands`,`trace`,`addContext`,`applyScope`,`fitScope`,`app`,`use`]),h=Reflect.get(Symbol,`dispose`),g=e=>typeof e==`function`;var _=class{};const v=(e,t,n,r)=>{n in t||Object.defineProperty(e,n,{configurable:!0,enumerable:!1,...r})},y=/* @__PURE__ */ new WeakMap,b=(e,t)=>{let n=y.get(e);if(n)return n;let r={on:(...n)=>d(e,t,`on`,n),once:(...n)=>d(e,t,`once`,n),off:(...n)=>f(e,t,n)};return y.set(e,r),r},x=(e,t)=>(...n)=>Reflect.apply(Reflect.get(e,t),e,n),S=(e,t)=>{for(let{method:n,spec:r}of a(Reflect.getPrototypeOf(e)??{})){let i=(t,r)=>Reflect.apply(n,e,[t,r]),a=r.wrap?r.wrap(i):i,o=r.once?`once`:`on`;d(e,t,o,[r.event,a,r.options])}},C=(e,t,n)=>{for(let r of[`on`,`once`,`off`])v(e,t,r,{get(){return b(this,n)[r]}});v(e,t,`emit`,{value:x(n,`emit`)});for(let[r,i]of Object.entries(n))typeof i==`function`&&!m.has(r)&&v(e,t,r,{value:x(n,r)});let r=function(){p(this)};v(e,t,`dispose`,{value:r}),h&&v(e,t,h,{value:r}),v(e,t,`bus`,{get:()=>n})},w=(e,t)=>{let n=function(...r){let i=Reflect.construct(t,r,new.target??n);return S(i,e),i};return Object.setPrototypeOf(n,t),n.prototype=Object.create(t.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),Object.defineProperty(n,"name",{value:t.name,configurable:!0}),`bus`in t||Object.defineProperty(n,"bus",{value:e,configurable:!0}),C(n.prototype,t.prototype,e),n};function T(t,n){return g(t)?w(e(),t):w(t,n??_)}function E(e){return t=>e?T(e,t):T(t)}const D=e=>{let[t,n,r]=e;return typeof n==`string`||typeof n==`symbol`?Reflect.get(Object(r),`value`):t},O=(e,n,r,i)=>((...a)=>{let o=D(a);if(typeof o==`function`){let a=typeof n==`function`?n:void 0,s=typeof n==`function`?r:n??r;t.set(o,{event:e,wrap:a,once:i,options:s})}});function k(e,t,n){return O(e,t,n,!1)}function A(e,t,n){return O(e,t,n,!0)}const j=/* @__PURE__ */ new WeakMap,M=t=>{let n=j.get(t);if(n)return n;let r=e();return j.set(t,r),r},N=[`on`,`once`,`off`,`emit`],P=e=>{for(let t of N){let n=Reflect.get(e,t);Reflect.set(e,t,n.bind(e))}};var F=class{constructor(){P(this),S(this,M(this))}get bus(){return M(this)}dispose(){p(this)}};const I={on(...e){return d(this,M(this),`on`,e)},once(...e){return d(this,M(this),`once`,e)},off(...e){return f(this,M(this),e)},emit(...e){let t=M(this);return Reflect.apply(Reflect.get(t,`emit`),t,e)}};for(let e of N)Object.defineProperty(F.prototype,e,{configurable:!0,writable:!0,value:I[e]});h&&Object.defineProperty(F.prototype,h,{configurable:!0,value:function(){this.dispose()}});export{F as Emitter,k as On,A as Once,E as WithBus,p as disposeBus,T as extendBus};
@@ -1,5 +1,5 @@
1
1
  import { B as EventMap, N as WithContext, V as Keys, b as Context, d as Effects, h as Bus, l as BoundForm } from "./action-creator-CQlFgQm4.js";
2
- import { n as NoClash } from "./context-plugin-Cphk8jfq.js";
2
+ import { n as NoClash } from "./context-types-qXxr_Grf.js";
3
3
  //#region src/utils/delay.d.ts
4
4
  /**
5
5
  * Resolves after `ms`, or rejects early if `ctx.signal` aborts. `ms` can be a
package/dist/scope.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { B as EventMap, a as EmitScope, d as Effects, f as Signaled, h as Bus, o as Scope, u as Effect } from "./action-creator-CQlFgQm4.js";
2
- import { n as ScopeSlots, r as Scoped } from "./types-bzocBVYc.js";
2
+ import { n as ScopeSlots, r as Scoped } from "./types-C7ukMwE-.js";
3
3
  //#region src/scope/effect.d.ts
4
4
  /**
5
5
  * Marks a function whose first parameter is `{ signal }` as an effect, so that
@@ -1,5 +1,5 @@
1
1
  import { B as EventMap, d as Effects, o as Scope, s as WithFields } from "./action-creator-CQlFgQm4.js";
2
- import { t as BoundOf } from "./plugin-BrTsPg2Y.js";
2
+ import { t as BoundOf } from "./plugin-15-kkZp0.js";
3
3
  //#region src/scope/types.d.ts
4
4
  /**
5
5
  * The slots `scopePlugin` fills for `modulePlugin`. They are not on `Bus`: a bus
@@ -60,7 +60,7 @@ const bus = createBus(); // the bus: events and handlers
60
60
  // latest: a newer search aborts the request in flight
61
61
  const searchLatest = latest(async (query, { signal }) => {
62
62
  const items = await api.search(query, { signal });
63
- bus.emit("results", items);
63
+ render(items);
64
64
  });
65
65
 
66
66
  // debounce: run after 300 ms without a new "typed"
@@ -84,7 +84,11 @@ const searchOnce = timeout(5000, (query, { signal }) => api.search(query, { sign
84
84
  // retry: up to 4 attempts, backoff makes the pause longer each time
85
85
  const searchWithRetry = retry(searchOnce, {
86
86
  times: 4,
87
- delay: backoff({ base: 200, max: 5000, jitter: true }),
87
+ delay: backoff({
88
+ base: 200,
89
+ max: 5000,
90
+ jitter: true,
91
+ }),
88
92
  });
89
93
 
90
94
  // latest: a newer search aborts the whole retry chain
package/docs/context.md CHANGED
@@ -25,10 +25,11 @@ To give fields to **one** command, action, job or module, or to replace them for
25
25
 
26
26
  ## Type the fields (optional)
27
27
 
28
- In TypeScript, `contextPlugin` puts the fields on every task of **this** bus and takes their types from the value you pass. The event map holds only events. It does not wire anything across the app — another bus can have different fields or none.
28
+ In TypeScript, `contextPlugin` (from `nanoeffects/context`) puts the fields on every task of **this** bus and takes their types from the value you pass. The event map holds only events. It does not wire anything across the app — another bus can have different fields or none.
29
29
 
30
30
  ```ts
31
- import { contextPlugin, createBus } from "nanoeffects";
31
+ import { createBus } from "nanoeffects";
32
+ import { contextPlugin } from "nanoeffects/context";
32
33
 
33
34
  // Events — event name → payload
34
35
  type Events = { save: Draft };
@@ -25,13 +25,11 @@ const bus = createBus(); // the bus: events and handlers
25
25
  const searchLatest = latest(async (query, { signal }) => {
26
26
  // signal: pass it on so the browser cancels the request itself
27
27
  const items = await api.search(query, { signal });
28
- bus.emit("results", items);
28
+ render(items);
29
29
  });
30
30
 
31
31
  // debounce: run searchLatest after 300 ms without a new "typed"
32
32
  bus.on("typed", debounce(300, searchLatest));
33
-
34
- bus.on("results", (items) => render(items));
35
33
  ```
36
34
 
37
35
  What to replace: `api.search` is your own request function. It must accept `{ signal }` and give it to `fetch`. `render` shows the items.
package/docs/effects.md CHANGED
@@ -124,7 +124,11 @@ import { backoff, retry } from "nanoeffects/effects";
124
124
  const loadWithRetry = retry(loadUser, {
125
125
  times: 5,
126
126
  // backoff — pause 200, 400, 800, … ms, never over 5 s, a random part of it
127
- delay: backoff({ base: 200, max: 5000, jitter: true }),
127
+ delay: backoff({
128
+ base: 200,
129
+ max: 5000,
130
+ jitter: true,
131
+ }),
128
132
  });
129
133
  ```
130
134
 
@@ -147,8 +151,11 @@ If handlers call the same helpers often, put them on the task once. A handler th
147
151
  import { createBus } from "nanoeffects";
148
152
  import { all, delay, effectsPlugin, race } from "nanoeffects/effects";
149
153
 
150
- // effectsPlugin — add race, all and delay to the context of every task; returns the same bus
151
- const bus = createBus().use(effectsPlugin({ race, all, delay }));
154
+ // effectsPlugin — add race, all and delay to the context of every task
155
+ const effects = effectsPlugin({ race, all, delay });
156
+
157
+ // use returns the same bus, typed with the new fields
158
+ const bus = createBus().use(effects);
152
159
 
153
160
  bus.on("load", async (id, { race, all, delay }) => {
154
161
  // race — same as race(ctx, …)
@@ -70,6 +70,56 @@ bus.on(queryChanged, (query) => console.log("search for", query));
70
70
 
71
71
  The Observable can come from anywhere, not only from `createObserve`. `subscription.unsubscribe()` stops it; the bus does not know about it. Pass the callback to `subscribe` as it is, and do not call `bus.emitter(...)` inside the operators on every value.
72
72
 
73
+ ## Read and write one event with a Subject
74
+
75
+ Problem: `createObserve` only reads. Code written for streams often also needs to push a value into the same event, and passing a bus and an event name around for that is clumsy.
76
+
77
+ `Subject` is one event of a bus as one object. `subscribe` listens, `next` emits. It has no `complete`, no `error` and no replay: an event of a bus does not end, and the bus keeps no values.
78
+
79
+ ```js
80
+ import { createBus } from "nanoeffects";
81
+ import { Subject } from "nanoeffects/observable";
82
+
83
+ const bus = createBus(); // the bus: events and handlers
84
+
85
+ const typed = new Subject(bus, "typed"); // Subject — the event "typed" as a stream you can also write to
86
+
87
+ const subscription = typed.subscribe((text) => console.log(text)); // like bus.on("typed", …)
88
+ typed.next("hello"); // like bus.emit("typed", "hello"): bus.on handlers and subscribers both run
89
+ subscription.unsubscribe(); // removes this subscription only
90
+ ```
91
+
92
+ - `next` works when you take it off the subject, so `input.addEventListener("input", (event) => typed.next(event.target.value))` and `store.subscribe(typed.next)` need no wrapper.
93
+ - A subscriber that is added later does not get a value emitted before. Add `startWith` or `shareReplay` in RxJS when it should.
94
+ - RxJS takes a `Subject` as it takes `createObserve`: `from(typed)`. It is not an RxJS `Subject` (no `error`, `complete` or `pipe`), so it does not fit where that type is wanted.
95
+
96
+ ### Extend it for a named stream
97
+
98
+ Problem: the event name is repeated wherever the stream is made. A subclass fixes it once.
99
+
100
+ ```js
101
+ import { createBus } from "nanoeffects";
102
+ import { Subject } from "nanoeffects/observable";
103
+
104
+ class TypedText extends Subject {
105
+ constructor(bus) {
106
+ super(bus, "typed"); // the event is fixed by the class
107
+ }
108
+
109
+ next(text) {
110
+ super.next(text.trim()); // a method of the subclass replaces the one of Subject
111
+ }
112
+ }
113
+
114
+ const bus = createBus(); // the bus: events and handlers
115
+ const typed = new TypedText(bus);
116
+ typed.next(" hello "); // emits "typed" with "hello"
117
+ ```
118
+
119
+ ### Use it inside a class with a bus
120
+
121
+ A class made with `extendBus` has its own `on` and `emit`. Pass `this` as the source and the subscription belongs to the instance, so `dispose()` ends it ([Classes with a bus](oop.md)).
122
+
73
123
  ## Do the work in a handler, not in `switchMap`
74
124
 
75
125
  An Observable is good for shaping values. A request is better in a handler with a policy, because the handler has a `signal` and the trace shows it. Split it: RxJS ends at the value, the bus starts the work.
package/docs/oop.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Use the bus from a class
2
2
 
3
- Use this page when **your domain logic lives in classes**: a service, a store or a controller that needs the bus inside its methods. A class made with `extendBus` has `on`, `emit` and the other methods of the bus, and remembers what each instance subscribed to, so `dispose()` ends it all.
3
+ Use this page when **your domain logic lives in classes**: a service, a store or a controller that needs the bus inside its methods. There are two ways to give a class the methods of a bus, and both remember what each instance subscribed to, so `dispose()` ends it all:
4
+
5
+ | You want | Use |
6
+ | -------- | --- |
7
+ | every `new Service()` to be an independent emitter | `extends Emitter` |
8
+ | the instances to share a bus, or to use plugins | `extends extendBus(bus)` |
4
9
 
5
10
  If your logic is plain functions, stay with [modules](actions.md#mount-domains-with-modules): a module is the same idea without a class.
6
11
 
@@ -8,7 +13,68 @@ If your logic is plain functions, stay with [modules](actions.md#mount-domains-w
8
13
 
9
14
  A class that listens to the bus keeps a reference to it, and keeps every unsubscribe function in a list to call when the object goes away. Forget one and the old object keeps handling events. `extendBus` does both for you.
10
15
 
11
- ## Give a class the methods of a bus
16
+ ## Make an independent emitter with `Emitter`
17
+
18
+ `Emitter` is a base class. Every instance of a class that extends it has a bus of its own: the events of one instance never reach another. It is the shape of Node's `EventEmitter`.
19
+
20
+ ```js
21
+ // video-service.js
22
+ import { Emitter } from "nanoeffects/oop";
23
+
24
+ // Emitter — a base class: each new VideoService() gets on, once, off, emit, dispose and its own bus
25
+ export class VideoService extends Emitter {
26
+ constructor() {
27
+ super();
28
+ // this.on — a subscription of this instance; dispose() removes it
29
+ this.on("played", () => console.log("playing"));
30
+ }
31
+ }
32
+ ```
33
+
34
+ ```js
35
+ import { VideoService } from "./video-service";
36
+
37
+ const first = new VideoService();
38
+ const second = new VideoService();
39
+ first.on("paused", () => console.log("first paused"));
40
+ second.emit("paused"); // nothing: the instances do not share a bus
41
+ first.emit("paused"); // logs "first paused"
42
+ first.dispose(); // its subscriptions are gone
43
+ ```
44
+
45
+ What you get:
46
+
47
+ - `on`, `once`, `off`, `emit`, `dispose` and `bus` (the bus of that instance). `instance[Symbol.dispose]()` does the same as `dispose()`, so `using video = new VideoService()` works where the runtime supports it.
48
+ - `@On` and `@Once` work on its methods (see below). They are subscribed when the base constructor runs, so before the fields of your subclass.
49
+ - `on`, `once`, `off` and `emit` are bound to the instance, so `const { emit } = video` works. A subclass replaces one with an arrow property (`emit = (…) => …`); TypeScript does not let it redeclare one as a method. `dispose` is a method and can be overridden as usual.
50
+ - No methods of plugins: the bus is made inside, so no plugin is on it. For plugins, or for a bus that several instances or classes share, use `extendBus` (next sections).
51
+ - For a stream of one event, see [`Subject`](observable.md#read-and-write-one-event-with-a-subject) and [Stream an event with a `Subject`](#stream-an-event-with-a-subject).
52
+
53
+ ### Type the events (optional)
54
+
55
+ `Emitter` takes the events of the class as a type parameter.
56
+
57
+ ```ts
58
+ // video-service.ts
59
+ import { Emitter } from "nanoeffects/oop";
60
+
61
+ // VideoEvents — checker only: the payload of each event of this class
62
+ type VideoEvents = { "video/time": number; "video/paused": void };
63
+
64
+ export class VideoService extends Emitter<VideoEvents> {
65
+ constructor() {
66
+ super();
67
+ // seconds is a number because "video/time" carries one
68
+ this.on("video/time", (seconds) => console.log(seconds));
69
+ }
70
+ }
71
+
72
+ const video = new VideoService();
73
+ video.emit("video/time", 12); // checked against VideoEvents
74
+ video.emit("video/paused"); // no payload
75
+ ```
76
+
77
+ ## Give a class the methods of a shared bus
12
78
 
13
79
  `extendBus(bus, Base)` returns a subclass of `Base`. Its instances have the methods of the bus, and the arguments of `new` go to the constructor of `Base`.
14
80
 
@@ -69,9 +135,33 @@ What you get:
69
135
  - `bus` is the bus, and `Class.bus` is the same one.
70
136
  - `this.on` works in the constructor of `Base`. The methods exist on the prototype of the subclass, which is the prototype of the instance already.
71
137
 
138
+ ### Extend the result with no class of your own
139
+
140
+ `extendBus(bus)` without a class returns a class with the methods of that bus and nothing else. Extend it as it is.
141
+
142
+ ```js
143
+ // service.js
144
+ import { createBus } from "nanoeffects";
145
+ import { extendBus } from "nanoeffects/oop";
146
+
147
+ const bus = createBus(); // the bus that the services share
148
+
149
+ // extendBus(bus) — a class with on, once, off, emit and dispose over this bus
150
+ export class AudioService extends extendBus(bus) {
151
+ constructor() {
152
+ super();
153
+ this.on("muted", () => console.log("muted")); // ends with dispose()
154
+ }
155
+ }
156
+
157
+ const audio = new AudioService();
158
+ bus.emit("muted"); // the instances share this bus: logs "muted"
159
+ audio.dispose();
160
+ ```
161
+
72
162
  ## Let the class make its bus
73
163
 
74
- Leave the bus out and the class gets its own, shared by all of its instances.
164
+ Leave the bus out and the class gets its own, shared by all of its instances. That is one bus for each class; `Emitter` makes one for each instance.
75
165
 
76
166
  ```js
77
167
  // notifier.js
@@ -113,6 +203,37 @@ stop(); // the function on returned still works, and does nothing now
113
203
 
114
204
  A subscription that you removed yourself (the function `on` returned, `this.off`, or a `once` that fired) is forgotten, so the instance holds nothing for it.
115
205
 
206
+ ## Stream an event with a `Subject`
207
+
208
+ Problem: a service hands one of its events to code written for streams, and the stream subscription should end with the service. `createObserve(this.bus)` would not: it subscribes through the bus, so `dispose()` does not know it.
209
+
210
+ Pass `this` to `Subject` as the source. The subject subscribes through `this.on`, so the subscription belongs to the instance ([Read and write one event with a Subject](observable.md#read-and-write-one-event-with-a-subject)).
211
+
212
+ ```js
213
+ // typing.js
214
+ import { createBus } from "nanoeffects";
215
+ import { Subject } from "nanoeffects/observable";
216
+ import { extendBus } from "nanoeffects/oop";
217
+
218
+ const bus = createBus(); // the bus: events and handlers
219
+
220
+ class TypingBase {
221
+ constructor() {
222
+ this.typed = new Subject(this, "typed"); // Subject — the event "typed" as a stream; this is the source
223
+ this.typed.subscribe((text) => console.log(text));
224
+ }
225
+ }
226
+
227
+ const Typing = extendBus(bus, TypingBase); // extendBus — a class with on, emit and dispose
228
+
229
+ const typing = new Typing();
230
+ typing.typed.next("a"); // logs "a"
231
+ typing.dispose(); // ends the subscription of the Subject too
232
+ typing.typed.next("b"); // nothing is logged
233
+ ```
234
+
235
+ In TypeScript, declare `on` and `emit` in the class (`declare on: BusMethods<typeof bus>["on"]`, see [Type the instances](#type-the-instances-optional)) so that `this` fits as the source.
236
+
116
237
  ## Keep a method of your own
117
238
 
118
239
  The class wins any clash of names. If `Base` has `emit`, `dispose`, `on` or `bus`, yours stays and the bus method is not added. A `Base` with its own `dispose` would never end the subscriptions, so call `disposeBus(this)` from there.
@@ -285,4 +406,5 @@ service.emit("order/placed", { id: "o1" }); // checked against Events
285
406
 
286
407
  - `this` is not bound in a handler that you pass by hand (see above).
287
408
  - The methods of a plugin used after `extendBus` are not on the class. Use the plugins first, then `extendBus`.
288
- - One bus for each instance, mixing the methods into an object that exists, and decorators for fields or parameters are not part of this entry.
409
+ - `Emitter` has no methods of plugins, because its bus is made inside. Use `extendBus(bus)` for them.
410
+ - Mixing the methods into an object that exists, and decorators for fields or parameters are not part of this entry.
package/docs/package.md CHANGED
@@ -9,6 +9,7 @@ Imports are split so you only load what you use. `createBus` stays small; the re
9
9
  | `nanoeffects/effects` | `retry`, `timeout`, `poll`, `delay`, `take`, `all`, `allSettled`, `race`, `effectsPlugin` |
10
10
  | `nanoeffects/actions` | `createAction`, `createCommand`, `createJob`, `createModule`, `modulePlugin`, `commandsPlugin` |
11
11
  | `nanoeffects/match` | `matchPlugin`: `on`, `once` and `off` that take a function which picks the events |
12
+ | `nanoeffects/context` | `contextPlugin`: your own fields (a store, a client) on `ctx` of every task, typed from the value |
12
13
  | `nanoeffects/signal` | `signalPlugin`: `emit(call, { signal })` ties the tasks of an emit to an outside `AbortSignal` |
13
14
  | `nanoeffects/oop` | `extendBus`, `WithBus`, `On`, `Once`, `disposeBus`: the methods of a bus on a class, with the subscriptions of each instance ended by `dispose()` |
14
15
  | `nanoeffects/scope` | `scopePlugin`, `effect`: fields for one command, action, job, module or `emit` (the types `Scope`, `Effect`) |