@doweljs/core 0.0.0-stage → 0.1.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 (178) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +258 -2
  3. package/dist/api.d.ts +145 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +3 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/compile/compile.d.ts +27 -0
  8. package/dist/compile/compile.d.ts.map +1 -0
  9. package/dist/compile/compile.js +395 -0
  10. package/dist/compile/compile.js.map +1 -0
  11. package/dist/compile/plan.d.ts +117 -0
  12. package/dist/compile/plan.d.ts.map +1 -0
  13. package/dist/compile/plan.js +19 -0
  14. package/dist/compile/plan.js.map +1 -0
  15. package/dist/container.d.ts +10 -0
  16. package/dist/container.d.ts.map +1 -0
  17. package/dist/container.js +109 -0
  18. package/dist/container.js.map +1 -0
  19. package/dist/declare/bind.d.ts +15 -0
  20. package/dist/declare/bind.d.ts.map +1 -0
  21. package/dist/declare/bind.js +21 -0
  22. package/dist/declare/bind.js.map +1 -0
  23. package/dist/declare/execution.d.ts +74 -0
  24. package/dist/declare/execution.d.ts.map +1 -0
  25. package/dist/declare/execution.js +56 -0
  26. package/dist/declare/execution.js.map +1 -0
  27. package/dist/declare/scope.d.ts +35 -0
  28. package/dist/declare/scope.d.ts.map +1 -0
  29. package/dist/declare/scope.js +18 -0
  30. package/dist/declare/scope.js.map +1 -0
  31. package/dist/declare/service.d.ts +34 -0
  32. package/dist/declare/service.d.ts.map +1 -0
  33. package/dist/declare/service.js +50 -0
  34. package/dist/declare/service.js.map +1 -0
  35. package/dist/declare/slot.d.ts +67 -0
  36. package/dist/declare/slot.d.ts.map +1 -0
  37. package/dist/declare/slot.js +19 -0
  38. package/dist/declare/slot.js.map +1 -0
  39. package/dist/errors.d.ts +60 -0
  40. package/dist/errors.d.ts.map +1 -0
  41. package/dist/errors.js +51 -0
  42. package/dist/errors.js.map +1 -0
  43. package/dist/hot/registry.d.ts +43 -0
  44. package/dist/hot/registry.d.ts.map +1 -0
  45. package/dist/hot/registry.js +91 -0
  46. package/dist/hot/registry.js.map +1 -0
  47. package/dist/hot/replace.d.ts +25 -0
  48. package/dist/hot/replace.d.ts.map +1 -0
  49. package/dist/hot/replace.js +122 -0
  50. package/dist/hot/replace.js.map +1 -0
  51. package/dist/hot/updates.d.ts +42 -0
  52. package/dist/hot/updates.d.ts.map +1 -0
  53. package/dist/hot/updates.js +136 -0
  54. package/dist/hot/updates.js.map +1 -0
  55. package/dist/hot.d.ts +37 -0
  56. package/dist/hot.d.ts.map +1 -0
  57. package/dist/hot.js +44 -0
  58. package/dist/hot.js.map +1 -0
  59. package/dist/index.d.ts +16 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +8 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/inferred.d.ts +12 -0
  64. package/dist/inferred.d.ts.map +1 -0
  65. package/dist/inferred.js +7 -0
  66. package/dist/inferred.js.map +1 -0
  67. package/dist/model/checks.d.ts +25 -0
  68. package/dist/model/checks.d.ts.map +1 -0
  69. package/dist/model/checks.js +7 -0
  70. package/dist/model/checks.js.map +1 -0
  71. package/dist/model/declarations.d.ts +117 -0
  72. package/dist/model/declarations.d.ts.map +1 -0
  73. package/dist/model/declarations.js +23 -0
  74. package/dist/model/declarations.js.map +1 -0
  75. package/dist/model/diagnostics.d.ts +114 -0
  76. package/dist/model/diagnostics.d.ts.map +1 -0
  77. package/dist/model/diagnostics.js +7 -0
  78. package/dist/model/diagnostics.js.map +1 -0
  79. package/dist/model/graph.d.ts +102 -0
  80. package/dist/model/graph.d.ts.map +1 -0
  81. package/dist/model/graph.js +10 -0
  82. package/dist/model/graph.js.map +1 -0
  83. package/dist/model/infer.d.ts +87 -0
  84. package/dist/model/infer.d.ts.map +1 -0
  85. package/dist/model/infer.js +6 -0
  86. package/dist/model/infer.js.map +1 -0
  87. package/dist/model/scopes.d.ts +39 -0
  88. package/dist/model/scopes.d.ts.map +1 -0
  89. package/dist/model/scopes.js +7 -0
  90. package/dist/model/scopes.js.map +1 -0
  91. package/dist/model/state.d.ts +172 -0
  92. package/dist/model/state.d.ts.map +1 -0
  93. package/dist/model/state.js +7 -0
  94. package/dist/model/state.js.map +1 -0
  95. package/dist/model/util.d.ts +12 -0
  96. package/dist/model/util.d.ts.map +1 -0
  97. package/dist/model/util.js +3 -0
  98. package/dist/model/util.js.map +1 -0
  99. package/dist/nodes/coerce.d.ts +25 -0
  100. package/dist/nodes/coerce.d.ts.map +1 -0
  101. package/dist/nodes/coerce.js +62 -0
  102. package/dist/nodes/coerce.js.map +1 -0
  103. package/dist/nodes/lineage.d.ts +35 -0
  104. package/dist/nodes/lineage.d.ts.map +1 -0
  105. package/dist/nodes/lineage.js +69 -0
  106. package/dist/nodes/lineage.js.map +1 -0
  107. package/dist/nodes/node.d.ts +79 -0
  108. package/dist/nodes/node.d.ts.map +1 -0
  109. package/dist/nodes/node.js +46 -0
  110. package/dist/nodes/node.js.map +1 -0
  111. package/dist/runtime/containers.d.ts +42 -0
  112. package/dist/runtime/containers.d.ts.map +1 -0
  113. package/dist/runtime/containers.js +150 -0
  114. package/dist/runtime/containers.js.map +1 -0
  115. package/dist/runtime/context.d.ts +8 -0
  116. package/dist/runtime/context.d.ts.map +1 -0
  117. package/dist/runtime/context.js +62 -0
  118. package/dist/runtime/context.js.map +1 -0
  119. package/dist/runtime/dispose.d.ts +36 -0
  120. package/dist/runtime/dispose.d.ts.map +1 -0
  121. package/dist/runtime/dispose.js +128 -0
  122. package/dist/runtime/dispose.js.map +1 -0
  123. package/dist/runtime/hooks.d.ts +61 -0
  124. package/dist/runtime/hooks.d.ts.map +1 -0
  125. package/dist/runtime/hooks.js +50 -0
  126. package/dist/runtime/hooks.js.map +1 -0
  127. package/dist/runtime/inspect.d.ts +6 -0
  128. package/dist/runtime/inspect.d.ts.map +1 -0
  129. package/dist/runtime/inspect.js +45 -0
  130. package/dist/runtime/inspect.js.map +1 -0
  131. package/dist/runtime/resolve.d.ts +31 -0
  132. package/dist/runtime/resolve.d.ts.map +1 -0
  133. package/dist/runtime/resolve.js +318 -0
  134. package/dist/runtime/resolve.js.map +1 -0
  135. package/dist/runtime/store.d.ts +61 -0
  136. package/dist/runtime/store.d.ts.map +1 -0
  137. package/dist/runtime/store.js +30 -0
  138. package/dist/runtime/store.js.map +1 -0
  139. package/dist/runtime/supply.d.ts +6 -0
  140. package/dist/runtime/supply.d.ts.map +1 -0
  141. package/dist/runtime/supply.js +35 -0
  142. package/dist/runtime/supply.js.map +1 -0
  143. package/package.json +40 -5
  144. package/src/api.ts +209 -0
  145. package/src/compile/compile.ts +519 -0
  146. package/src/compile/plan.ts +134 -0
  147. package/src/container.ts +140 -0
  148. package/src/declare/bind.ts +29 -0
  149. package/src/declare/execution.ts +175 -0
  150. package/src/declare/scope.ts +61 -0
  151. package/src/declare/service.ts +115 -0
  152. package/src/declare/slot.ts +96 -0
  153. package/src/errors.ts +100 -0
  154. package/src/hot/registry.ts +119 -0
  155. package/src/hot/replace.ts +139 -0
  156. package/src/hot/updates.ts +193 -0
  157. package/src/hot.ts +47 -0
  158. package/src/index.ts +22 -0
  159. package/src/inferred.ts +12 -0
  160. package/src/model/checks.ts +28 -0
  161. package/src/model/declarations.ts +132 -0
  162. package/src/model/diagnostics.ts +135 -0
  163. package/src/model/graph.ts +190 -0
  164. package/src/model/infer.ts +140 -0
  165. package/src/model/scopes.ts +58 -0
  166. package/src/model/state.ts +355 -0
  167. package/src/model/util.ts +22 -0
  168. package/src/nodes/coerce.ts +106 -0
  169. package/src/nodes/lineage.ts +76 -0
  170. package/src/nodes/node.ts +120 -0
  171. package/src/runtime/containers.ts +189 -0
  172. package/src/runtime/context.ts +75 -0
  173. package/src/runtime/dispose.ts +138 -0
  174. package/src/runtime/hooks.ts +97 -0
  175. package/src/runtime/inspect.ts +43 -0
  176. package/src/runtime/resolve.ts +400 -0
  177. package/src/runtime/store.ts +91 -0
  178. package/src/runtime/supply.ts +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jack Weilage
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 CHANGED
@@ -1,3 +1,259 @@
1
- # Temporary Holding Version
1
+ # @doweljs/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Type-complete dependency injection for TypeScript.
4
+
5
+ - **The compiler checks completeness.** A container can't be built while a slot it needs is unbound, can't be opened without its scope's values, and can't resolve a node it can't see. These are type errors that name what is missing and who needs it.
6
+ - **Async only at the edges.** `build()` and `open()` return promises; `get()` never does. Independent async services start concurrently, and a failed build disposes what it already created.
7
+ - **Scope is inferred.** Depend on a request-scoped slot and your service lives per request too. Capturing a shorter-lived value in a longer-lived service is a compile error.
8
+ - **Ownership is explicit.** A service disposes exactly the resources it adopts, and a resource can't have two owners.
9
+ - **Child work is injectable.** An _execution_ hands a service a typed runner for jobs and message handlers, each run in its own short-lived container.
10
+ - **No decorators, reflection or proxies.** Plain values, about 9 KB minified and gzipped, and a `get()` of about 40–50 ns.
11
+ - **Hot replaceable.** With [`@doweljs/vite`](../vite), editing a service swaps it inside running containers.
12
+
13
+ ```ts
14
+ import { container, service, slot } from '@doweljs/core'
15
+
16
+ const Logger = slot('Logger')<{ info(message: string): void }>()
17
+
18
+ const Config = service('Config', () => ({ dbUrl: process.env.DATABASE_URL! }))
19
+ const Db = service('Db', { config: Config }, async ({ config }, ctx) =>
20
+ ctx.adopt(await connect(config.dbUrl), (client) => client.close()),
21
+ )
22
+ const Users = service('Users', { db: Db, log: Logger }, ({ db, log }) => ({
23
+ list: () => {
24
+ log.info('listing users')
25
+ return db.query('select * from users')
26
+ },
27
+ }))
28
+
29
+ const app = await container().provide(Users).bind(Logger, { info: console.log }).build()
30
+
31
+ app.get(Users).list()
32
+ await app.dispose()
33
+ ```
34
+
35
+ Remove `.bind(Logger, …)` and `build()` stops being callable. Its type becomes `MissingBindings<{ Logger: "Users" }>`: the missing slot, and what needs it.
36
+
37
+ ## Concepts
38
+
39
+ ### Slots
40
+
41
+ A slot is a named gap the container fills: a name and a type, with no implementation. Slots are the only open points in a graph; everything else is wired automatically.
42
+
43
+ ```ts
44
+ // Bound once per container.
45
+ const Logger = slot('Logger')<LoggerApi>()
46
+
47
+ // Supplied each time a container for PerRequest opens.
48
+ const PerRequest = scope('request')
49
+ const Request = slot('Request', { scope: PerRequest })<IncomingRequest>()
50
+
51
+ // Collects contributions into a readonly array.
52
+ const Middleware = slot('Middleware', { many: true })<Middleware>()
53
+ ```
54
+
55
+ Names must be unique within a container. The type system and hot replacement both identify nodes by name.
56
+
57
+ ### Services
58
+
59
+ A service lists its dependencies as values. Its requirements, its scope and whether it is async are all inferred from them.
60
+
61
+ ```ts
62
+ const Users = service(
63
+ 'Users',
64
+ { db: Db, log: Logger, metrics: optional(Metrics) },
65
+ ({ db, log, metrics }, ctx) => {
66
+ ctx.defer(() => log.info('Users disposed'))
67
+ return {
68
+ list() {
69
+ metrics?.increment('users.list')
70
+ return db.query('select * from users')
71
+ },
72
+ }
73
+ },
74
+ )
75
+ ```
76
+
77
+ The service's type carries its whole subgraph. Hover `Users` to see the record the compiler reasons about:
78
+
79
+ ```ts
80
+ Service<UsersApi, {
81
+ name: 'Users'; scope: 'root'; above: never
82
+ graph: {
83
+ Users: { kind: 'service'; scope: 'root'; deps: 'Db' | 'Logger' | 'Metrics'; optional: 'Metrics'; … }
84
+ Db: { kind: 'service'; scope: 'root'; deps: 'Config'; … }
85
+ Config: { kind: 'service'; scope: 'root'; deps: never; … }
86
+ Logger: { kind: 'slot'; scope: 'root'; … }
87
+ Metrics: { kind: 'slot'; scope: 'root'; … }
88
+ }
89
+ }>
90
+ ```
91
+
92
+ A service has one instance per container of its scope: one in the app's container for root-scope services, one in each opened container for scoped ones. There are no other lifetimes.
93
+
94
+ The only option is `scope`. It places a service deeper than its dependencies imply, such as one unit of work per request although it only needs the database. Placing it shallower is a compile error.
95
+
96
+ #### Resources and disposal
97
+
98
+ The factory's context mirrors `AsyncDisposableStack`:
99
+
100
+ - `ctx.use(resource)` adopts a `Disposable` or `AsyncDisposable` and returns it.
101
+ - `ctx.adopt(value, dispose)` adopts anything else, with the function that disposes it.
102
+ - `ctx.defer(fn)` runs a callback at disposal.
103
+ - `ctx.signal` aborts when the instance's disposal starts, even if its factory is still running. Pass it to slow startup work: `connect(url, { signal: ctx.signal })`.
104
+
105
+ Only what a factory adopts is disposed. Returning a disposable value doesn't hand it to the container, and adopting a value another service already owns is an error.
106
+
107
+ Disposal runs last-in-first-out, inner scopes before outer ones, one scope at a time. It waits for factories still running, so their instances are disposed before what they depend on. A factory that ignores its signal and never settles keeps disposal waiting.
108
+
109
+ ### Containers
110
+
111
+ ```ts
112
+ const app = await container()
113
+ .provide(Users, Mailer) // services; their dependencies are discovered
114
+ .bind(Logger, PinoLogger) // a slot to a service…
115
+ .bind(Config, { port: 3000 }) // …or to a value
116
+ .contribute(Middleware, Cors) // one item of a multi-binding
117
+ .with(infrastructure) // apply a composition function
118
+ .override(Db, fakeDb) // replace a node in the graph (tests)
119
+ .build()
120
+ ```
121
+
122
+ What you `provide()` are the container's roots. Everything they reach is created at `build()`, and nothing else is. A binding alone activates nothing: `bind(Cache, RedisCache)` only takes effect once something reachable depends on `Cache`, or `Cache` itself is provided.
123
+
124
+ If a factory fails, `build()` disposes what it already created, then rejects. Failures during that cleanup are attached to the original error as an `AggregateError`.
125
+
126
+ Builders are persistent: every call returns a new builder, so a base composition can be shared between tests or environments. Write reusable composition functions generic in the state, so they apply anywhere in a chain:
127
+
128
+ ```ts
129
+ const infrastructure = <S extends ContainerState>(b: ContainerBuilder<S>) =>
130
+ b.bind(Logger, ConsoleLogger).bind(Database, SqlDatabase)
131
+
132
+ container().with(infrastructure).bind(Config, config).provide(Checkout).build()
133
+ ```
134
+
135
+ `get()` is typed against what the container can reach:
136
+
137
+ ```ts
138
+ app.get(Users) // UsersApi
139
+ app.get(Mailer) // error: NotProvided<"Mailer">
140
+ app.get(Current) // error: NotVisible<"Current", "request">
141
+ ```
142
+
143
+ Overrides are exact. Override `Db` with a fake, and nothing that only `Db` needed is required any more; `get(Db)` returns the fake.
144
+
145
+ ### Scopes and containers
146
+
147
+ A scope is a lifetime shorter than the app's, such as one request. `open()` creates a child container for a scope, and you pass the scope's slots their values as bindings. The graph decides which values are required, so a missing one is a compile error naming the slot and what needs it.
148
+
149
+ ```ts
150
+ await using request = await app.open(PerRequest, bind(Request, req))
151
+ request.get(CurrentUser) // created once per opened container
152
+ request.get(Users) // root-scope services are shared
153
+ ```
154
+
155
+ **Always dispose what `open()` returns**, with `await using` or in a `finally`. A container keeps every container opened from it until they are disposed, so that disposing the app can still close them. One dropped without being disposed stays in memory, with everything it created, until its parent is disposed.
156
+
157
+ Everything in a scope is created when its container opens, not when first resolved; that is what keeps `get()` synchronous. Keep factories in short-lived scopes cheap, and free of side effects a request may not need.
158
+
159
+ Scopes nest. Declare them as values, give slots a scope, and open containers one scope at a time:
160
+
161
+ ```ts
162
+ export const Session = scope('session')
163
+ export const Request = scope('request', Session)
164
+
165
+ const Sess = slot('Sess', { scope: Session })<SessionData>()
166
+ const Req = slot('Req', { scope: Request })<IncomingRequest>()
167
+
168
+ const Cart = service('Cart', { sess: Sess }, …) // lives in 'session'
169
+ const Handler = service('Handler', { req: Req, cart: Cart }, …) // lives in 'request'
170
+
171
+ await using session = await app.open(Session, bind(Sess, data))
172
+ await using request = await session.open(Request, bind(Req, req))
173
+ request.get(Handler) // sees request, session and root
174
+ session.get(Handler) // error: NotVisible<"Handler", "request">
175
+ app.open(Request, …) // error: NotChildScope<"request", "root">
176
+ ```
177
+
178
+ A service lives in the deepest scope among its dependencies. Dependencies on different branches, such as a request-scope slot and a job-scope slot, are rejected where the service is declared.
179
+
180
+ Scopes are compared by lineage, their names from the root down, so a different scope that happens to share a name can't be opened in its place.
181
+
182
+ ### Executions
183
+
184
+ Work a service starts and finishes on its own, such as a job, a message handler or a connection, is declared as an execution. It names a scope, an entry node in that scope or above, and the inputs each run supplies. Services depend on it like anything else and receive a runner.
185
+
186
+ ```ts
187
+ const Job = scope('job')
188
+ const JobInput = slot('JobInput', { scope: Job })<{ id: string }>()
189
+ const JobRunner = service('JobRunner', { input: JobInput, db: Db }, ({ input, db }) => ({
190
+ run: () => db.process(input.id),
191
+ }))
192
+
193
+ const RunJob = execution('RunJob', { scope: Job, entry: JobRunner, inputs: { job: JobInput } })
194
+
195
+ const Scheduler = service('Scheduler', { runJob: RunJob }, ({ runJob }) => ({
196
+ schedule: (id: string) => runJob({ job: { id } }, (runner) => runner.run()),
197
+ }))
198
+ ```
199
+
200
+ Each call to the runner:
201
+
202
+ 1. opens a container for the execution's scope, with the inputs it was given;
203
+ 2. resolves the entry and hands it to the body;
204
+ 3. disposes the container once the body settles, whether it returned or threw.
205
+
206
+ The runner takes an object with a value for each input, typed from `inputs`. An execution without inputs takes only the body. `build()` isn't callable while the inputs don't cover what the entry needs: `IncompleteExecutions<{ RunJob: "Other" }>`.
207
+
208
+ A run belongs to the container its runner was resolved from. Disposing that container aborts the `signal` the body receives as its second argument, as in `runJob({ job }, (runner, { signal }) => runner.run(signal))`. It then waits for the body to settle before disposing the run's container and its own instances, so a body never sees what it uses closed beneath it. A body that ignores the signal keeps disposal waiting.
209
+
210
+ ### Multi-bindings
211
+
212
+ ```ts
213
+ const Middleware = slot('Middleware', { many: true })<Middleware>()
214
+ const Server = service('Server', { middleware: Middleware }, ({ middleware }) =>
215
+ createServer(middleware),
216
+ )
217
+
218
+ container().provide(Server).contribute(Middleware, Auth).contribute(Middleware, Cors).build()
219
+ ```
220
+
221
+ Contributions resolve to a readonly array, in the order they were contributed. A multi-binding never needs binding: with no contributions it resolves to `[]`. Contributing a service brings that service's own requirements into the container. `override()` on a multi-binding replaces the whole set.
222
+
223
+ ### Introspection
224
+
225
+ `app.inspect()` and `request.inspect()` describe every node: its scope, dependencies, binding and instance status. [`@doweljs/devtools`](../devtools) shows the same, live, on the Vite dev server.
226
+
227
+ ## Errors
228
+
229
+ Runtime errors are `DowelError`s with a stable `code`, such as `UNBOUND_SLOT`, `CAPTIVE_DEPENDENCY`, `CYCLE`, `INVALID_SCOPE` or `DISPOSED`. Messages include the dependency path:
230
+
231
+ ```text
232
+ Slot 'Logger' is not bound. Needed by Api → Users → Logger.
233
+ ```
234
+
235
+ Well-typed code rarely reaches them. They are a safety net for `any` and for dynamic composition.
236
+
237
+ ## Hot replacement
238
+
239
+ `@doweljs/core/hot` lets a build tool re-register a module's declarations each time the module is re-evaluated; [`@doweljs/vite`](../vite) wires it up.
240
+
241
+ Nodes are matched by name, and a name belongs to the module that first declared it; another module declaring it is rejected. Invalidation is per module and conservative: a factory may close over anything in its module, so every declaration in a re-evaluated module counts as changed.
242
+
243
+ For each live container, an update:
244
+
245
+ 1. compiles the latest declarations and initializes them as a _proposal_, while the current generation keeps running. Root-scope instances from untouched modules, whose dependencies didn't change, are carried over;
246
+ 2. commits the proposal once it is up. Opened containers and running executions are disposed (running executions have their `signal` aborted and are waited for), and so are the instances it replaced;
247
+ 3. leaves stale references, held by modules that weren't re-evaluated, working.
248
+
249
+ The next update doesn't wait for that teardown, so a run that ignores its signal can't hold up later reloads.
250
+
251
+ A proposal that fails to compile or initialize is discarded. The container stays on its last good generation, and `snapshot()` reports it as failed until a later update succeeds.
252
+
253
+ ## Design notes
254
+
255
+ - **Cycles can't be declared.** Dependencies are values, and a `const` can't reference a later one. A cycle can only come from binding a slot to a service that depends on it, from `override()`, or from an execution whose entry reaches the service that runs it. All three are detected at build; the first two are also type errors, which `build()` reports.
256
+ - **Type-level cost.** The model is checked against a generated graph of 300 services in ten layers, 144 of them reachable from the roots (`test/types/scale.test-d.ts`). Checking the whole package takes about five seconds on TypeScript 5.9 and four to eight on TypeScript 7, mostly in that test. Builder types stay short because each step is an interface instantiation, not an expanded record.
257
+ - **TypeScript 5.9 or later.** Earlier versions, checked down to 5.5, accept small graphs but stop with "type instantiation is excessively deep" on one that size.
258
+ - **Declaration emit.** Builder steps over a generic state stay named (`Provide<Provide<S, A>, B>`) instead of expanding the merged graph, so libraries can export generic composition functions with declarations. The types an exported service, builder or container is inferred from are exported for the same reason.
259
+ - **Literal types.** TypeScript keeps the literal return types of nested functions during generic inference: `service('S', () => ({ kind: () => 'a' }))` infers `() => "a"`. Annotate the return type when you want a wider one. This is how inference works, not something dowel does.
package/dist/api.d.ts ADDED
@@ -0,0 +1,145 @@
1
+ /** The public interfaces the runtime implements. */
2
+ import type { DeclaredScope } from './model/checks.ts';
3
+ import type { AnyBinding, AnyExecution, AnyScope, AnyService, AnySlot, Binding, RootMeta, ScopeMeta, ServiceMeta, Slot, SlotMeta } from './model/declarations.ts';
4
+ import type { CompositionIssues, IncompleteExecutions, InputsBoundAtBuild, MissingBindings } from './model/diagnostics.ts';
5
+ import type { NeededBy } from './model/graph.ts';
6
+ import type { BindingMetaOf, Impl, ImplAt, MetaOf } from './model/infer.ts';
7
+ import type { BoundExecutionInputs, BoundNames, ChildCheck, ContainerState, GetResult, Issues, KnownLength, Missing, MissingExecutionInputs, OpenCheck, Overridable, Override, Providable, Provide, ProvideAll, Resolvable, TargetOf } from './model/state.ts';
8
+ import type { Simplify } from './model/util.ts';
9
+ /**
10
+ * The context handed to every factory. It mirrors `AsyncDisposableStack`:
11
+ * a factory owns what it adopts, and nothing else.
12
+ */
13
+ export interface ServiceContext {
14
+ /**
15
+ * Adopt a resource that implements `Symbol.dispose` or `Symbol.asyncDispose`
16
+ * and return it. It is disposed with the instance.
17
+ *
18
+ * ```ts
19
+ * const client = ctx.use(await connect(url))
20
+ * ```
21
+ */
22
+ use<T extends Disposable | AsyncDisposable>(resource: T): T;
23
+ /** Adopt any value with an explicit disposer and return it. */
24
+ adopt<T>(resource: T, dispose: (resource: T) => void | PromiseLike<void>): T;
25
+ /** Run a callback when the instance is disposed. */
26
+ defer(dispose: () => void | PromiseLike<void>): void;
27
+ /**
28
+ * Aborted when disposal of the instance starts, or of its owner while the
29
+ * factory is still running. Disposal waits for a running factory, so pass
30
+ * this to slow startup work (connects, fetches) to let it stop promptly.
31
+ *
32
+ * ```ts
33
+ * const client = ctx.use(await connect(url, { signal: ctx.signal }))
34
+ * ```
35
+ */
36
+ readonly signal: AbortSignal;
37
+ }
38
+ /** What a run's body receives besides the entry. */
39
+ export interface RunContext {
40
+ /**
41
+ * Aborted when the container the runner was resolved from is disposed while
42
+ * the body runs. Disposal waits for the body to settle, so a long-running
43
+ * body should stop when this aborts.
44
+ */
45
+ readonly signal: AbortSignal;
46
+ }
47
+ /** How a node's value arrives, as reported by `inspect()`. */
48
+ export type NodeBinding = {
49
+ readonly kind: 'service';
50
+ readonly name: string;
51
+ } | {
52
+ readonly kind: 'value';
53
+ } | {
54
+ readonly kind: 'scope';
55
+ } | {
56
+ readonly kind: 'absent';
57
+ } | {
58
+ readonly kind: 'many';
59
+ readonly items: readonly ({
60
+ readonly kind: 'service';
61
+ readonly name: string;
62
+ } | {
63
+ readonly kind: 'value';
64
+ })[];
65
+ };
66
+ /** A node of a built container, as reported by `inspect()`. */
67
+ export interface NodeInfo {
68
+ readonly name: string;
69
+ readonly kind: 'service' | 'slot' | 'execution';
70
+ readonly scope: string;
71
+ readonly many: boolean;
72
+ readonly deps: readonly {
73
+ readonly key: string;
74
+ readonly name: string;
75
+ }[];
76
+ readonly binding: NodeBinding | null;
77
+ /** Instance state for cached nodes visible from here; `null` otherwise. */
78
+ readonly status: 'idle' | 'pending' | 'ready' | null;
79
+ }
80
+ /**
81
+ * An immutable description of a container. Every method returns a new
82
+ * builder, so partial compositions can be shared and extended freely.
83
+ */
84
+ export interface ContainerBuilder<S extends ContainerState> {
85
+ /**
86
+ * Add roots and bindings. Services, executions and slots given here are
87
+ * activated at `build()` together with everything they reach; bindings
88
+ * only make implementations available and are activated when reached.
89
+ */
90
+ provide<const I extends readonly Providable[]>(...items: I & KnownLength<I>): ContainerBuilder<ProvideAll<S, I>>;
91
+ /** Shorthand for `provide(bind(slot, impl))`. */
92
+ bind<T, TM extends SlotMeta, const I extends Impl<T, TM>>(slot: Slot<T, TM>, impl: I): ContainerBuilder<Provide<S, Binding<T, BindingMetaOf<TM, I>>>>;
93
+ /** Contribute one item to a multi-binding slot. The same as `bind()` for such slots, named for clarity. */
94
+ contribute<T, TM extends SlotMeta & {
95
+ readonly many: true;
96
+ }, const I extends Impl<T, TM>>(slot: Slot<T, TM>, impl: I): ContainerBuilder<Provide<S, Binding<T, BindingMetaOf<TM, I>>>>;
97
+ /**
98
+ * Replace a node in the graph. Dependents receive the replacement; the
99
+ * original is never instantiated, and nothing that only it needed is
100
+ * required any more. Intended for tests and environment specific swaps.
101
+ * A service must be reachable from what is provided and a slot must be
102
+ * bound by the time `build()` is called, in any order.
103
+ */
104
+ override<T, M extends ServiceMeta | SlotMeta, const I extends NoInfer<T> | ImplAt<T, M>>(target: Overridable<T, M>, impl: I): ContainerBuilder<Override<S, TargetOf<M>, I>>;
105
+ /**
106
+ * Apply a reusable composition function. Write it generic in the state so
107
+ * it applies at any point of a chain:
108
+ *
109
+ * ```ts
110
+ * const infra = <S extends ContainerState>(b: ContainerBuilder<S>) => b.bind(Logger, Pino).provide(Db)
111
+ * container().with(infra).provide(Users).build()
112
+ * ```
113
+ */
114
+ with<R>(compose: (builder: this) => R): R;
115
+ /**
116
+ * Build the container: activate every root and what it reaches, awaiting
117
+ * async factories. Only callable once the composition has no issues,
118
+ * every reachable root-scope slot is bound and every execution's inputs
119
+ * are complete. If construction fails, everything created is disposed
120
+ * before the returned promise rejects.
121
+ */
122
+ readonly build: [Issues<S>] extends [never] ? [Missing<S>] extends [never] ? [keyof MissingExecutionInputs<S>] extends [never] ? [keyof BoundExecutionInputs<S>] extends [never] ? () => Promise<Container<S>> : InputsBoundAtBuild<Simplify<BoundExecutionInputs<S>>> : IncompleteExecutions<Simplify<MissingExecutionInputs<S>>> : MissingBindings<Simplify<NeededBy<S['graph'], Missing<S>>>> : CompositionIssues<Issues<S>>;
123
+ }
124
+ /**
125
+ * A container: the one `build()` returns, for the `root` scope, or one opened
126
+ * from it for a child scope with `open()`. Each sees its own scope and every
127
+ * scope above it.
128
+ */
129
+ export interface Container<S extends ContainerState = ContainerState, Current extends ScopeMeta = RootMeta,
130
+ /** Slots supplied to `open()` for this container and those it was opened from. */
131
+ Supplied extends string = never> extends AsyncDisposable {
132
+ /** Resolve a node visible from this scope. Always synchronous. */
133
+ get<N extends AnyService | AnySlot | AnyExecution>(node: Resolvable<N, S, Current>): GetResult<N, S, Supplied>;
134
+ /**
135
+ * Open a container for a child scope. Required slots of that scope must be
136
+ * supplied as value bindings. Everything in that scope is
137
+ * initialized before the promise resolves; on failure it is disposed first.
138
+ */
139
+ open<Sc extends AnyScope, const B extends readonly AnyBinding[]>(scope: Sc & DeclaredScope<Sc> & ChildCheck<Current, Sc> & OpenCheck<S, MetaOf<Sc>['name'], B>, ...bindings: B): Promise<Container<S, MetaOf<Sc>, Supplied | BoundNames<B>>>;
140
+ /** Describe every node in the graph and the state of instances visible from here. */
141
+ inspect(): readonly NodeInfo[];
142
+ /** Dispose every container and execution opened from here, then every instance, in reverse creation order. */
143
+ dispose(): Promise<void>;
144
+ }
145
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js ADDED
@@ -0,0 +1,3 @@
1
+ /** The public interfaces the runtime implements. */
2
+ export {};
3
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,oDAAoD"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Turns a composition (roots, bindings, contributions and overrides) into
3
+ * plans.
4
+ *
5
+ * Compilation walks the graph from the roots, validates it (unbound slots,
6
+ * captive dependencies, cycles, duplicate names, scope mismatches, execution
7
+ * inputs) and produces one `Plan` per reached node, allocating each cached
8
+ * plan a cell in the stores of its scope. Bindings that nothing reaches are
9
+ * never compiled, so they never activate.
10
+ */
11
+ import { type DeclaredNode, type ImplNode, type ScopeNode, type ServiceNode, type SlotNode } from '../nodes/node.ts';
12
+ import type { Compiled } from './plan.ts';
13
+ /** Everything a builder accumulates. Immutable once a container is built from it. */
14
+ export interface Composition {
15
+ /** Services, executions and slots activation starts from. */
16
+ readonly roots: readonly DeclaredNode[];
17
+ readonly bindings: ReadonlyMap<SlotNode, ImplNode>;
18
+ readonly contributions: ReadonlyMap<SlotNode, readonly ImplNode[]>;
19
+ readonly overrides: ReadonlyMap<SlotNode | ServiceNode, ImplNode>;
20
+ }
21
+ /** Maps a node to the one that should stand in for it (used by hot replacement). */
22
+ export type Canonicalize = <N extends DeclaredNode | ScopeNode>(node: N) => N;
23
+ export interface CompileOptions {
24
+ readonly canonical?: Canonicalize;
25
+ }
26
+ export declare function compile(composition: Composition, options?: CompileOptions): Compiled;
27
+ //# sourceMappingURL=compile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile/compile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EACN,KAAK,YAAY,EAEjB,KAAK,QAAQ,EAEb,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAGX,QAAQ,EAYR,MAAM,WAAW,CAAA;AAElB,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IAClD,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC,CAAA;IAClE,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,QAAQ,GAAG,WAAW,EAAE,QAAQ,CAAC,CAAA;CACjE;AAED,oFAAoF;AACpF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,SAAS,YAAY,GAAG,SAAS,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAA;AAE7E,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAA;CACjC;AAYD,wBAAgB,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,GAAE,cAAmB,GAAG,QAAQ,CAExF"}