@chidchanun/bcp 0.2.19 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -98
- package/docs/README.md +82 -62
- package/docs/api-freeze-snapshot.json +254 -232
- package/docs/api-manifest.json +29 -13
- package/docs/api-reference.md +240 -149
- package/docs/application-platform.md +348 -0
- package/docs/docs-web-manifest.json +10 -5
- package/docs/migration-0.3.md +280 -0
- package/docs/platform-contract.md +68 -23
- package/docs/platform-manifest.json +38 -6
- package/docs/releases/0.3.0.md +129 -0
- package/docs/releases/0.3.1.md +116 -0
- package/docs/service-container.md +333 -0
- package/docs/stability-api-freeze.md +62 -91
- package/package.json +14 -2
- package/packages/bundler/src/client-boundary.ts +2 -0
- package/packages/client/src/application.mjs +2439 -0
- package/packages/client/src/application.ts +12 -0
- package/packages/client/src/container.mjs +573 -0
- package/packages/client/src/container.ts +24 -0
- package/packages/server/src/application.ts +956 -0
- package/packages/server/src/container.ts +970 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# BCP Application Platform
|
|
2
|
+
|
|
3
|
+
BCP Framework `0.3.x` provides a server-side application composition root that coordinates the infrastructure platforms introduced throughout `0.2.x` without replacing their independent APIs.
|
|
4
|
+
|
|
5
|
+
`0.3.1` extends the original `0.3.0` runtime with typed dependency injection through `bcp/container`.
|
|
6
|
+
|
|
7
|
+
## Public entrypoint
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
createApp,
|
|
12
|
+
defineApp,
|
|
13
|
+
} from "bcp/application";
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`bcp/application` is server-only and blocked from browser/page dependency graphs.
|
|
17
|
+
|
|
18
|
+
## Composition model
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
BCP Application
|
|
22
|
+
|
|
|
23
|
+
+-- typed application config
|
|
24
|
+
+-- typed DI container
|
|
25
|
+
+-- legacy plugin service registry
|
|
26
|
+
+-- plugin/module host
|
|
27
|
+
+-- infrastructure resources
|
|
28
|
+
+-- deployment metadata/readiness/diagnostics
|
|
29
|
+
+-- graceful shutdown
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Applications still construct database, cache, jobs, workflow, event, realtime and observability implementations explicitly. The application root composes their lifecycle; it does not hide provider creation.
|
|
33
|
+
|
|
34
|
+
## Basic application
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import {
|
|
38
|
+
createApp,
|
|
39
|
+
} from "bcp/application";
|
|
40
|
+
|
|
41
|
+
export const app =
|
|
42
|
+
createApp({
|
|
43
|
+
name: "orders-api",
|
|
44
|
+
version: "1.0.0",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await app.start();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Shutdown:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
await app.stop();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`shutdown()` and `close()` are aliases for the same terminal application shutdown operation.
|
|
57
|
+
|
|
58
|
+
## Typed application config
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const app =
|
|
62
|
+
createApp<{
|
|
63
|
+
port: number;
|
|
64
|
+
}>({
|
|
65
|
+
name: "orders-api",
|
|
66
|
+
config: {
|
|
67
|
+
port: process.env.PORT,
|
|
68
|
+
},
|
|
69
|
+
schema: {
|
|
70
|
+
parse(value) {
|
|
71
|
+
const raw = value as {
|
|
72
|
+
port?: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
port:
|
|
77
|
+
Number(
|
|
78
|
+
raw.port ?? 3000
|
|
79
|
+
),
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The schema may be an object with `parse()` or a parser function.
|
|
87
|
+
|
|
88
|
+
## Typed dependency injection — 0.3.1
|
|
89
|
+
|
|
90
|
+
Define providers with `bcp/container`:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import {
|
|
94
|
+
createServiceToken,
|
|
95
|
+
provideFactory,
|
|
96
|
+
provideValue,
|
|
97
|
+
} from "bcp/container";
|
|
98
|
+
|
|
99
|
+
const configToken =
|
|
100
|
+
createServiceToken<{
|
|
101
|
+
apiUrl: string;
|
|
102
|
+
}>("config");
|
|
103
|
+
|
|
104
|
+
const repositoryToken =
|
|
105
|
+
createServiceToken<UserRepository>(
|
|
106
|
+
"user-repository"
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const app =
|
|
110
|
+
createApp({
|
|
111
|
+
name: "orders-api",
|
|
112
|
+
providers: [
|
|
113
|
+
provideValue(
|
|
114
|
+
configToken,
|
|
115
|
+
{
|
|
116
|
+
apiUrl: "https://api.example.com",
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
provideFactory(
|
|
120
|
+
repositoryToken,
|
|
121
|
+
[
|
|
122
|
+
configToken,
|
|
123
|
+
] as const,
|
|
124
|
+
(_context, [config]) =>
|
|
125
|
+
createRepository(
|
|
126
|
+
config.apiUrl
|
|
127
|
+
)
|
|
128
|
+
),
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Resolve from application hooks:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
createApp({
|
|
137
|
+
name: "orders-api",
|
|
138
|
+
providers: [
|
|
139
|
+
repositoryProvider,
|
|
140
|
+
],
|
|
141
|
+
|
|
142
|
+
async setup(context) {
|
|
143
|
+
const repository =
|
|
144
|
+
await context.container.resolve(
|
|
145
|
+
repositoryToken
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Application DI APIs:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
app.container
|
|
155
|
+
app.context.container
|
|
156
|
+
app.register(provider)
|
|
157
|
+
app.createScope(options)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`app.register()` follows the same deterministic mutation boundary as plugins/resources and is only allowed before startup begins.
|
|
161
|
+
|
|
162
|
+
For request/job/test boundaries:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const scope =
|
|
166
|
+
app.createScope({
|
|
167
|
+
name: "request:123",
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Testing overrides can be supplied to the scope without modifying the application root.
|
|
172
|
+
|
|
173
|
+
See [Dependency Injection & Service Container](service-container.md).
|
|
174
|
+
|
|
175
|
+
## Legacy plugin services
|
|
176
|
+
|
|
177
|
+
`app.services` / `context.services` remain the registry used by `bcp/plugins`:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
app.provide(
|
|
181
|
+
"database",
|
|
182
|
+
database
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
This remains supported for compatibility. New typed application dependencies should prefer `app.container`.
|
|
187
|
+
|
|
188
|
+
The two registries are intentionally not silently synchronized because typed `ServiceToken<T>` values and plugin string/symbol service keys have different contracts.
|
|
189
|
+
|
|
190
|
+
## Plugins and modules
|
|
191
|
+
|
|
192
|
+
Existing `bcp/plugins` definitions plug directly into the application runtime:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const app =
|
|
196
|
+
createApp({
|
|
197
|
+
name: "orders-api",
|
|
198
|
+
modules: [
|
|
199
|
+
backendModule,
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Before startup:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
app.use(mailPlugin);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Infrastructure resources
|
|
211
|
+
|
|
212
|
+
Use the existing Deployment Platform resource contract:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
app.addResource({
|
|
216
|
+
name: "database",
|
|
217
|
+
|
|
218
|
+
async start() {
|
|
219
|
+
await database.connect();
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
ready() {
|
|
223
|
+
return database.ready;
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
async stop() {
|
|
227
|
+
await database.close();
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Lifecycle order
|
|
233
|
+
|
|
234
|
+
`setup()` runs before deployment startup, allowing config and DI services to be resolved before resource startup.
|
|
235
|
+
|
|
236
|
+
Deployment order in `0.3.1`:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
application.setup()
|
|
240
|
+
|
|
|
241
|
+
v
|
|
242
|
+
bcp:container
|
|
243
|
+
|
|
|
244
|
+
v
|
|
245
|
+
bcp:plugins
|
|
246
|
+
|
|
|
247
|
+
v
|
|
248
|
+
resource 1
|
|
249
|
+
resource 2
|
|
250
|
+
...
|
|
251
|
+
|
|
|
252
|
+
v
|
|
253
|
+
bcp:application / application.start()
|
|
254
|
+
|
|
|
255
|
+
v
|
|
256
|
+
state = ready
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Shutdown reverses deployment dependencies:
|
|
260
|
+
|
|
261
|
+
```text
|
|
262
|
+
application.stop()
|
|
263
|
+
|
|
|
264
|
+
v
|
|
265
|
+
resource N
|
|
266
|
+
...
|
|
267
|
+
resource 1
|
|
268
|
+
|
|
|
269
|
+
v
|
|
270
|
+
plugins stop/dispose
|
|
271
|
+
|
|
|
272
|
+
v
|
|
273
|
+
container dispose
|
|
274
|
+
|
|
|
275
|
+
v
|
|
276
|
+
application.dispose()
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
This keeps injected services alive while application resources and plugins are shutting down.
|
|
280
|
+
|
|
281
|
+
Startup failures use Deployment Platform rollback. Application cleanup also closes plugins and disposes the DI container idempotently when startup fails before the deployment graph becomes active.
|
|
282
|
+
|
|
283
|
+
## Readiness and diagnostics
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
const readiness =
|
|
287
|
+
await app.readiness();
|
|
288
|
+
|
|
289
|
+
const diagnostics =
|
|
290
|
+
await app.diagnostics();
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Readiness now includes internal `bcp:container`, `bcp:plugins`, application resources and `bcp:application` entries.
|
|
294
|
+
|
|
295
|
+
Application diagnostics expose registered container provider descriptions in addition to plugin/service/deployment information.
|
|
296
|
+
|
|
297
|
+
## Signals and framework shutdown hooks
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
const removeSignals =
|
|
301
|
+
app.installSignalHandlers();
|
|
302
|
+
|
|
303
|
+
const unregister =
|
|
304
|
+
app.registerShutdownHook();
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Both paths perform full application shutdown, including application stop hooks and container disposal.
|
|
308
|
+
|
|
309
|
+
## Application state
|
|
310
|
+
|
|
311
|
+
```text
|
|
312
|
+
created
|
|
313
|
+
starting
|
|
314
|
+
ready
|
|
315
|
+
stopping
|
|
316
|
+
stopped
|
|
317
|
+
failed
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Concurrent/repeated start and shutdown calls are idempotent in their supported states. A stopped application is terminal and cannot be restarted.
|
|
321
|
+
|
|
322
|
+
## Mutation boundary
|
|
323
|
+
|
|
324
|
+
The following are rejected once startup begins:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
app.use(...)
|
|
328
|
+
app.provide(...)
|
|
329
|
+
app.register(...)
|
|
330
|
+
app.addResource(...)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Creating child scopes is allowed while the container is active. Resolving/creating scopes after container disposal is rejected.
|
|
334
|
+
|
|
335
|
+
## Compatibility
|
|
336
|
+
|
|
337
|
+
`0.3.1` is additive over `0.3.0`:
|
|
338
|
+
|
|
339
|
+
```text
|
|
340
|
+
previous baseline: 0.3.0
|
|
341
|
+
intentional breaking changes: false
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Existing applications that use only `app.services` continue to work. DI adoption can be incremental.
|
|
345
|
+
|
|
346
|
+
## Next platform work
|
|
347
|
+
|
|
348
|
+
The next planned milestone is `0.3.2 — Module System v2`, using the Application Platform and DI container as the composition foundation for reusable application-native modules.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"framework": "bcp",
|
|
4
|
-
"versionTarget": "0.
|
|
4
|
+
"versionTarget": "0.3.1",
|
|
5
5
|
"releaseState": "unreleased",
|
|
6
6
|
"sections": [
|
|
7
7
|
{
|
|
@@ -58,8 +58,10 @@
|
|
|
58
58
|
{
|
|
59
59
|
"id": "runtime",
|
|
60
60
|
"title": "Runtime & Infrastructure",
|
|
61
|
-
"description": "
|
|
61
|
+
"description": "Application composition, dependency injection, middleware, jobs, scheduling, workflows, event delivery, realtime channels, observability, deployment lifecycle, distributed caching, security and production hardening.",
|
|
62
62
|
"pages": [
|
|
63
|
+
{ "route": "/docs/application-platform", "source": "application-platform.md", "title": "Application Platform" },
|
|
64
|
+
{ "route": "/docs/service-container", "source": "service-container.md", "title": "Dependency Injection & Service Container" },
|
|
63
65
|
{ "route": "/docs/middleware", "source": "middleware.md", "title": "Middleware" },
|
|
64
66
|
{ "route": "/docs/hydration", "source": "hydration.md", "title": "Hydration" },
|
|
65
67
|
{ "route": "/docs/development-logging", "source": "development-logging.md", "title": "Logging" },
|
|
@@ -102,11 +104,12 @@
|
|
|
102
104
|
{
|
|
103
105
|
"id": "platform-compatibility",
|
|
104
106
|
"title": "Platform & Compatibility",
|
|
105
|
-
"description": "Supported platform contracts, API
|
|
107
|
+
"description": "Supported platform contracts, API baselines, documentation integration and migration guidance.",
|
|
106
108
|
"pages": [
|
|
107
109
|
{ "route": "/docs/platform-contract", "source": "platform-contract.md", "title": "Framework Platform Contract" },
|
|
108
|
-
{ "route": "/docs/stability-api-freeze", "source": "stability-api-freeze.md", "title": "Stability & API Freeze" },
|
|
110
|
+
{ "route": "/docs/stability-api-freeze", "source": "stability-api-freeze.md", "title": "0.2 Stability & API Freeze" },
|
|
109
111
|
{ "route": "/docs/documentation-platform", "source": "documentation-platform.md", "title": "Documentation Platform" },
|
|
112
|
+
{ "route": "/docs/migration-0.3", "source": "migration-0.3.md", "title": "Migrating to 0.3.x" },
|
|
110
113
|
{ "route": "/docs/migration-0.2", "source": "migration-0.2.md", "title": "Migrating to 0.2.x" }
|
|
111
114
|
]
|
|
112
115
|
},
|
|
@@ -120,7 +123,9 @@
|
|
|
120
123
|
}
|
|
121
124
|
],
|
|
122
125
|
"releases": [
|
|
123
|
-
{ "route": "/releases/0.
|
|
126
|
+
{ "route": "/releases/0.3.1", "source": "releases/0.3.1.md", "version": "0.3.1", "state": "unreleased" },
|
|
127
|
+
{ "route": "/releases/0.3.0", "source": "releases/0.3.0.md", "version": "0.3.0" },
|
|
128
|
+
{ "route": "/releases/0.2.19", "source": "releases/0.2.19.md", "version": "0.2.19" },
|
|
124
129
|
{ "route": "/releases/0.2.18", "source": "releases/0.2.18.md", "version": "0.2.18" },
|
|
125
130
|
{ "route": "/releases/0.2.17", "source": "releases/0.2.17.md", "version": "0.2.17" },
|
|
126
131
|
{ "route": "/releases/0.2.16", "source": "releases/0.2.16.md", "version": "0.2.16" },
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Migrating to BCP Framework 0.3.x
|
|
2
|
+
|
|
3
|
+
BCP Framework `0.3.0` established the Application Platform baseline. `0.3.1` extends it additively with typed dependency injection through `bcp/container`.
|
|
4
|
+
|
|
5
|
+
## Compatibility
|
|
6
|
+
|
|
7
|
+
Current target:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
0.3.1 — Dependency Injection & Service Container
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Previous baseline:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
0.3.0
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Intentional breaking changes:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
false
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Existing `0.3.0` applications do not have to adopt dependency injection immediately.
|
|
26
|
+
|
|
27
|
+
## Upgrade to 0.3.1
|
|
28
|
+
|
|
29
|
+
After publication:
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
npm exec -- bcp-framework update --check
|
|
33
|
+
npm run update -- 0.3.1
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then validate the application:
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
npm run typecheck
|
|
40
|
+
npm run build
|
|
41
|
+
npm exec -- bcp-framework routes
|
|
42
|
+
npm exec -- bcp-framework doctor
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Existing Application Platform code remains valid
|
|
46
|
+
|
|
47
|
+
This remains supported:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import {
|
|
51
|
+
createApp,
|
|
52
|
+
} from "bcp/application";
|
|
53
|
+
|
|
54
|
+
const app =
|
|
55
|
+
createApp({
|
|
56
|
+
name: "my-app",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
app.provide(
|
|
60
|
+
"database",
|
|
61
|
+
database
|
|
62
|
+
);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`app.services` and `context.services` continue to use the Plugin Platform registry.
|
|
66
|
+
|
|
67
|
+
## Optional migration to typed DI
|
|
68
|
+
|
|
69
|
+
New typed dependencies can use `bcp/container`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import {
|
|
73
|
+
createServiceToken,
|
|
74
|
+
provideValue,
|
|
75
|
+
} from "bcp/container";
|
|
76
|
+
|
|
77
|
+
const databaseToken =
|
|
78
|
+
createServiceToken<typeof database>(
|
|
79
|
+
"database"
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const app =
|
|
83
|
+
createApp({
|
|
84
|
+
name: "my-app",
|
|
85
|
+
providers: [
|
|
86
|
+
provideValue(
|
|
87
|
+
databaseToken,
|
|
88
|
+
database
|
|
89
|
+
),
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Then resolve from application hooks or server composition code:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const database =
|
|
98
|
+
await app.container.resolve(
|
|
99
|
+
databaseToken
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You do not need to migrate every plugin service at once.
|
|
104
|
+
|
|
105
|
+
## Factory dependencies
|
|
106
|
+
|
|
107
|
+
Replace manual service construction:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const repository =
|
|
111
|
+
createRepository(
|
|
112
|
+
database,
|
|
113
|
+
logger
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
with explicit typed provider dependencies when useful:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const repositoryProvider =
|
|
121
|
+
provideFactory(
|
|
122
|
+
repositoryToken,
|
|
123
|
+
[
|
|
124
|
+
databaseToken,
|
|
125
|
+
loggerToken,
|
|
126
|
+
] as const,
|
|
127
|
+
(
|
|
128
|
+
_context,
|
|
129
|
+
[database, logger]
|
|
130
|
+
) =>
|
|
131
|
+
createRepository(
|
|
132
|
+
database,
|
|
133
|
+
logger
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
No decorators or reflection metadata are required.
|
|
139
|
+
|
|
140
|
+
## Request/job/test scopes
|
|
141
|
+
|
|
142
|
+
Use child scopes for dependencies that should not be application singletons:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const requestScope =
|
|
146
|
+
app.createScope({
|
|
147
|
+
name: `request:${requestId}`,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const service =
|
|
152
|
+
await requestScope.resolve(
|
|
153
|
+
requestServiceToken
|
|
154
|
+
);
|
|
155
|
+
} finally {
|
|
156
|
+
await requestScope.dispose();
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Testing overrides:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const testScope =
|
|
164
|
+
app.createScope({
|
|
165
|
+
name: "test",
|
|
166
|
+
overrides: [
|
|
167
|
+
provideValue(
|
|
168
|
+
mailerToken,
|
|
169
|
+
fakeMailer
|
|
170
|
+
),
|
|
171
|
+
],
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Lifetimes
|
|
176
|
+
|
|
177
|
+
Provider lifetimes are:
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
singleton
|
|
181
|
+
scoped
|
|
182
|
+
transient
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The default is `singleton`.
|
|
186
|
+
|
|
187
|
+
A common mapping is:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
database pool singleton
|
|
191
|
+
cache client singleton
|
|
192
|
+
repository scoped or singleton depending on state
|
|
193
|
+
request context scoped
|
|
194
|
+
small stateless factory output transient when required
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Disposal ownership
|
|
198
|
+
|
|
199
|
+
If the DI container owns a resource through a provider `dispose()` callback, avoid also registering an independent shutdown owner for the same instance unless the cleanup operation is explicitly idempotent.
|
|
200
|
+
|
|
201
|
+
Example:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
provideFactory(
|
|
205
|
+
databaseToken,
|
|
206
|
+
[] as const,
|
|
207
|
+
createDatabase,
|
|
208
|
+
{
|
|
209
|
+
dispose(database) {
|
|
210
|
+
return database.close();
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The Application Platform keeps the container alive until application resources/plugins stop, then disposes injected services in reverse creation order.
|
|
217
|
+
|
|
218
|
+
## Application resource lifecycle
|
|
219
|
+
|
|
220
|
+
Long-running components can still use `app.addResource()`:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
app.addResource({
|
|
224
|
+
name: "worker",
|
|
225
|
+
start: () => worker.start(),
|
|
226
|
+
stop: () => worker.stop(),
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
DI and Deployment resources solve different concerns:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
container provider -> construct/resolve/dispose dependencies
|
|
234
|
+
application resource -> start/readiness/stop long-running runtime components
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
A provider may construct the worker while an application resource starts/stops it.
|
|
238
|
+
|
|
239
|
+
## Server-only boundary
|
|
240
|
+
|
|
241
|
+
Both entrypoints are server-only:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
bcp/container
|
|
245
|
+
bcp/application
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Do not import them into React client pages/islands.
|
|
249
|
+
|
|
250
|
+
## API baseline
|
|
251
|
+
|
|
252
|
+
`0.3.1` adds one public entrypoint over `0.3.0`:
|
|
253
|
+
|
|
254
|
+
```text
|
|
255
|
+
bcp/container
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The reviewed API snapshot records the new `container.mjs` prepared runtime and browser poison boundary.
|
|
259
|
+
|
|
260
|
+
## Framework validation checklist
|
|
261
|
+
|
|
262
|
+
```powershell
|
|
263
|
+
npm run typecheck
|
|
264
|
+
npm run test:unit
|
|
265
|
+
npm run test:integration
|
|
266
|
+
npm run test:e2e
|
|
267
|
+
npm run test:package
|
|
268
|
+
npm run api:check
|
|
269
|
+
npm run release:readiness
|
|
270
|
+
npm run rc:check
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
For application upgrades:
|
|
274
|
+
|
|
275
|
+
```powershell
|
|
276
|
+
npm run typecheck
|
|
277
|
+
npm run build
|
|
278
|
+
npm exec -- bcp-framework routes
|
|
279
|
+
npm exec -- bcp-framework doctor
|
|
280
|
+
```
|