@chidchanun/bcp 0.3.0 → 0.3.2

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.
@@ -1,8 +1,8 @@
1
1
  # BCP Application Platform
2
2
 
3
- BCP Framework `0.3.0` adds a framework-level application runtime that composes the infrastructure platforms introduced throughout `0.2.x` without replacing their individual APIs.
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
4
 
5
- > **Release state:** unreleased development target until the complete RC workflow passes, the `v0.3.0` tag is created and npm publication succeeds.
5
+ `0.3.1` extends the original `0.3.0` runtime with typed dependency injection through `bcp/container`.
6
6
 
7
7
  ## Public entrypoint
8
8
 
@@ -13,34 +13,23 @@ import {
13
13
  } from "bcp/application";
14
14
  ```
15
15
 
16
- `bcp/application` is server-only and is blocked from browser/page dependency graphs.
16
+ `bcp/application` is server-only and blocked from browser/page dependency graphs.
17
17
 
18
- ## Why an application runtime
19
-
20
- Before `0.3.0`, applications could use BCP databases, jobs, workflows, events, realtime, plugins, cache, observability and deployment independently. That remains supported.
21
-
22
- The Application Platform adds one composition root for applications that want those systems to share lifecycle and services:
18
+ ## Composition model
23
19
 
24
20
  ```text
25
21
  BCP Application
26
22
  |
27
23
  +-- typed application config
28
- +-- shared service registry
29
- +-- plugin/modules host
24
+ +-- typed DI container
25
+ +-- legacy plugin service registry
26
+ +-- plugin/module host
30
27
  +-- infrastructure resources
31
28
  +-- deployment metadata/readiness/diagnostics
32
29
  +-- graceful shutdown
33
- |
34
- +-- database
35
- +-- cache / Redis
36
- +-- jobs / scheduler
37
- +-- workflow
38
- +-- events / outbox
39
- +-- realtime
40
- +-- observability
41
30
  ```
42
31
 
43
- The application runtime does not create hidden database/job/cache implementations. Applications continue constructing the provider/runtime objects they need and register them with the composition root.
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.
44
33
 
45
34
  ## Basic application
46
35
 
@@ -96,245 +85,228 @@ const app =
96
85
 
97
86
  The schema may be an object with `parse()` or a parser function.
98
87
 
99
- ## Shared services
100
-
101
- The application service registry is the same registry used by BCP plugins.
88
+ ## Typed dependency injection — 0.3.1
102
89
 
103
- Register before startup:
90
+ Define providers with `bcp/container`:
104
91
 
105
92
  ```ts
106
- app.provide(
107
- "database",
108
- database
109
- );
93
+ import {
94
+ createServiceToken,
95
+ provideFactory,
96
+ provideValue,
97
+ } from "bcp/container";
110
98
 
111
- app.provide(
112
- "cache",
113
- cache
114
- );
115
- ```
99
+ const configToken =
100
+ createServiceToken<{
101
+ apiUrl: string;
102
+ }>("config");
116
103
 
117
- Consume from application hooks:
104
+ const repositoryToken =
105
+ createServiceToken<UserRepository>(
106
+ "user-repository"
107
+ );
118
108
 
119
- ```ts
120
109
  const app =
121
110
  createApp({
122
111
  name: "orders-api",
123
-
124
- start(context) {
125
- const database =
126
- context.services.get(
127
- "database"
128
- );
129
- },
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
130
  });
131
131
  ```
132
132
 
133
- Plugins receive the same registry through their normal `PluginContext`.
133
+ Resolve from application hooks:
134
134
 
135
- ## Plugins and modules
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
+ ```
136
150
 
137
- Existing `bcp/plugins` definitions plug directly into the application runtime:
151
+ Application DI APIs:
138
152
 
