@reactra/service 0.1.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/index.d.ts +217 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +272 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akhil Shastri and the Reactra contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @reactra/service
|
|
2
|
+
|
|
3
|
+
> Reactra service/DI runtime — dependency injection for Reactra apps.
|
|
4
|
+
|
|
5
|
+
**Alpha** — published under the npm dist-tag `alpha`. APIs may change before 1.0.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @reactra/service@alpha
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Part of [Reactra](https://github.com/akhilshastri/reactra) — a compiler-first,
|
|
12
|
+
React-19-compatible framework. See the [documentation](https://reactra-docs.vercel.app) to get
|
|
13
|
+
started.
|
|
14
|
+
|
|
15
|
+
## License
|
|
16
|
+
|
|
17
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-service runtime instance — the object the factory returns. Method
|
|
3
|
+
* names are the keys; values are plain (async or sync) functions. Services
|
|
4
|
+
* are stateless callable units per Service spec §1, so there is no
|
|
5
|
+
* subscribe/notify/snapshot — they expose methods only.
|
|
6
|
+
*/
|
|
7
|
+
export type ServiceInstance = Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* The shape a compiler-emitted service binding takes: a `{ name, factory }`
|
|
10
|
+
* tuple the user passes to `configureServices`. The factory is invoked
|
|
11
|
+
* eagerly at registration time so service-to-service `inject` lookups
|
|
12
|
+
* resolve in declaration order (the user lists dependencies first in the
|
|
13
|
+
* `configureServices` call).
|
|
14
|
+
*/
|
|
15
|
+
export interface ServiceBinding {
|
|
16
|
+
name: string;
|
|
17
|
+
factory: () => ServiceInstance;
|
|
18
|
+
/**
|
|
19
|
+
* Wave 3 §2b — `export service scoped X { ... }`. When `true`, the
|
|
20
|
+
* binding is REGISTERED at `configureServices` time but NOT
|
|
21
|
+
* instantiated. The instance is created on
|
|
22
|
+
* `ServiceRegistry.activateScopedServices()` (typically wired to
|
|
23
|
+
* `RouterRegistry`'s setLocation via `bindScopedServicesToRouter`) and
|
|
24
|
+
* disposed on the next `deactivateScopedServices` call — invoking
|
|
25
|
+
* `instance.dispose()` if the surface includes a `dispose` method.
|
|
26
|
+
* Absent / `false` for app-singleton bindings (the default).
|
|
27
|
+
*/
|
|
28
|
+
scoped?: boolean;
|
|
29
|
+
}
|
|
30
|
+
declare class ServiceRegistryImpl {
|
|
31
|
+
private instances;
|
|
32
|
+
private scopedFactories;
|
|
33
|
+
/**
|
|
34
|
+
* Register a service binding. App-singleton (`scoped !== true`) is
|
|
35
|
+
* instantiated immediately so downstream services can resolve
|
|
36
|
+
* dependencies via `ServiceRegistry.get` during their own factory
|
|
37
|
+
* call. Scoped bindings (Wave 3 §2b) store the factory only — they
|
|
38
|
+
* spin up on `activateScopedServices()`.
|
|
39
|
+
*/
|
|
40
|
+
register: (binding: ServiceBinding) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Wave 3 §2b — instantiate every scoped service. Idempotent: a
|
|
43
|
+
* second activation without intervening deactivate is a no-op for
|
|
44
|
+
* services that already have a live instance (the user might call
|
|
45
|
+
* this twice on a route re-entry). Called by the router-bound
|
|
46
|
+
* lifecycle hook in `bindScopedServicesToRouter`; safe to call
|
|
47
|
+
* manually in tests.
|
|
48
|
+
*/
|
|
49
|
+
activateScopedServices: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Wave 3 §2b — dispose + drop every scoped service instance. For
|
|
52
|
+
* each scoped instance with a `dispose()` method, invokes it before
|
|
53
|
+
* deletion. Errors thrown by `dispose` are caught + console.error'd
|
|
54
|
+
* so a single misbehaving service can't block the next route's
|
|
55
|
+
* activation. Singleton instances are unaffected.
|
|
56
|
+
*/
|
|
57
|
+
deactivateScopedServices: () => void;
|
|
58
|
+
/**
|
|
59
|
+
* Look up a singleton by name. Throws if the service wasn't registered —
|
|
60
|
+
* usually means the user forgot to add it to `configureServices`, or
|
|
61
|
+
* listed it after a consumer in the same array.
|
|
62
|
+
*/
|
|
63
|
+
get: <T extends ServiceInstance = ServiceInstance>(name: string) => T;
|
|
64
|
+
/** True if a service is registered. Used by tests + future provide wiring. */
|
|
65
|
+
has: (name: string) => boolean;
|
|
66
|
+
/**
|
|
67
|
+
* HMR hot-replace a binding (Compiler §5.3). Compiler-emitted
|
|
68
|
+
* `import.meta.hot.accept(...)` blocks in service-bearing files call
|
|
69
|
+
* this after the module re-evaluates so the new factory takes effect
|
|
70
|
+
* without a full page reload.
|
|
71
|
+
*
|
|
72
|
+
* Services are stateless callable units (Service spec §1) so the
|
|
73
|
+
* replace is just an instance swap. Consumers that cached a reference
|
|
74
|
+
* via `inject X` at component-body top see the stale singleton until
|
|
75
|
+
* their own component re-renders (component-side Fast Refresh in the
|
|
76
|
+
* consumer file, or the next interaction). Phase-1 trade-off; matches
|
|
77
|
+
* the store-replace one-render-delay characteristic.
|
|
78
|
+
*
|
|
79
|
+
* Idempotent on an unknown name (treated as a fresh register).
|
|
80
|
+
*/
|
|
81
|
+
replace: (binding: ServiceBinding) => void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The process-wide service registry singleton. The compiler emits imports
|
|
85
|
+
* of this and `ServiceRegistry.get("X")` at every `inject X` site. User
|
|
86
|
+
* code typically doesn't touch it directly except through `configureServices`.
|
|
87
|
+
*/
|
|
88
|
+
export declare const ServiceRegistry: ServiceRegistryImpl;
|
|
89
|
+
/**
|
|
90
|
+
* Bootstrap call — invoked from `src/main.tsx` per Runtime v0 §5. Registers
|
|
91
|
+
* every service binding the app cares about. Order matters: a service must
|
|
92
|
+
* appear in this array after the services it injects, because each
|
|
93
|
+
* factory call resolves `inject` references eagerly via `ServiceRegistry.get`.
|
|
94
|
+
*/
|
|
95
|
+
export declare const configureServices: (opts: {
|
|
96
|
+
services: ServiceBinding[];
|
|
97
|
+
}) => void;
|
|
98
|
+
/**
|
|
99
|
+
* Compiler-emitted factories call this to build a `ServiceInstance`. The
|
|
100
|
+
* `build` callback returns the surface object — `{ methodName: fn, ... }`.
|
|
101
|
+
* This helper exists for symmetry with `createStoreInstance` and to leave a
|
|
102
|
+
* future hook for Strategy B context-wrapping; today it is a pass-through.
|
|
103
|
+
*
|
|
104
|
+
* Shape used by emitted code (see Pass 9 codegen):
|
|
105
|
+
*
|
|
106
|
+
* createServiceInstance(() => {
|
|
107
|
+
* const authService = ServiceRegistry.get("authService")
|
|
108
|
+
* const get = async (id) => { ... authService.getToken() ... }
|
|
109
|
+
* return { get }
|
|
110
|
+
* })
|
|
111
|
+
*/
|
|
112
|
+
export declare const createServiceInstance: (build: () => ServiceInstance) => ServiceInstance;
|
|
113
|
+
/**
|
|
114
|
+
* The per-subtree map of service overrides. Read by {@link useService};
|
|
115
|
+
* written by the compiler-emitted `<ServiceContext.Provider>` wrap that
|
|
116
|
+
* Pass 9 emits around a component's view when the component has any
|
|
117
|
+
* `provide X with Y` declarations.
|
|
118
|
+
*
|
|
119
|
+
* Plain readonly object instead of just `ReadonlyMap` so future fields
|
|
120
|
+
* (e.g. a debug label) can be added without a breaking shape change.
|
|
121
|
+
*/
|
|
122
|
+
export interface ServiceOverrideMap {
|
|
123
|
+
readonly map: ReadonlyMap<string, unknown>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The empty map used as the default `ServiceContext` value (no overrides
|
|
127
|
+
* — every `useService(name)` falls through to `ServiceRegistry.get`).
|
|
128
|
+
* Exported so tests can compare against it and the codegen can short-
|
|
129
|
+
* circuit composing an empty additions object.
|
|
130
|
+
*/
|
|
131
|
+
export declare const EMPTY_OVERRIDES: ServiceOverrideMap;
|
|
132
|
+
/**
|
|
133
|
+
* React context carrying the service-override map for the current subtree.
|
|
134
|
+
* Initial value is `EMPTY_OVERRIDES` so a component that doesn't sit under
|
|
135
|
+
* any `<ServiceContext.Provider>` still reads via the singleton-fallback
|
|
136
|
+
* path. The context is shared across the whole app — a SINGLE context,
|
|
137
|
+
* not one-per-service, so the runtime stays simple at the cost of a tiny
|
|
138
|
+
* per-render lookup hit (a single `Map.get`).
|
|
139
|
+
*/
|
|
140
|
+
export declare const ServiceContext: import("react").Context<ServiceOverrideMap>;
|
|
141
|
+
/**
|
|
142
|
+
* Pure-logic resolver — used by tests AND by {@link useService}. Looks
|
|
143
|
+
* `name` up in `overrides`; on miss, falls through to `ServiceRegistry`'s
|
|
144
|
+
* singleton. Exported so the inject/provide tests can exercise the
|
|
145
|
+
* fallback chain without standing up a React renderer.
|
|
146
|
+
*/
|
|
147
|
+
export declare const resolveServiceFromOverrides: <T extends ServiceInstance = ServiceInstance>(name: string, overrides: ServiceOverrideMap) => T;
|
|
148
|
+
/**
|
|
149
|
+
* Component-side service read. The compiler emits this in place of
|
|
150
|
+
* `ServiceRegistry.get` for `inject service X` in component bodies, so
|
|
151
|
+
* the call site honours any ancestor `provide X with Y` override.
|
|
152
|
+
*
|
|
153
|
+
* Reads the nearest enclosing `ServiceContext` via `React.use` (React 19),
|
|
154
|
+
* then delegates to {@link resolveServiceFromOverrides}. Service-to-service
|
|
155
|
+
* injections (inside a service body, not a component) continue to use
|
|
156
|
+
* `ServiceRegistry.get` directly — there's no React render context
|
|
157
|
+
* available there to read.
|
|
158
|
+
*/
|
|
159
|
+
export declare const useService: <T extends ServiceInstance = ServiceInstance>(name: string) => T;
|
|
160
|
+
/**
|
|
161
|
+
* Pure helper — layer `additions` on top of `parent`'s map, with the
|
|
162
|
+
* additions WINNING (child `provide` overrides ancestor `provide` for the
|
|
163
|
+
* same name). The compiler wraps the call in `useMemo` so the provider
|
|
164
|
+
* value is stable across re-renders unless the parent map or additions
|
|
165
|
+
* change identity. Pass-9 emits one entry per `provide X with Y` in the
|
|
166
|
+
* component body — `additions = { X: ServiceRegistry.get("Y") }`.
|
|
167
|
+
*
|
|
168
|
+
* Returns a NEW map; never mutates the input. Passing an empty
|
|
169
|
+
* `additions` returns the parent's map identity (avoids spurious context
|
|
170
|
+
* value churn when a component declared `provide` for no service after
|
|
171
|
+
* all — e.g. after a refactor).
|
|
172
|
+
*/
|
|
173
|
+
export declare const composeOverrides: (parent: ServiceOverrideMap, additions: Readonly<Record<string, unknown>>) => ServiceOverrideMap;
|
|
174
|
+
/**
|
|
175
|
+
* Minimal duck-type for `RouterRegistry` — the only method this helper
|
|
176
|
+
* cares about is `subscribe`. Avoids a compile-time dependency on
|
|
177
|
+
* `@reactra/router` (which would invert today's import graph).
|
|
178
|
+
*/
|
|
179
|
+
export interface RouterLike {
|
|
180
|
+
subscribe(onChange: () => void): () => void;
|
|
181
|
+
/**
|
|
182
|
+
* Present on the real `RouterRegistry` (Middleware spec v2 §3): the slot
|
|
183
|
+
* `bindScopedServicesToRouter` installs the middleware `ctx.service`
|
|
184
|
+
* resolver into. Optional so test doubles that only exercise the scoped
|
|
185
|
+
* lifecycle stay valid.
|
|
186
|
+
*/
|
|
187
|
+
__setMiddlewareServiceResolver?(resolver: (name: string) => unknown): void;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Wire scoped services to the router's route-change lifecycle. On the
|
|
191
|
+
* first invocation, activates every registered scoped service (the app
|
|
192
|
+
* just landed on its initial route). On every subsequent route change,
|
|
193
|
+
* deactivates the previous route's instances + activates fresh ones for
|
|
194
|
+
* the new route.
|
|
195
|
+
*
|
|
196
|
+
* Returns an unsubscribe function — call it on app teardown (rarely
|
|
197
|
+
* needed in browser, useful in tests).
|
|
198
|
+
*
|
|
199
|
+
* KNOWN LIMITATION (Stage 3 of Service Strategy B): the router's
|
|
200
|
+
* `subscribe` listeners fire AFTER `setLocation`'s onEnter has already
|
|
201
|
+
* called the new route's stores. So a store's `onEnter` reading a scoped
|
|
202
|
+
* service still sees the PREVIOUS route's instance until the next
|
|
203
|
+
* microtask. Components (which read via useService → React.use →
|
|
204
|
+
* re-render) see the new instance on the very next render. Spec-faithful
|
|
205
|
+
* pre-onEnter scope activation needs a router-side hook the runtime
|
|
206
|
+
* doesn't expose yet — filed as a follow-up.
|
|
207
|
+
*
|
|
208
|
+
* // main.tsx
|
|
209
|
+
* import { RouterRegistry } from "@reactra/router"
|
|
210
|
+
* import { bindScopedServicesToRouter } from "@reactra/service"
|
|
211
|
+
* ...
|
|
212
|
+
* configureRouter({ ... })
|
|
213
|
+
* bindScopedServicesToRouter(RouterRegistry)
|
|
214
|
+
*/
|
|
215
|
+
export declare const bindScopedServicesToRouter: (router: RouterLike) => (() => void);
|
|
216
|
+
export {};
|
|
217
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+BA;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAErD;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,eAAe,CAAA;IAC9B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,cAAM,mBAAmB;IACvB,OAAO,CAAC,SAAS,CAAqC;IAMtD,OAAO,CAAC,eAAe,CAA2C;IAElE;;;;;;OAMG;IACH,QAAQ,GAAI,SAAS,cAAc,KAAG,IAAI,CAgBzC;IAED;;;;;;;OAOG;IACH,sBAAsB,QAAO,IAAI,CAKhC;IAED;;;;;;OAMG;IACH,wBAAwB,QAAO,IAAI,CAmBlC;IAED;;;;OAIG;IACH,GAAG,GAAI,CAAC,SAAS,eAAe,GAAG,eAAe,EAAE,MAAM,MAAM,KAAG,CAAC,CAQnE;IAED,8EAA8E;IAC9E,GAAG,GAAI,MAAM,MAAM,KAAG,OAAO,CAA4B;IAEzD;;;;;;;;;;;;;;OAcG;IACH,OAAO,GAAI,SAAS,cAAc,KAAG,IAAI,CAExC;CACF;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,qBAA4B,CAAA;AAExD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM;IAAE,QAAQ,EAAE,cAAc,EAAE,CAAA;CAAE,KAAG,IAExE,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,MAAM,eAAe,KAAG,eAA0B,CAAA;AAM/F;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC3C;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,kBAAuC,CAAA;AAErE;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,6CAAqD,CAAA;AAEhF;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,GAAI,CAAC,SAAS,eAAe,GAAG,eAAe,EACrF,MAAM,MAAM,EACZ,WAAW,kBAAkB,KAC5B,CAIF,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,eAAe,GAAG,eAAe,EACpE,MAAM,MAAM,KACX,CAA8D,CAAA;AAEjE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,kBAAkB,EAC1B,WAAW,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAC3C,kBAMF,CAAA;AAID;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAC3C;;;;;OAKG;IACH,8BAA8B,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAA;CAC3E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,UAAU,KAAG,CAAC,MAAM,IAAI,CAgB1E,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// @reactra/service — Strategy A singletons + Strategy B `provide` runtime
|
|
2
|
+
//
|
|
3
|
+
// Owner: reactra-service-spec.md (Spec 2.0 / Discussion v2)
|
|
4
|
+
//
|
|
5
|
+
// What's shipped:
|
|
6
|
+
// ServiceRegistry: register / get / has / replace (HMR)
|
|
7
|
+
// configureServices({ services })
|
|
8
|
+
// createServiceInstance — helper consumed by compiler-emitted factories
|
|
9
|
+
//
|
|
10
|
+
// Strategy B runtime (Wave 3 §2b — this commit):
|
|
11
|
+
// ServiceContext — React context carrying the override map for the
|
|
12
|
+
// current subtree.
|
|
13
|
+
// useService<T>(name) — read a service from the nearest enclosing
|
|
14
|
+
// override map; falls back to ServiceRegistry's
|
|
15
|
+
// singleton when not overridden. The compiler
|
|
16
|
+
// emits this in place of `ServiceRegistry.get`
|
|
17
|
+
// for component-side `inject service X` once the
|
|
18
|
+
// Pass-9 changes (next commit) land.
|
|
19
|
+
// composeOverrides — pure helper Pass-9 wraps in `useMemo` to build
|
|
20
|
+
// the value the component's <ServiceContext.Provider>
|
|
21
|
+
// passes down. Layers `additions` on top of
|
|
22
|
+
// `parent` so a child `provide` overrides the
|
|
23
|
+
// ancestor for the same name.
|
|
24
|
+
//
|
|
25
|
+
// Still deferred to subsequent commits:
|
|
26
|
+
// Pass-1.1 `provide X with Y` preprocessor + AST + Pass 5c + Pass 9 emit
|
|
27
|
+
// `service scoped` route-co-located lifecycle (depends on router glue)
|
|
28
|
+
// `service server` server-action emission (React 19 "use server")
|
|
29
|
+
// AbortSignal auto-injection from resources (Service spec §8.4)
|
|
30
|
+
// Dead-code elimination of unused methods (Pass 6b)
|
|
31
|
+
class ServiceRegistryImpl {
|
|
32
|
+
instances = new Map();
|
|
33
|
+
// Wave 3 §2b — scoped service factories registered but NOT instantiated
|
|
34
|
+
// at `configureServices` time. `activateScopedServices()` instantiates
|
|
35
|
+
// them on route enter; `deactivateScopedServices()` disposes them on
|
|
36
|
+
// exit. A separate map keeps the singleton/scoped registries clean —
|
|
37
|
+
// the `get` path still consults `instances` first either way.
|
|
38
|
+
scopedFactories = new Map();
|
|
39
|
+
/**
|
|
40
|
+
* Register a service binding. App-singleton (`scoped !== true`) is
|
|
41
|
+
* instantiated immediately so downstream services can resolve
|
|
42
|
+
* dependencies via `ServiceRegistry.get` during their own factory
|
|
43
|
+
* call. Scoped bindings (Wave 3 §2b) store the factory only — they
|
|
44
|
+
* spin up on `activateScopedServices()`.
|
|
45
|
+
*/
|
|
46
|
+
register = (binding) => {
|
|
47
|
+
if (binding.scoped === true) {
|
|
48
|
+
if (this.scopedFactories.has(binding.name) || this.instances.has(binding.name)) {
|
|
49
|
+
throw new Error(`[reactra:service] SVC001: service "${binding.name}" is already registered`);
|
|
50
|
+
}
|
|
51
|
+
this.scopedFactories.set(binding.name, binding.factory);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (this.instances.has(binding.name)) {
|
|
55
|
+
throw new Error(`[reactra:service] SVC001: service "${binding.name}" is already registered`);
|
|
56
|
+
}
|
|
57
|
+
this.instances.set(binding.name, binding.factory());
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Wave 3 §2b — instantiate every scoped service. Idempotent: a
|
|
61
|
+
* second activation without intervening deactivate is a no-op for
|
|
62
|
+
* services that already have a live instance (the user might call
|
|
63
|
+
* this twice on a route re-entry). Called by the router-bound
|
|
64
|
+
* lifecycle hook in `bindScopedServicesToRouter`; safe to call
|
|
65
|
+
* manually in tests.
|
|
66
|
+
*/
|
|
67
|
+
activateScopedServices = () => {
|
|
68
|
+
for (const [name, factory] of this.scopedFactories) {
|
|
69
|
+
if (this.instances.has(name))
|
|
70
|
+
continue;
|
|
71
|
+
this.instances.set(name, factory());
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Wave 3 §2b — dispose + drop every scoped service instance. For
|
|
76
|
+
* each scoped instance with a `dispose()` method, invokes it before
|
|
77
|
+
* deletion. Errors thrown by `dispose` are caught + console.error'd
|
|
78
|
+
* so a single misbehaving service can't block the next route's
|
|
79
|
+
* activation. Singleton instances are unaffected.
|
|
80
|
+
*/
|
|
81
|
+
deactivateScopedServices = () => {
|
|
82
|
+
for (const name of this.scopedFactories.keys()) {
|
|
83
|
+
const inst = this.instances.get(name);
|
|
84
|
+
if (!inst)
|
|
85
|
+
continue;
|
|
86
|
+
const disposer = inst.dispose;
|
|
87
|
+
if (typeof disposer === "function") {
|
|
88
|
+
try {
|
|
89
|
+
;
|
|
90
|
+
disposer.call(inst);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (typeof console !== "undefined") {
|
|
94
|
+
console.error(`[reactra:service] scoped service "${name}".dispose threw — continuing teardown`, err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this.instances.delete(name);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Look up a singleton by name. Throws if the service wasn't registered —
|
|
103
|
+
* usually means the user forgot to add it to `configureServices`, or
|
|
104
|
+
* listed it after a consumer in the same array.
|
|
105
|
+
*/
|
|
106
|
+
get = (name) => {
|
|
107
|
+
const inst = this.instances.get(name);
|
|
108
|
+
if (!inst) {
|
|
109
|
+
throw new Error(`[reactra:service] service "${name}" was used before configureServices() registered it`);
|
|
110
|
+
}
|
|
111
|
+
return inst;
|
|
112
|
+
};
|
|
113
|
+
/** True if a service is registered. Used by tests + future provide wiring. */
|
|
114
|
+
has = (name) => this.instances.has(name);
|
|
115
|
+
/**
|
|
116
|
+
* HMR hot-replace a binding (Compiler §5.3). Compiler-emitted
|
|
117
|
+
* `import.meta.hot.accept(...)` blocks in service-bearing files call
|
|
118
|
+
* this after the module re-evaluates so the new factory takes effect
|
|
119
|
+
* without a full page reload.
|
|
120
|
+
*
|
|
121
|
+
* Services are stateless callable units (Service spec §1) so the
|
|
122
|
+
* replace is just an instance swap. Consumers that cached a reference
|
|
123
|
+
* via `inject X` at component-body top see the stale singleton until
|
|
124
|
+
* their own component re-renders (component-side Fast Refresh in the
|
|
125
|
+
* consumer file, or the next interaction). Phase-1 trade-off; matches
|
|
126
|
+
* the store-replace one-render-delay characteristic.
|
|
127
|
+
*
|
|
128
|
+
* Idempotent on an unknown name (treated as a fresh register).
|
|
129
|
+
*/
|
|
130
|
+
replace = (binding) => {
|
|
131
|
+
this.instances.set(binding.name, binding.factory());
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The process-wide service registry singleton. The compiler emits imports
|
|
136
|
+
* of this and `ServiceRegistry.get("X")` at every `inject X` site. User
|
|
137
|
+
* code typically doesn't touch it directly except through `configureServices`.
|
|
138
|
+
*/
|
|
139
|
+
export const ServiceRegistry = new ServiceRegistryImpl();
|
|
140
|
+
/**
|
|
141
|
+
* Bootstrap call — invoked from `src/main.tsx` per Runtime v0 §5. Registers
|
|
142
|
+
* every service binding the app cares about. Order matters: a service must
|
|
143
|
+
* appear in this array after the services it injects, because each
|
|
144
|
+
* factory call resolves `inject` references eagerly via `ServiceRegistry.get`.
|
|
145
|
+
*/
|
|
146
|
+
export const configureServices = (opts) => {
|
|
147
|
+
for (const binding of opts.services)
|
|
148
|
+
ServiceRegistry.register(binding);
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Compiler-emitted factories call this to build a `ServiceInstance`. The
|
|
152
|
+
* `build` callback returns the surface object — `{ methodName: fn, ... }`.
|
|
153
|
+
* This helper exists for symmetry with `createStoreInstance` and to leave a
|
|
154
|
+
* future hook for Strategy B context-wrapping; today it is a pass-through.
|
|
155
|
+
*
|
|
156
|
+
* Shape used by emitted code (see Pass 9 codegen):
|
|
157
|
+
*
|
|
158
|
+
* createServiceInstance(() => {
|
|
159
|
+
* const authService = ServiceRegistry.get("authService")
|
|
160
|
+
* const get = async (id) => { ... authService.getToken() ... }
|
|
161
|
+
* return { get }
|
|
162
|
+
* })
|
|
163
|
+
*/
|
|
164
|
+
export const createServiceInstance = (build) => build();
|
|
165
|
+
// ─── Strategy B — `provide` runtime ────────────────────────────────────────
|
|
166
|
+
import { createContext, use } from "react";
|
|
167
|
+
/**
|
|
168
|
+
* The empty map used as the default `ServiceContext` value (no overrides
|
|
169
|
+
* — every `useService(name)` falls through to `ServiceRegistry.get`).
|
|
170
|
+
* Exported so tests can compare against it and the codegen can short-
|
|
171
|
+
* circuit composing an empty additions object.
|
|
172
|
+
*/
|
|
173
|
+
export const EMPTY_OVERRIDES = { map: new Map() };
|
|
174
|
+
/**
|
|
175
|
+
* React context carrying the service-override map for the current subtree.
|
|
176
|
+
* Initial value is `EMPTY_OVERRIDES` so a component that doesn't sit under
|
|
177
|
+
* any `<ServiceContext.Provider>` still reads via the singleton-fallback
|
|
178
|
+
* path. The context is shared across the whole app — a SINGLE context,
|
|
179
|
+
* not one-per-service, so the runtime stays simple at the cost of a tiny
|
|
180
|
+
* per-render lookup hit (a single `Map.get`).
|
|
181
|
+
*/
|
|
182
|
+
export const ServiceContext = createContext(EMPTY_OVERRIDES);
|
|
183
|
+
/**
|
|
184
|
+
* Pure-logic resolver — used by tests AND by {@link useService}. Looks
|
|
185
|
+
* `name` up in `overrides`; on miss, falls through to `ServiceRegistry`'s
|
|
186
|
+
* singleton. Exported so the inject/provide tests can exercise the
|
|
187
|
+
* fallback chain without standing up a React renderer.
|
|
188
|
+
*/
|
|
189
|
+
export const resolveServiceFromOverrides = (name, overrides) => {
|
|
190
|
+
const override = overrides.map.get(name);
|
|
191
|
+
if (override !== undefined)
|
|
192
|
+
return override;
|
|
193
|
+
return ServiceRegistry.get(name);
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Component-side service read. The compiler emits this in place of
|
|
197
|
+
* `ServiceRegistry.get` for `inject service X` in component bodies, so
|
|
198
|
+
* the call site honours any ancestor `provide X with Y` override.
|
|
199
|
+
*
|
|
200
|
+
* Reads the nearest enclosing `ServiceContext` via `React.use` (React 19),
|
|
201
|
+
* then delegates to {@link resolveServiceFromOverrides}. Service-to-service
|
|
202
|
+
* injections (inside a service body, not a component) continue to use
|
|
203
|
+
* `ServiceRegistry.get` directly — there's no React render context
|
|
204
|
+
* available there to read.
|
|
205
|
+
*/
|
|
206
|
+
export const useService = (name) => resolveServiceFromOverrides(name, use(ServiceContext));
|
|
207
|
+
/**
|
|
208
|
+
* Pure helper — layer `additions` on top of `parent`'s map, with the
|
|
209
|
+
* additions WINNING (child `provide` overrides ancestor `provide` for the
|
|
210
|
+
* same name). The compiler wraps the call in `useMemo` so the provider
|
|
211
|
+
* value is stable across re-renders unless the parent map or additions
|
|
212
|
+
* change identity. Pass-9 emits one entry per `provide X with Y` in the
|
|
213
|
+
* component body — `additions = { X: ServiceRegistry.get("Y") }`.
|
|
214
|
+
*
|
|
215
|
+
* Returns a NEW map; never mutates the input. Passing an empty
|
|
216
|
+
* `additions` returns the parent's map identity (avoids spurious context
|
|
217
|
+
* value churn when a component declared `provide` for no service after
|
|
218
|
+
* all — e.g. after a refactor).
|
|
219
|
+
*/
|
|
220
|
+
export const composeOverrides = (parent, additions) => {
|
|
221
|
+
const keys = Object.keys(additions);
|
|
222
|
+
if (keys.length === 0)
|
|
223
|
+
return parent;
|
|
224
|
+
const merged = new Map(parent.map);
|
|
225
|
+
for (const k of keys)
|
|
226
|
+
merged.set(k, additions[k]);
|
|
227
|
+
return { map: merged };
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Wire scoped services to the router's route-change lifecycle. On the
|
|
231
|
+
* first invocation, activates every registered scoped service (the app
|
|
232
|
+
* just landed on its initial route). On every subsequent route change,
|
|
233
|
+
* deactivates the previous route's instances + activates fresh ones for
|
|
234
|
+
* the new route.
|
|
235
|
+
*
|
|
236
|
+
* Returns an unsubscribe function — call it on app teardown (rarely
|
|
237
|
+
* needed in browser, useful in tests).
|
|
238
|
+
*
|
|
239
|
+
* KNOWN LIMITATION (Stage 3 of Service Strategy B): the router's
|
|
240
|
+
* `subscribe` listeners fire AFTER `setLocation`'s onEnter has already
|
|
241
|
+
* called the new route's stores. So a store's `onEnter` reading a scoped
|
|
242
|
+
* service still sees the PREVIOUS route's instance until the next
|
|
243
|
+
* microtask. Components (which read via useService → React.use →
|
|
244
|
+
* re-render) see the new instance on the very next render. Spec-faithful
|
|
245
|
+
* pre-onEnter scope activation needs a router-side hook the runtime
|
|
246
|
+
* doesn't expose yet — filed as a follow-up.
|
|
247
|
+
*
|
|
248
|
+
* // main.tsx
|
|
249
|
+
* import { RouterRegistry } from "@reactra/router"
|
|
250
|
+
* import { bindScopedServicesToRouter } from "@reactra/service"
|
|
251
|
+
* ...
|
|
252
|
+
* configureRouter({ ... })
|
|
253
|
+
* bindScopedServicesToRouter(RouterRegistry)
|
|
254
|
+
*/
|
|
255
|
+
export const bindScopedServicesToRouter = (router) => {
|
|
256
|
+
// Service-aware middleware (Middleware spec v2 §3): the same explicit
|
|
257
|
+
// bootstrap line also installs the `ctx.service` resolver — app singletons
|
|
258
|
+
// + currently-activated scoped instances, via the registry's own lookup
|
|
259
|
+
// (its SVC diagnostics propagate unchanged).
|
|
260
|
+
router.__setMiddlewareServiceResolver?.((name) => ServiceRegistry.get(name));
|
|
261
|
+
let activated = false;
|
|
262
|
+
return router.subscribe(() => {
|
|
263
|
+
if (!activated) {
|
|
264
|
+
ServiceRegistry.activateScopedServices();
|
|
265
|
+
activated = true;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
ServiceRegistry.deactivateScopedServices();
|
|
269
|
+
ServiceRegistry.activateScopedServices();
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reactra/service",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"react": "^19.2.0"
|
|
14
|
+
},
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"tag": "alpha",
|
|
22
|
+
"provenance": false
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/akhilshastri/reactra.git",
|
|
27
|
+
"directory": "packages/service"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://reactra-docs.vercel.app",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22.18"
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"description": "Reactra service/DI runtime — dependency injection for Reactra apps."
|
|
36
|
+
}
|