@atolljs/angular 0.1.12 → 0.1.14

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,100 @@
1
+ import { type ModuleWithProviders, type Type } from '@angular/core';
2
+ import { type AtollFeature, type AtollPoolDeclaration, type AtollPoolSpec, type AtollProvideOptions } from './provideAtoll.js';
3
+ /**
4
+ * A pool whose declaration resolves from injected deps during app init — the
5
+ * async form of AtollPoolDeclaration. The name stays static so the
6
+ * `ATOLL_POOL:<name>` injection token can be declared up front.
7
+ */
8
+ export interface AtollPoolAsyncDecl {
9
+ /** Registry name — declares the ATOLL_POOL:<name> token before the factory resolves. */
10
+ name?: string;
11
+ /** Provider tokens passed to useFactory, in order. */
12
+ inject?: unknown[];
13
+ /**
14
+ * Receives the injected deps; returns a AtollPoolDeclaration or a Promise
15
+ * of one. A `name` on the result is ignored — the static `name` field owns
16
+ * the registry slot.
17
+ */
18
+ useFactory: (...args: any[]) => AtollPoolDeclaration | Promise<AtollPoolDeclaration>;
19
+ }
20
+ /** AtollModule.forRootAsync options. */
21
+ export interface AtollModuleAsyncOptions {
22
+ pools?: AtollPoolAsyncDecl[];
23
+ }
24
+ /** AtollModule.registerPoolAsync options — BullModule.registerQueueAsync analog. */
25
+ export interface AtollPoolAsyncOptions {
26
+ /** Registry name — static so `ATOLL_POOL:<name>` stays injectable while the factory resolves. */
27
+ name?: string;
28
+ /**
29
+ * Kept for Nest-parity/documentation: Angular's ModuleWithProviders has no
30
+ * `imports` slot, so this only documents where `inject` deps come from —
31
+ * they resolve through whichever injector imports the module (NgModule
32
+ * providers flatten into the importing injector). List dep-providing
33
+ * modules on the consuming @NgModule's `imports`.
34
+ */
35
+ imports?: Array<Type<unknown> | ModuleWithProviders<unknown> | unknown[]>;
36
+ /** Provider tokens passed to useFactory, in order. */
37
+ inject?: unknown[];
38
+ /** Receives the injected deps; returns a AtollPoolSpec (no `name` — the static field owns it) or a Promise of one. */
39
+ useFactory: (...args: any[]) => AtollPoolSpec | Promise<AtollPoolSpec>;
40
+ }
41
+ /**
42
+ * NgModule form of provideAtoll — the NestJS binding's vocabulary
43
+ * (forRoot / forRootAsync / registerPool / registerPoolAsync) for apps still
44
+ * on NgModule bootstrap:
45
+ *
46
+ * @NgModule({
47
+ * imports: [
48
+ * AtollModule.forRoot(), // or forRoot({ pools: [...] })
49
+ * IncidentsModule, // imports AtollModule.registerPool({ name: 'incidents', client: incidents })
50
+ * ],
51
+ * })
52
+ * export class AppModule {}
53
+ *
54
+ * The standalone provider API — `provideAtoll` + `injectAtollPool` — remains
55
+ * the primary interface (bootstrapApplication providers, route-level pools);
56
+ * AtollModule exists for codebases that organize DI around NgModules. Both
57
+ * paths register the same `ATOLL_POOL:<name>` tokens, spawn eagerly, and
58
+ * terminate pools when the owning injector is destroyed.
59
+ */
60
+ export declare class AtollModule {
61
+ /**
62
+ * Root registration — equivalent providers to `provideAtoll(options,
63
+ * ...features)`, imported once by the root module.
64
+ */
65
+ static forRoot(options?: AtollProvideOptions, ...features: AtollFeature[]): ModuleWithProviders<AtollModule>;
66
+ /**
67
+ * Async root registration — each pool's declaration resolves through an
68
+ * APP_INITIALIZER before bootstrap completes. The `name` field is static
69
+ * so the `ATOLL_POOL:<name>` token stays injectable.
70
+ */
71
+ static forRootAsync(options: AtollModuleAsyncOptions): ModuleWithProviders<AtollModule>;
72
+ /**
73
+ * Registers one pool inside whichever module imports it — the
74
+ * `provideAtoll({ pools: [decl] })` equivalent scoped to the importing
75
+ * NgModule: pool config lives in the feature module that owns the worker.
76
+ */
77
+ static registerPool(decl: AtollPoolDeclaration): ModuleWithProviders<AtollModule>;
78
+ /**
79
+ * Async pool registration — the name is static so `ATOLL_POOL:<name>` stays
80
+ * injectable, while the rest of the spec resolves from injected deps:
81
+ *
82
+ * AtollModule.registerPoolAsync({
83
+ * name: 'incidents',
84
+ * useFactory: (config: PoolConfig) => Promise.resolve({
85
+ * worker: () => new Worker(new URL('./incidents.worker.ts', import.meta.url)),
86
+ * sharedMemory: incidentsMemory,
87
+ * poolSize: config.poolSize,
88
+ * }),
89
+ * inject: [PoolConfig],
90
+ * })
91
+ */
92
+ static registerPoolAsync(options: AtollPoolAsyncOptions): ModuleWithProviders<AtollModule>;
93
+ }
94
+ /**
95
+ * Constructor-parameter form of injectAtollPool — injects the pool registered
96
+ * under `name` into an @Injectable() (or component/directive) constructor:
97
+ *
98
+ * constructor(@InjectAtollPool('incidents') private readonly pool: IncidentsClient) {}
99
+ */
100
+ export declare const InjectAtollPool: (name?: string) => ParameterDecorator;
@@ -0,0 +1,33 @@
1
+ import { type Signal } from '@angular/core';
2
+ import type { AsyncTask, ObservableValue, PathConnector, SharedMemory, SharedSpec, SpecPath, SliceOptions, TaskSnapshot } from '@atolljs/core';
3
+ /**
4
+ * Wrap any sdk ObservableValue in a Signal. Call in an injection context
5
+ * (component field initializer or constructor) so the subscription is
6
+ * released on destroy; without one the subscription lives forever.
7
+ */
8
+ export declare function observableSignal<T>(source: ObservableValue<T>): Signal<T>;
9
+ /**
10
+ * Bind one shared-memory field to a Signal. Stays `undefined` until the
11
+ * contract is bound and the field written; updates on every write.
12
+ */
13
+ export declare function sharedValue<S extends SharedSpec, K extends SpecPath<S>>(memory: SharedMemory<S>, key: K): Signal<(PathConnector<S, K> extends {
14
+ read(): infer T;
15
+ } ? T : never) | undefined>;
16
+ export declare function sharedValue<S extends SharedSpec, K extends SpecPath<S>, Sel>(memory: SharedMemory<S>, key: K, select: (value: PathConnector<S, K> extends {
17
+ read(): infer T;
18
+ } ? T : never) => Sel, options?: SliceOptions<Sel>): Signal<Sel | undefined>;
19
+ /** Bind an AsyncTask or plain async fn (via toTask): `{ state }` is a Signal of its snapshot; `run`/`runOnce` trigger it. */
20
+ export declare function taskState<R>(source: AsyncTask<void, R> | (() => Promise<R>)): {
21
+ state: Signal<TaskSnapshot<R>>;
22
+ run: AsyncTask<void, R>['run'];
23
+ runOnce: AsyncTask<void, R>['runOnce'];
24
+ };
25
+ export declare function taskState<A, R>(source: AsyncTask<A, R> | ((input: A) => Promise<R>)): {
26
+ state: Signal<TaskSnapshot<R>>;
27
+ run: AsyncTask<A, R>['run'];
28
+ runOnce: AsyncTask<A, R>['runOnce'];
29
+ };
30
+ export { provideAtoll, injectAtollPool, getAtollPoolToken } from './provideAtoll.js';
31
+ export type { AtollPoolDeclaration, AtollPoolSpec, AtollProvideOptions, AtollFeature } from './provideAtoll.js';
32
+ export { AtollModule, InjectAtollPool } from './atollModule.js';
33
+ export type { AtollModuleAsyncOptions, AtollPoolAsyncOptions, AtollPoolAsyncDecl } from './atollModule.js';
package/dist/index.js ADDED
@@ -0,0 +1,193 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ // src/index.ts
13
+ import { DestroyRef as DestroyRef3, inject as inject2, signal } from "@angular/core";
14
+ import { observe, toTask } from "@atolljs/core";
15
+
16
+ // src/provideAtoll.ts
17
+ import {
18
+ DestroyRef,
19
+ ENVIRONMENT_INITIALIZER,
20
+ inject,
21
+ InjectionToken,
22
+ makeEnvironmentProviders
23
+ } from "@angular/core";
24
+ import {
25
+ WorkerPool
26
+ } from "@atolljs/core";
27
+ var poolTokens = /* @__PURE__ */ new Map();
28
+ function getAtollPoolToken(name = "default") {
29
+ let token = poolTokens.get(name);
30
+ if (!token) poolTokens.set(name, token = new InjectionToken(`ATOLL_POOL:${name}`));
31
+ return token;
32
+ }
33
+ function injectAtollPool(name = "default") {
34
+ return inject(getAtollPoolToken(name));
35
+ }
36
+ var defaultName = (cfg, i) => cfg.name ?? (i === 0 ? "default" : `pool${i}`);
37
+ function buildAtollPool(cfg) {
38
+ if ("client" in cfg) return cfg.client;
39
+ if ("pool" in cfg) return cfg.pool();
40
+ const { name: _name, worker, sharedMemory, ...rest } = cfg;
41
+ return new WorkerPool({
42
+ ...rest,
43
+ ...sharedMemory ? { sharedMemory } : {},
44
+ createWorker: worker
45
+ });
46
+ }
47
+ function atollProviders(pools, features = []) {
48
+ const named = pools.map((cfg, i) => ({ ...cfg, name: defaultName(cfg, i) }));
49
+ return [
50
+ ...named.map(
51
+ (cfg) => ({
52
+ provide: getAtollPoolToken(cfg.name),
53
+ useFactory: () => buildAtollPool(cfg)
54
+ })
55
+ ),
56
+ {
57
+ provide: ENVIRONMENT_INITIALIZER,
58
+ multi: true,
59
+ useValue: () => {
60
+ const destroyRef = inject(DestroyRef);
61
+ for (const cfg of named) {
62
+ const pool = inject(getAtollPoolToken(cfg.name));
63
+ destroyRef.onDestroy(() => pool.terminate());
64
+ }
65
+ }
66
+ },
67
+ ...features.flatMap((f) => f.providers)
68
+ ];
69
+ }
70
+ function provideAtoll(options, ...features) {
71
+ return makeEnvironmentProviders(atollProviders(options.pools ?? [], features));
72
+ }
73
+
74
+ // src/atollModule.ts
75
+ import {
76
+ APP_INITIALIZER,
77
+ DestroyRef as DestroyRef2,
78
+ Inject,
79
+ InjectionToken as InjectionToken2,
80
+ Injector,
81
+ NgModule
82
+ } from "@angular/core";
83
+ function asyncPoolProviders(name, options) {
84
+ const declHolder = new InjectionToken2(`ATOLL_POOL_DECL:${name}`);
85
+ return [
86
+ { provide: declHolder, useValue: {} },
87
+ {
88
+ provide: getAtollPoolToken(name),
89
+ useFactory: (holder) => {
90
+ if (!holder.decl) {
91
+ throw new Error(
92
+ `Atoll pool "${name}" injected before its async declaration resolved \u2014 bootstrap blocks on APP_INITIALIZER; under TestBed await ApplicationInitStatus.donePromise first.`
93
+ );
94
+ }
95
+ return buildAtollPool({ ...holder.decl, name });
96
+ },
97
+ deps: [declHolder]
98
+ },
99
+ {
100
+ provide: APP_INITIALIZER,
101
+ multi: true,
102
+ useFactory: (holder, injector, ...deps) => {
103
+ const destroyRef = injector.get(DestroyRef2);
104
+ return async () => {
105
+ const resolved = await options.useFactory(...deps);
106
+ holder.decl = { ...resolved, name };
107
+ const pool = injector.get(getAtollPoolToken(name));
108
+ destroyRef.onDestroy(() => pool.terminate());
109
+ };
110
+ },
111
+ deps: [declHolder, Injector, ...options.inject ?? []]
112
+ }
113
+ ];
114
+ }
115
+ var AtollModule = class {
116
+ /**
117
+ * Root registration — equivalent providers to `provideAtoll(options,
118
+ * ...features)`, imported once by the root module.
119
+ */
120
+ static forRoot(options, ...features) {
121
+ return { ngModule: AtollModule, providers: atollProviders(options?.pools ?? [], features) };
122
+ }
123
+ /**
124
+ * Async root registration — each pool's declaration resolves through an
125
+ * APP_INITIALIZER before bootstrap completes. The `name` field is static
126
+ * so the `ATOLL_POOL:<name>` token stays injectable.
127
+ */
128
+ static forRootAsync(options) {
129
+ const providers = (options.pools ?? []).flatMap(
130
+ (decl, i) => asyncPoolProviders(decl.name ?? (i === 0 ? "default" : `pool${i}`), decl)
131
+ );
132
+ return { ngModule: AtollModule, providers };
133
+ }
134
+ /**
135
+ * Registers one pool inside whichever module imports it — the
136
+ * `provideAtoll({ pools: [decl] })` equivalent scoped to the importing
137
+ * NgModule: pool config lives in the feature module that owns the worker.
138
+ */
139
+ static registerPool(decl) {
140
+ return { ngModule: AtollModule, providers: atollProviders([decl]) };
141
+ }
142
+ /**
143
+ * Async pool registration — the name is static so `ATOLL_POOL:<name>` stays
144
+ * injectable, while the rest of the spec resolves from injected deps:
145
+ *
146
+ * AtollModule.registerPoolAsync({
147
+ * name: 'incidents',
148
+ * useFactory: (config: PoolConfig) => Promise.resolve({
149
+ * worker: () => new Worker(new URL('./incidents.worker.ts', import.meta.url)),
150
+ * sharedMemory: incidentsMemory,
151
+ * poolSize: config.poolSize,
152
+ * }),
153
+ * inject: [PoolConfig],
154
+ * })
155
+ */
156
+ static registerPoolAsync(options) {
157
+ return {
158
+ ngModule: AtollModule,
159
+ providers: asyncPoolProviders(options.name ?? "default", options)
160
+ };
161
+ }
162
+ };
163
+ AtollModule = __decorateClass([
164
+ NgModule()
165
+ ], AtollModule);
166
+ var InjectAtollPool = (name = "default") => Inject(getAtollPoolToken(name));
167
+
168
+ // src/index.ts
169
+ function unhook(stop) {
170
+ inject2(DestroyRef3, { optional: true })?.onDestroy(stop);
171
+ }
172
+ function observableSignal(source) {
173
+ const value = signal(source.get());
174
+ unhook(source.subscribe((v) => value.set(v)));
175
+ return value.asReadonly();
176
+ }
177
+ function sharedValue(memory, key, select, options) {
178
+ return observableSignal(observe(memory, key, select, options));
179
+ }
180
+ function taskState(source) {
181
+ const task = toTask(source);
182
+ return { state: observableSignal(task), run: task.run, runOnce: task.runOnce };
183
+ }
184
+ export {
185
+ AtollModule,
186
+ InjectAtollPool,
187
+ getAtollPoolToken,
188
+ injectAtollPool,
189
+ observableSignal,
190
+ provideAtoll,
191
+ sharedValue,
192
+ taskState
193
+ };
@@ -0,0 +1,117 @@
1
+ import { InjectionToken, type EnvironmentProviders, type Provider } from '@angular/core';
2
+ import { WorkerPool, type SharedMemory, type SharedSpec, type TaskMap, type WorkerPoolConfig, type WorkerRunner } from '@atolljs/core';
3
+ /**
4
+ * The lifecycle surface every connectWorker client exposes. Declared
5
+ * structurally rather than as `WorkerClient<any, any>` — instantiating
6
+ * WorkerClient's generics with `any` collapses its method map to an index
7
+ * signature no concrete client satisfies.
8
+ */
9
+ type AnyWorkerClient = {
10
+ start(): void;
11
+ terminate(): void;
12
+ readonly pool: WorkerRunner<any> | null;
13
+ readonly sharedMemory: unknown;
14
+ };
15
+ /**
16
+ * One pool's shape minus its registry name — also the return type of the
17
+ * async pool factories AtollModule.forRootAsync/registerPoolAsync accept.
18
+ * Three forms:
19
+ *
20
+ * // inline: the worker factory keeps the bundler-detectable literal so the
21
+ * // TS source is the reference
22
+ * { worker: () => new Worker(new URL('./x.worker.ts', import.meta.url)), sharedMemory, tasks }
23
+ *
24
+ * // existing: a domain package's already-configured pool factory — the
25
+ * // same instance task helpers resolve, registered for DI + lifecycle
26
+ * { pool: getIncidentsPool }
27
+ *
28
+ * // typed client: a domain package's connectWorker client — registered
29
+ * // for DI + lifecycle (terminate() on destroy; it re-spawns lazily)
30
+ * { client: incidents }
31
+ */
32
+ export type AtollPoolSpec<S extends SharedSpec = SharedSpec, T extends TaskMap = TaskMap> = (Omit<WorkerPoolConfig<S, T>, 'workerUrl' | 'createWorker' | 'sharedMemory'> & {
33
+ /** Spawns one pool worker — usually `() => new Worker(new URL('./x.worker.ts', import.meta.url))`. */
34
+ worker: () => Worker;
35
+ /**
36
+ * The contract this pool shares with its workers. Typed as SharedMemory<S>
37
+ * (not the config field's intersection) so declarations for heterogeneous
38
+ * specs coexist — access fields through the contract const, not the pool.
39
+ * Omit for message-only pools.
40
+ */
41
+ sharedMemory?: SharedMemory<S>;
42
+ }) | {
43
+ /**
44
+ * An already-configured pool — e.g. a domain package's singleton the
45
+ * task helpers dispatch through. Registered for DI + lifecycle; the
46
+ * same instance is what injectAtollPool returns.
47
+ */
48
+ pool: () => WorkerPool<any, any>;
49
+ } | {
50
+ /**
51
+ * An already-built connectWorker client — the provider value is the
52
+ * client itself, so injectAtollPool returns the typed call surface.
53
+ * Terminated on injector destroy; the lazy client re-spawns on the
54
+ * next call.
55
+ */
56
+ client: AnyWorkerClient;
57
+ };
58
+ /**
59
+ * One pool's declaration inside provideAtoll — a named AtollPoolSpec. The name
60
+ * selects the injection token (`ATOLL_POOL:<name>`); the first pool defaults
61
+ * to "default".
62
+ */
63
+ export type AtollPoolDeclaration<S extends SharedSpec = SharedSpec, T extends TaskMap = TaskMap> = {
64
+ name?: string;
65
+ } & AtollPoolSpec<S, T>;
66
+ /** provideAtoll / AtollModule.forRoot options. */
67
+ export interface AtollProvideOptions {
68
+ pools?: AtollPoolDeclaration[];
69
+ }
70
+ /**
71
+ * Composable feature for provideAtoll — the withX pattern. Reserved for
72
+ * cross-cutting additions (logging, timeouts); carries extra providers.
73
+ */
74
+ export interface AtollFeature {
75
+ kind: string;
76
+ providers: Provider[];
77
+ }
78
+ /** InjectionToken for the pool registered under `name` — ATOLL_POOL:<name>. */
79
+ export declare function getAtollPoolToken(name?: string): InjectionToken<WorkerPool>;
80
+ /**
81
+ * Injects the WorkerPool registered under `name` by provideAtoll — call in an
82
+ * injection context (field initializer / constructor / factory). Pass the
83
+ * domain pool type to keep its task methods typed; override the provider in
84
+ * TestBed to stub the pool.
85
+ */
86
+ export declare function injectAtollPool<T = WorkerPool>(name?: string): T;
87
+ type NamedPoolDeclaration = AtollPoolDeclaration & {
88
+ name: string;
89
+ };
90
+ /** @internal Instantiate whatever a declaration describes — client / existing pool / inline config. */
91
+ export declare function buildAtollPool(cfg: NamedPoolDeclaration): WorkerPool;
92
+ /**
93
+ * @internal The provider list both provideAtoll and AtollModule's statics are
94
+ * built from — keeps the declaration-union handling in one place. Appends an
95
+ * ENVIRONMENT_INITIALIZER that eagerly spawns each pool and terminates it
96
+ * when the owning injector is destroyed (app teardown / HMR).
97
+ */
98
+ export declare function atollProviders(pools: AtollPoolDeclaration[], features?: AtollFeature[]): Provider[];
99
+ /**
100
+ * Registers atoll worker pools as Angular providers — the provideHttpClient
101
+ * analog:
102
+ *
103
+ * bootstrapApplication(AppComponent, {
104
+ * providers: [
105
+ * provideAtoll({ pools: [{ name: 'incidents', client: incidents }] }),
106
+ * ],
107
+ * });
108
+ *
109
+ * // anywhere DI runs:
110
+ * private readonly incidents = injectAtollPool<IncidentsClient>('incidents');
111
+ *
112
+ * Pools spawn eagerly and terminate when the environment injector is
113
+ * destroyed (app teardown / HMR). Also usable at route level via
114
+ * `Route.providers` for lazily-scoped pools.
115
+ */
116
+ export declare function provideAtoll(options: AtollProvideOptions, ...features: AtollFeature[]): EnvironmentProviders;
117
+ export {};
package/package.json CHANGED
@@ -1,21 +1,28 @@
1
1
  {
2
2
  "name": "@atolljs/angular",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Angular bindings — observableSignal, sharedValue, taskState",
5
5
  "homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
6
6
  "type": "module",
7
7
  "exports": {
8
- ".": "./src/index.ts"
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
9
12
  },
10
13
  "files": [
11
- "src"
14
+ "src",
15
+ "dist"
12
16
  ],
17
+ "scripts": {
18
+ "build": "node ../../scripts/build-lib.mjs"
19
+ },
13
20
  "publishConfig": {
14
21
  "access": "public",
15
22
  "registry": "https://registry.npmjs.org"
16
23
  },
17
24
  "peerDependencies": {
18
- "@atolljs/core": "0.1.12",
25
+ "@atolljs/core": "^0.1.14",
19
26
  "@angular/core": "^22.2.0"
20
27
  },
21
28
  "repository": {