139
- ```ts
140
- import {
141
- definePlugin,
142
- } from "bcp/plugins";
143
- import {
144
- createApp,
145
- } from "bcp/application";
153
+ ```text
154
+ app.container
155
+ app.context.container
156
+ app.register(provider)
157
+ app.createScope(options)
158
+ ```
146
159
 
147
- const mailPlugin =
148
- definePlugin({
149
- name: "mail",
160
+ `app.register()` follows the same deterministic mutation boundary as plugins/resources and is only allowed before startup begins.
150
161
 
151
- setup(context) {
152
- context.services.provide(
153
- "mail",
154
- mailer
155
- );
156
- },
157
- });
162
+ For request/job/test boundaries:
158
163
 
159
- const app =
160
- createApp({
161
- name: "orders-api",
162
- plugins: [
163
- mailPlugin,
164
- ],
164
+ ```ts
165
+ const scope =
166
+ app.createScope({
167
+ name: "request:123",
165
168
  });
166
169
  ```
167
170
 
168
- Existing `defineModule()` plugin modules can be passed through `modules` without conversion.
171
+ Testing overrides can be supplied to the scope without modifying the application root.
169
172
 
170
- Before startup, applications may also register extensions fluently:
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`:
171
178
 
172
179
  ```ts
173
- app.use(mailPlugin);
180
+ app.provide(
181
+ "database",
182
+ database
183
+ );
174
184
  ```
175
185
 
176
- ## Infrastructure resources
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
177
191
 
178
- Register lifecycle resources using the existing Deployment Platform resource contract:
192
+ Existing `bcp/plugins` definitions plug directly into the application runtime:
179
193
 
180
194
  ```ts
181
195
  const app =
182
196
  createApp({
183
197
  name: "orders-api",
184
- resources: [
185
- {
186
- name: "database",
187
-
188
- async start() {
189
- await database.connect();
190
- },
191
-
192
- ready() {
193
- return database.ready;
194
- },
195
-
196
- async stop() {
197
- await database.close();
198
- },
199
- },
198
+ modules: [
199
+ backendModule,
200
200
  ],
201
201
  });
202
202
  ```
203
203
 
204
- Or before `start()`:
204
+ Before startup:
205
+
206
+ ```ts
207
+ app.use(mailPlugin);
208
+ ```
209
+
210
+ ## Infrastructure resources
211
+
212
+ Use the existing Deployment Platform resource contract:
205
213
 
206
214
  ```ts
207
215
  app.addResource({
208
- name: "workers",
216
+ name: "database",
209
217
 
210
- start() {
211
- worker = jobs.startWorker();
218
+ async start() {
219
+ await database.connect();
212
220
  },
213
221
 
214
- stop() {
215
- return worker.stop();
222
+ ready() {
223
+ return database.ready;
224
+ },
225
+
226
+ async stop() {
227
+ await database.close();
216
228
  },
217
229
  });
218
230
  ```
219
231
 
220
232
  ## Lifecycle order
221
233
 
222
- Application startup has one deterministic order:
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`:
223
237
 
224
238
  ```text
225
239
  application.setup()
226
240
  |
227
241
  v
228
- plugin setup/start
242
+ bcp:container
229
243
  |
230
244
  v
231
- resource 1 start
232
- resource 2 start
245
+ bcp:plugins
246
+ |
247
+ v
248
+ resource 1
249
+ resource 2
233
250
  ...
234
251
  |
235
252
  v
236
- application.start()
253
+ bcp:application / application.start()
237
254
  |
238
255
  v
239
256
  state = ready
240
257
  ```
241
258
 
242
- Shutdown reverses the dependency order:
259
+ Shutdown reverses deployment dependencies:
243
260
 
244
261
  ```text
245
262
  application.stop()
246
263
  |
247
264
  v
248
- resource N stop
265
+ resource N
249
266
  ...
250
- resource 1 stop
267
+ resource 1
268
+ |
269
+ v
270
+ plugins stop/dispose
251
271
  |
252
272
  v
253
- plugin stop/dispose
273
+ container dispose
254
274
  |
255
275
  v
256
276
  application.dispose()
