@celerity-sdk/core 0.4.0 → 0.5.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/README.md +281 -47
- package/dist/index.cjs +118 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -17
- package/dist/index.d.ts +21 -17
- package/dist/index.js +108 -44
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @celerity-sdk/core
|
|
2
2
|
|
|
3
|
-
Core SDK for building Celerity applications
|
|
3
|
+
Core SDK for building Celerity applications — decorators, dependency injection, layers, guards, handler pipelines, and the application factory.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,15 +8,16 @@ Core SDK for building Celerity applications - decorators, dependency injection,
|
|
|
8
8
|
pnpm add @celerity-sdk/core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Requires `reflect-metadata` and `@celerity-sdk/types` as peer dependencies.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Handler Types
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
The core package supports five handler types, each with class-based (decorator-first) and function-based (blueprint-first) styles.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
import { Controller, Get, Post, Body } from "@celerity-sdk/core";
|
|
17
|
+
### HTTP
|
|
19
18
|
|
|
19
|
+
```typescript
|
|
20
|
+
// Class-based
|
|
20
21
|
@Controller("/orders")
|
|
21
22
|
class OrdersHandler {
|
|
22
23
|
@Get("/{orderId}")
|
|
@@ -29,41 +30,157 @@ class OrdersHandler {
|
|
|
29
30
|
return { created: true };
|
|
30
31
|
}
|
|
31
32
|
}
|
|
33
|
+
|
|
34
|
+
// Function-based
|
|
35
|
+
const getHealth = httpGet("/health", (req, ctx) => ({ status: "ok" }));
|
|
36
|
+
const handler = createHttpHandler({}, (req, ctx) => ({ ok: true }));
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### WebSocket
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Class-based
|
|
43
|
+
@WebSocketController()
|
|
44
|
+
class ChatHandler {
|
|
45
|
+
@OnConnect()
|
|
46
|
+
connect(@ConnectionId() connId: string) {}
|
|
47
|
+
|
|
48
|
+
@OnMessage()
|
|
49
|
+
message(@ConnectionId() connId: string, @MessageBody() body: unknown) {}
|
|
50
|
+
|
|
51
|
+
@OnDisconnect()
|
|
52
|
+
disconnect(@ConnectionId() connId: string) {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Function-based
|
|
56
|
+
const wsHandler = createWebSocketHandler({ route: "$default" }, (msg, ctx) => {});
|
|
32
57
|
```
|
|
33
58
|
|
|
34
|
-
###
|
|
59
|
+
### Consumer
|
|
35
60
|
|
|
36
|
-
|
|
61
|
+
```typescript
|
|
62
|
+
// Class-based
|
|
63
|
+
@Consumer("ordersConsumer")
|
|
64
|
+
class OrderConsumer {
|
|
65
|
+
@MessageHandler()
|
|
66
|
+
async process(
|
|
67
|
+
@Messages(OrderSchema) messages: ValidatedConsumerMessage<Order>[],
|
|
68
|
+
): Promise<EventResult> {
|
|
69
|
+
return { success: true };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Function-based
|
|
74
|
+
const consumer = createConsumerHandler(
|
|
75
|
+
{ messageSchema: OrderSchema },
|
|
76
|
+
async (messages, ctx) => ({ success: true }),
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Schedule
|
|
37
81
|
|
|
38
82
|
```typescript
|
|
39
|
-
|
|
83
|
+
// Class-based — cross-cutting, works on any controller type
|
|
84
|
+
@Controller()
|
|
85
|
+
class MaintenanceTasks {
|
|
86
|
+
@ScheduleHandler("dailyCleanup")
|
|
87
|
+
async cleanup(@ScheduleInput() input: unknown): Promise<EventResult> {
|
|
88
|
+
return { success: true };
|
|
89
|
+
}
|
|
40
90
|
|
|
41
|
-
|
|
42
|
-
|
|
91
|
+
@ScheduleHandler("rate(1 hour)")
|
|
92
|
+
async sync(): Promise<EventResult> {
|
|
93
|
+
return { success: true };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
43
96
|
|
|
44
|
-
//
|
|
45
|
-
const
|
|
97
|
+
// Function-based
|
|
98
|
+
const scheduled = createScheduleHandler(
|
|
99
|
+
{ source: "dailyCleanup", schedule: "rate(1 day)" },
|
|
100
|
+
async (event, ctx) => ({ success: true }),
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Custom / Invocable
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Class-based
|
|
108
|
+
@Controller()
|
|
109
|
+
class Reports {
|
|
110
|
+
@Invoke("generateReport")
|
|
111
|
+
async generate(@Payload() data: unknown) {
|
|
112
|
+
return { reportId: "r-1" };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Function-based
|
|
117
|
+
const custom = createCustomHandler(
|
|
118
|
+
{ name: "generateReport" },
|
|
119
|
+
async (payload, ctx) => ({ reportId: "r-1" }),
|
|
120
|
+
);
|
|
46
121
|
```
|
|
47
122
|
|
|
48
123
|
## Decorators
|
|
49
124
|
|
|
125
|
+
### Class Decorators
|
|
126
|
+
|
|
50
127
|
| Decorator | Purpose |
|
|
51
128
|
|---|---|
|
|
52
|
-
| `@Controller(prefix)` |
|
|
53
|
-
| `@
|
|
54
|
-
| `@
|
|
55
|
-
| `@Injectable()` | Marks a class for DI |
|
|
56
|
-
| `@Inject(token)` | Overrides constructor parameter injection token |
|
|
57
|
-
| `@Module(metadata)` | Defines a module with controllers, providers, imports, and exports |
|
|
129
|
+
| `@Controller(prefix?)` | HTTP handler controller with optional path prefix |
|
|
130
|
+
| `@WebSocketController()` | WebSocket handler controller |
|
|
131
|
+
| `@Consumer(source?)` | Consumer controller with optional blueprint resource name |
|
|
132
|
+
| `@Injectable()` | Marks a class for DI registration |
|
|
58
133
|
| `@Guard(name)` | Declares a class as a named custom guard |
|
|
134
|
+
| `@Module(metadata)` | Defines a module with controllers, providers, imports, and exports |
|
|
135
|
+
|
|
136
|
+
### HTTP Method Decorators
|
|
137
|
+
|
|
138
|
+
| Decorator | Purpose |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `@Get(path?)`, `@Post(path?)`, `@Put(path?)`, `@Patch(path?)`, `@Delete(path?)`, `@Head(path?)`, `@Options(path?)` | HTTP method routing on controller methods |
|
|
141
|
+
|
|
142
|
+
### WebSocket Event Decorators
|
|
143
|
+
|
|
144
|
+
| Decorator | Purpose |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `@OnConnect()` | Handles `$connect` events |
|
|
147
|
+
| `@OnMessage(route?)` | Handles messages (custom route or `$default`) |
|
|
148
|
+
| `@OnDisconnect()` | Handles `$disconnect` events |
|
|
149
|
+
|
|
150
|
+
### Other Handler Decorators
|
|
151
|
+
|
|
152
|
+
| Decorator | Purpose |
|
|
153
|
+
|---|---|
|
|
154
|
+
| `@MessageHandler(route?)` | Consumer message handler with optional routing key |
|
|
155
|
+
| `@ScheduleHandler(arg?)` | Schedule handler — string or `{ source?, schedule? }` |
|
|
156
|
+
| `@Invoke(name)` | Custom invocable handler |
|
|
157
|
+
|
|
158
|
+
### Cross-Cutting Decorators
|
|
159
|
+
|
|
160
|
+
| Decorator | Purpose |
|
|
161
|
+
|---|---|
|
|
59
162
|
| `@ProtectedBy(...guards)` | Declares guard requirements (class or method level) |
|
|
60
163
|
| `@Public()` | Opts a method out of guard protection |
|
|
61
|
-
| `@UseLayer(
|
|
164
|
+
| `@UseLayer(...layers)` / `@UseLayers(layers)` | Attaches layers to handler or controller |
|
|
165
|
+
| `@UseResource(...names)` / `@UseResources(names)` | Declares blueprint resource dependencies |
|
|
62
166
|
| `@SetMetadata(key, value)` / `@Action(name)` | Attaches custom metadata |
|
|
167
|
+
| `@Inject(token)` | Overrides DI token for a constructor parameter |
|
|
168
|
+
|
|
169
|
+
### Parameter Decorators
|
|
170
|
+
|
|
171
|
+
| Handler Type | Decorators |
|
|
172
|
+
|---|---|
|
|
173
|
+
| HTTP | `@Body(schema?)`, `@Query(key?, schema?)`, `@Param(key?, schema?)`, `@Headers(key?, schema?)`, `@Auth()`, `@Token()`, `@Req()`, `@Cookies(key?)`, `@RequestId()` |
|
|
174
|
+
| WebSocket | `@ConnectionId()`, `@MessageBody(schema?)`, `@MessageId()`, `@RequestContext()`, `@EventType()` |
|
|
175
|
+
| Consumer | `@Messages(schema?)`, `@EventInput()`, `@Vendor()`, `@ConsumerTraceContext()` |
|
|
176
|
+
| Schedule | `@ScheduleInput(schema?)`, `@ScheduleId()`, `@ScheduleExpression()`, `@ScheduleEventInputParam()` |
|
|
177
|
+
| Custom | `@Payload(schema?)`, `@InvokeContext()` |
|
|
178
|
+
|
|
179
|
+
Parameter decorators that accept a `schema` use any Zod-compatible `Schema<T>` (`{ parse(data): T }`) for automatic validation.
|
|
63
180
|
|
|
64
181
|
## Dependency Injection
|
|
65
182
|
|
|
66
|
-
The DI container supports class, factory, and value providers with automatic constructor injection.
|
|
183
|
+
The DI container supports class, factory, and value providers with automatic constructor injection, lazy resolution, and circular dependency detection.
|
|
67
184
|
|
|
68
185
|
```typescript
|
|
69
186
|
@Injectable()
|
|
@@ -72,20 +189,41 @@ class OrderService {
|
|
|
72
189
|
}
|
|
73
190
|
|
|
74
191
|
@Module({
|
|
75
|
-
providers: [
|
|
192
|
+
providers: [
|
|
193
|
+
OrderService,
|
|
194
|
+
{ provide: DB_TOKEN, useFactory: () => new DatabaseClient(), onClose: (db) => db.close() },
|
|
195
|
+
],
|
|
76
196
|
controllers: [OrdersHandler],
|
|
77
197
|
})
|
|
78
198
|
class AppModule {}
|
|
79
199
|
```
|
|
80
200
|
|
|
201
|
+
Provider types:
|
|
202
|
+
|
|
203
|
+
| Type | Registration |
|
|
204
|
+
|---|---|
|
|
205
|
+
| Class | `@Injectable()` class or `{ provide: token, useClass: MyClass }` |
|
|
206
|
+
| Factory | `{ provide: token, useFactory: (...deps) => value, inject?: [tokens] }` |
|
|
207
|
+
| Value | `{ provide: token, useValue: value }` |
|
|
208
|
+
|
|
209
|
+
All providers support an optional `onClose` hook for shutdown cleanup. The container also auto-detects `close`, `end`, `quit`, `disconnect`, and `destroy` methods.
|
|
210
|
+
|
|
81
211
|
## Layers
|
|
82
212
|
|
|
83
|
-
Layers are the cross-cutting mechanism (
|
|
213
|
+
Layers are the cross-cutting mechanism (similar to middleware). They wrap handler execution in a composable pipeline.
|
|
84
214
|
|
|
85
215
|
```typescript
|
|
86
|
-
|
|
216
|
+
class LoggingLayer implements CelerityLayer {
|
|
217
|
+
async handle(ctx: BaseHandlerContext, next: NextFunction) {
|
|
218
|
+
console.log("before");
|
|
219
|
+
const result = await next();
|
|
220
|
+
console.log("after");
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
87
224
|
|
|
88
225
|
@Controller("/orders")
|
|
226
|
+
@UseLayer(LoggingLayer)
|
|
89
227
|
class OrdersHandler {
|
|
90
228
|
@Post("/")
|
|
91
229
|
@UseLayer(validate({ body: orderSchema }))
|
|
@@ -95,54 +233,150 @@ class OrdersHandler {
|
|
|
95
233
|
}
|
|
96
234
|
```
|
|
97
235
|
|
|
236
|
+
Layer execution order: `[system layers] → [app layers] → [class layers] → [method layers] → handler`.
|
|
237
|
+
|
|
238
|
+
The built-in `validate()` layer factory supports schema validation for all handler types:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
validate({ body: schema }) // HTTP body
|
|
242
|
+
validate({ query: schema }) // HTTP query
|
|
243
|
+
validate({ consumerMessage: schema }) // Consumer messages
|
|
244
|
+
validate({ scheduleInput: schema }) // Schedule input
|
|
245
|
+
validate({ customPayload: schema }) // Custom payload
|
|
246
|
+
validate({ wsMessageBody: schema }) // WebSocket message body
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Guards
|
|
250
|
+
|
|
251
|
+
Guards are declarative — they annotate handlers with protection requirements but do not execute in the Node.js process. Guard enforcement happens at the Rust runtime layer (containers) or API Gateway (serverless).
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
@Guard("jwt")
|
|
255
|
+
class JwtGuard {
|
|
256
|
+
async check(req: GuardHandlerRequest, ctx: GuardHandlerContext) {
|
|
257
|
+
return { sub: "user-1", role: "admin" };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@Controller("/api")
|
|
262
|
+
@ProtectedBy("jwt")
|
|
263
|
+
class ApiController {
|
|
264
|
+
@Public()
|
|
265
|
+
@Get("/health")
|
|
266
|
+
health() { return { ok: true }; }
|
|
267
|
+
|
|
268
|
+
@Get("/secret")
|
|
269
|
+
secret(@Auth() claims: unknown) { return claims; }
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Function-based guards: `createGuard({ name: "jwt" }, async (req, ctx) => claims)`.
|
|
274
|
+
|
|
98
275
|
## Application Factory
|
|
99
276
|
|
|
100
277
|
```typescript
|
|
101
278
|
import { CelerityFactory } from "@celerity-sdk/core";
|
|
102
279
|
|
|
280
|
+
// Auto-detects platform from CELERITY_RUNTIME_PLATFORM env var
|
|
103
281
|
const app = await CelerityFactory.create(AppModule);
|
|
104
|
-
// Auto-detects platform from CELERITY_PLATFORM env var
|
|
105
282
|
```
|
|
106
283
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
When a handler is invoked by ID (e.g. from a blueprint's `spec.handler` field), the SDK resolves it using a multi-step strategy:
|
|
110
|
-
|
|
111
|
-
1. **Direct registry ID match** - looks up the handler by its explicit `id` (set via decorator or `createHttpHandler`).
|
|
112
|
-
2. **Module resolution fallback** - if the direct lookup fails, the ID is treated as a module reference and dynamically imported:
|
|
113
|
-
- `"handlers.hello"` - named export `hello` from module `handlers`
|
|
114
|
-
- `"handlers"` - default export from module `handlers`
|
|
115
|
-
- `"app.module"` - tries named export split first (`module: "app"`, `export: "module"`), then falls back to default export from module `app.module`
|
|
116
|
-
3. **Path/method routing** - if both ID-based lookups fail, falls back to matching the incoming request's HTTP method and path against the registry.
|
|
284
|
+
Three application types:
|
|
117
285
|
|
|
118
|
-
|
|
286
|
+
| Class | Purpose |
|
|
287
|
+
|---|---|
|
|
288
|
+
| `CelerityApplication` | Runtime container application (default) |
|
|
289
|
+
| `ServerlessApplication` | Serverless adapter (created when `adapter` option provided) |
|
|
290
|
+
| `TestingApplication` | Lightweight app for unit testing handlers |
|
|
119
291
|
|
|
120
|
-
|
|
292
|
+
## Module System
|
|
121
293
|
|
|
122
|
-
|
|
294
|
+
Modules organize controllers, providers, and sub-modules into a dependency graph.
|
|
123
295
|
|
|
124
|
-
|
|
296
|
+
```typescript
|
|
297
|
+
@Module({
|
|
298
|
+
imports: [DatabaseModule, AuthModule],
|
|
299
|
+
controllers: [UserController, OrderController],
|
|
300
|
+
providers: [UserService],
|
|
301
|
+
functionHandlers: [healthCheck],
|
|
302
|
+
guards: [JwtGuard],
|
|
303
|
+
})
|
|
304
|
+
class AppModule {}
|
|
305
|
+
```
|
|
125
306
|
|
|
126
307
|
## Testing
|
|
127
308
|
|
|
128
309
|
```typescript
|
|
129
|
-
import {
|
|
310
|
+
import { CelerityFactory, mockRequest, mockConsumerEvent, mockScheduleEvent } from "@celerity-sdk/core";
|
|
311
|
+
|
|
312
|
+
const app = await CelerityFactory.createTestingApp(AppModule);
|
|
313
|
+
|
|
314
|
+
// HTTP
|
|
315
|
+
const response = await app.injectHttp(mockRequest({ method: "GET", path: "/orders/1" }));
|
|
130
316
|
|
|
131
|
-
|
|
132
|
-
const
|
|
317
|
+
// Consumer
|
|
318
|
+
const result = await app.injectConsumer("tag", mockConsumerEvent("tag"));
|
|
319
|
+
|
|
320
|
+
// Schedule
|
|
321
|
+
const result = await app.injectSchedule("tag", mockScheduleEvent("tag"));
|
|
322
|
+
|
|
323
|
+
// Custom
|
|
324
|
+
const result = await app.injectCustom("generateReport", { type: "monthly" });
|
|
325
|
+
|
|
326
|
+
// WebSocket
|
|
327
|
+
await app.injectWebSocket("$default", mockWebSocketMessage());
|
|
133
328
|
```
|
|
134
329
|
|
|
330
|
+
## Handler Resolution
|
|
331
|
+
|
|
332
|
+
When a handler is invoked by ID (e.g. from a blueprint's `spec.handler` field), the SDK resolves it using a multi-step strategy:
|
|
333
|
+
|
|
334
|
+
1. **Direct registry ID match** — looks up the handler by its explicit `id`.
|
|
335
|
+
2. **Module resolution fallback** — treats the ID as a module reference and dynamically imports it (e.g. `"handlers.hello"` → named export `hello` from module `handlers`).
|
|
336
|
+
3. **Path/method routing** — falls back to matching the incoming request's HTTP method and path against the registry.
|
|
337
|
+
|
|
338
|
+
## HTTP Exceptions
|
|
339
|
+
|
|
340
|
+
All extend `HttpException(statusCode, message, details?)` and are caught by the HTTP pipeline to return structured error responses:
|
|
341
|
+
|
|
342
|
+
| Exception | Status |
|
|
343
|
+
|---|---|
|
|
344
|
+
| `BadRequestException` | 400 |
|
|
345
|
+
| `UnauthorizedException` | 401 |
|
|
346
|
+
| `ForbiddenException` | 403 |
|
|
347
|
+
| `NotFoundException` | 404 |
|
|
348
|
+
| `MethodNotAllowedException` | 405 |
|
|
349
|
+
| `NotAcceptableException` | 406 |
|
|
350
|
+
| `ConflictException` | 409 |
|
|
351
|
+
| `GoneException` | 410 |
|
|
352
|
+
| `UnprocessableEntityException` | 422 |
|
|
353
|
+
| `TooManyRequestsException` | 429 |
|
|
354
|
+
| `InternalServerErrorException` | 500 |
|
|
355
|
+
| `NotImplementedException` | 501 |
|
|
356
|
+
| `BadGatewayException` | 502 |
|
|
357
|
+
| `ServiceUnavailableException` | 503 |
|
|
358
|
+
| `GatewayTimeoutException` | 504 |
|
|
359
|
+
|
|
135
360
|
## Advanced Exports
|
|
136
361
|
|
|
137
|
-
|
|
362
|
+
For adapter authors building custom serverless or runtime integrations:
|
|
138
363
|
|
|
139
364
|
| Export | Purpose |
|
|
140
365
|
|---|---|
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
366
|
+
| `HandlerRegistry` | Handler registry with route and ID-based lookups |
|
|
367
|
+
| `resolveHandlerByModuleRef` | Resolve a handler ID as a module reference via dynamic import |
|
|
368
|
+
| `executeHttpPipeline` | Execute the HTTP layer + handler pipeline |
|
|
369
|
+
| `executeWebSocketPipeline` | Execute the WebSocket pipeline |
|
|
370
|
+
| `executeConsumerPipeline` | Execute the consumer pipeline |
|
|
371
|
+
| `executeSchedulePipeline` | Execute the schedule pipeline |
|
|
372
|
+
| `executeCustomPipeline` | Execute the custom handler pipeline |
|
|
373
|
+
| `executeGuardPipeline` | Execute the guard pipeline |
|
|
374
|
+
| `bootstrapForRuntime` | Bootstrap for the Celerity runtime host |
|
|
145
375
|
| `mapRuntimeRequest` / `mapToRuntimeResponse` | Runtime request/response mappers |
|
|
376
|
+
| `mapWebSocketMessage` | Runtime WebSocket message mapper |
|
|
377
|
+
| `mapConsumerEventInput` | Runtime consumer event mapper |
|
|
378
|
+
| `mapScheduleEventInput` | Runtime schedule event mapper |
|
|
379
|
+
| `mapToNapiEventResult` | Runtime EventResult mapper |
|
|
146
380
|
|
|
147
381
|
## Part of the Celerity Framework
|
|
148
382
|
|