257
277
  ```
258
278
 
259
- This allows application logic and workers to stop before their shared database/cache/Redis dependencies disappear.
260
-
261
- Startup failures use Deployment Platform rollback. Already-started resources are stopped in reverse order and application/plugin disposal still runs.
279
+ This keeps injected services alive while application resources and plugins are shutting down.
262
280
 
263
- ## Setup versus start
264
-
265
- `setup()` runs before the deployment resource graph starts. It is useful for service registration and in-process configuration:
266
-
267
- ```ts
268
- createApp({
269
- name: "orders-api",
270
-
271
- setup(context) {
272
- context.services.provide(
273
- "feature-flags",
274
- flags
275
- );
276
- },
277
- });
278
- ```
279
-
280
- `start()` runs after plugins and infrastructure resources have started:
281
-
282
- ```ts
283
- createApp({
284
- name: "orders-api",
285
-
286
- start(context) {
287
- const database =
288
- context.services.get(
289
- "database"
290
- );
291
-
292
- // Application-level startup after infrastructure is available.
293
- },
294
- });
295
- ```
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.
296
282
 
297
283
  ## Readiness and diagnostics
298
284
 
299
- Application readiness delegates to the Deployment Platform:
300
-
301
285
  ```ts
302
- const report =
286
+ const readiness =
303
287
  await app.readiness();
304
- ```
305
288
 
306
- The report includes the internal plugin host, registered resources and the application lifecycle resource.
307
-
308
- Diagnostics:
309
-
310
- ```ts
311
289
  const diagnostics =
312
290
  await app.diagnostics();
313
291
  ```
314
292
 
315
- Application diagnostics include deployment identity plus lifecycle information for every registered resource.
293
+ Readiness now includes internal `bcp:container`, `bcp:plugins`, application resources and `bcp:application` entries.
316
294
 
317
- ## Signals and framework shutdown hooks
295
+ Application diagnostics expose registered container provider descriptions in addition to plugin/service/deployment information.
318
296
 
319
- If this process owns termination signals:
297
+ ## Signals and framework shutdown hooks
320
298
 
321
299
  ```ts
322
300
  const removeSignals =
323
301
  app.installSignalHandlers();
324
- ```
325
-
326
- The default deployment signals remain `SIGTERM` and `SIGINT`.
327
302
 
328
- To participate in the existing BCP production shutdown registry:
329
-
330
- ```ts
331
303
  const unregister =
332
304
  app.registerShutdownHook();
333
305
  ```
334
306
 
335
- ## Application state
307
+ Both paths perform full application shutdown, including application stop hooks and container disposal.
336
308
 
337
- The public state model is:
309
+ ## Application state
338
310
 
339
311
  ```text
340
312
  created
@@ -345,34 +317,32 @@ stopped
345
317
  failed
346
318
  ```
347
319
 
348
- Startup and shutdown are idempotent for concurrent/repeated calls while the application remains in the corresponding active/terminal lifecycle.
349
-
350
- A stopped application is terminal and cannot be started again. Create a new application runtime for a new process lifecycle.
320
+ Concurrent/repeated start and shutdown calls are idempotent in their supported states. A stopped application is terminal and cannot be restarted.
351
321
 
352
322
  ## Mutation boundary
353
323
 
354
- Plugins, services and deployment resources must be registered before `start()`.
355
-
356
- The following are intentionally rejected after startup begins:
324
+ The following are rejected once startup begins:
357
325
 
358
326
  ```ts
359
327
  app.use(...)
360
328
  app.provide(...)
329
+ app.register(...)
361
330
  app.addResource(...)
362
331
  ```
363
332
 
364
- This keeps the production lifecycle graph deterministic.
333
+ Creating child scopes is allowed while the container is active. Resolving/creating scopes after container disposal is rejected.
365
334
 
366
- ## Relationship to 0.2.19 API freeze
335
+ ## Compatibility
367
336
 
368
- `0.2.19` froze the `0.2.x` package/API baseline. `0.3.0` intentionally establishes the next baseline by adding one public server-only entrypoint:
337
+ `0.3.1` is additive over `0.3.0`:
369
338
 
370
339
  ```text
371
- bcp/application
340
+ previous baseline: 0.3.0
341
+ intentional breaking changes: false
372
342
  ```
373
343
 
374
- Existing `0.2.19` public entrypoints remain available. `0.3.0` does not intentionally remove them.
344
+ Existing applications that use only `app.services` continue to work. DI adoption can be incremental.
375
345
 
376
346
  ## Next platform work
377
347
 
378
- The next planned milestone is `0.3.1Dependency Injection & Service Container`, which can build typed service tokens, scopes and testing overrides on top of the shared service composition introduced here.
348
+ The next planned milestone is `0.3.2Module 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.3.0",
4
+ "versionTarget": "0.3.2",
5
5
  "releaseState": "unreleased",
6
6
  "sections": [
7
7
  {
@@ -58,9 +58,11 @@
58
58
  {
59
59
  "id": "runtime",
60
60
  "title": "Runtime & Infrastructure",
61
- "description": "Application composition, middleware, jobs, scheduling, workflows, event delivery, realtime channels, observability, deployment lifecycle, distributed caching, security and production hardening.",
61
+ "description": "Application composition, dependency injection, modules, middleware, jobs, scheduling, workflows, event delivery, realtime channels, observability, deployment lifecycle, distributed caching, security and production hardening.",
62
62
  "pages": [
63
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" },
65
+ { "route": "/docs/module-system-v2", "source": "module-system-v2.md", "title": "Module System v2" },
64
66
  { "route": "/docs/middleware", "source": "middleware.md", "title": "Middleware" },
65
67
  { "route": "/docs/hydration", "source": "hydration.md", "title": "Hydration" },
66
68
  { "route": "/docs/development-logging", "source": "development-logging.md", "title": "Logging" },
@@ -97,7 +99,7 @@
97
99
  { "route": "/docs/generators", "source": "generators.md", "title": "Project Generators" },
98
100
  { "route": "/docs/developer-tools", "source": "developer-tools.md", "title": "Doctor & Inspect" },
99
101
  { "route": "/docs/testing-platform", "source": "testing-platform.md", "title": "Testing Platform" },
100
- { "route": "/docs/plugin-module-platform", "source": "plugin-module-platform.md", "title": "Plugin & Module Platform" }
102
+ { "route": "/docs/plugin-module-platform", "source": "plugin-module-platform.md", "title": "Plugin & Legacy Module Platform" }
101
103
  ]
102
104
  },
103
105
  {
@@ -108,7 +110,7 @@
108
110
  { "route": "/docs/platform-contract", "source": "platform-contract.md", "title": "Framework Platform Contract" },
109
111
  { "route": "/docs/stability-api-freeze", "source": "stability-api-freeze.md", "title": "0.2 Stability & API Freeze" },
110
112
  { "route": "/docs/documentation-platform", "source": "documentation-platform.md", "title": "Documentation Platform" },
111
- { "route": "/docs/migration-0.3", "source": "migration-0.3.md", "title": "Migrating to 0.3.0" },
113
+ { "route": "/docs/migration-0.3", "source": "migration-0.3.md", "title": "Migrating to 0.3.x" },
112
114
  { "route": "/docs/migration-0.2", "source": "migration-0.2.md", "title": "Migrating to 0.2.x" }
113
115
  ]
114
116
  },
@@ -122,7 +124,9 @@
122
124
  }
123
125
  ],
124
126
  "releases": [
125
- { "route": "/releases/0.3.0", "source": "releases/0.3.0.md", "version": "0.3.0", "state": "unreleased" },
127
+ { "route": "/releases/0.3.2", "source": "releases/0.3.2.md", "version": "0.3.2", "state": "unreleased" },
128
+ { "route": "/releases/0.3.1", "source": "releases/0.3.1.md", "version": "0.3.1" },
129
+ { "route": "/releases/0.3.0", "source": "releases/0.3.0.md", "version": "0.3.0" },
126
130
  { "route": "/releases/0.2.19", "source": "releases/0.2.19.md", "version": "0.2.19" },
127
131
  { "route": "/releases/0.2.18", "source": "releases/0.2.18.md", "version": "0.2.18" },
128
132
  { "route": "/releases/0.2.17", "source": "releases/0.2.17.md", "version": "0.2.17" },