@getsoda/core 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +76 -2
- package/dist/app/app.d.ts +70 -0
- package/dist/app/app.d.ts.map +1 -0
- package/dist/app/app.js +173 -0
- package/dist/app/app.js.map +1 -0
- package/dist/app/hot.d.ts +33 -0
- package/dist/app/hot.d.ts.map +1 -0
- package/dist/app/hot.js +46 -0
- package/dist/app/hot.js.map +1 -0
- package/dist/app/index.d.ts +7 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +6 -0
- package/dist/app/index.js.map +1 -0
- package/dist/di/container.d.ts +93 -0
- package/dist/di/container.d.ts.map +1 -0
- package/dist/di/container.js +364 -0
- package/dist/di/container.js.map +1 -0
- package/dist/di/context.d.ts +42 -0
- package/dist/di/context.d.ts.map +1 -0
- package/dist/di/context.js +36 -0
- package/dist/di/context.js.map +1 -0
- package/dist/di/decorators.d.ts +94 -0
- package/dist/di/decorators.d.ts.map +1 -0
- package/dist/di/decorators.js +58 -0
- package/dist/di/decorators.js.map +1 -0
- package/dist/di/errors.d.ts +30 -0
- package/dist/di/errors.d.ts.map +1 -0
- package/dist/di/errors.js +18 -0
- package/dist/di/errors.js.map +1 -0
- package/dist/di/graph.d.ts +68 -0
- package/dist/di/graph.d.ts.map +1 -0
- package/dist/di/graph.js +250 -0
- package/dist/di/graph.js.map +1 -0
- package/dist/di/index.d.ts +18 -0
- package/dist/di/index.d.ts.map +1 -0
- package/dist/di/index.js +11 -0
- package/dist/di/index.js.map +1 -0
- package/dist/di/lifecycle.d.ts +40 -0
- package/dist/di/lifecycle.d.ts.map +1 -0
- package/dist/di/lifecycle.js +57 -0
- package/dist/di/lifecycle.js.map +1 -0
- package/dist/di/provider.d.ts +121 -0
- package/dist/di/provider.d.ts.map +1 -0
- package/dist/di/provider.js +116 -0
- package/dist/di/provider.js.map +1 -0
- package/dist/di/token.d.ts +58 -0
- package/dist/di/token.d.ts.map +1 -0
- package/dist/di/token.js +39 -0
- package/dist/di/token.js.map +1 -0
- package/dist/di/utils.d.ts +23 -0
- package/dist/di/utils.d.ts.map +1 -0
- package/dist/di/utils.js +34 -0
- package/dist/di/utils.js.map +1 -0
- package/dist/di/visibility.d.ts +32 -0
- package/dist/di/visibility.d.ts.map +1 -0
- package/dist/di/visibility.js +77 -0
- package/dist/di/visibility.js.map +1 -0
- package/dist/http/arguments.d.ts +51 -0
- package/dist/http/arguments.d.ts.map +1 -0
- package/dist/http/arguments.js +48 -0
- package/dist/http/arguments.js.map +1 -0
- package/dist/http/body.d.ts +26 -0
- package/dist/http/body.d.ts.map +1 -0
- package/dist/http/body.js +66 -0
- package/dist/http/body.js.map +1 -0
- package/dist/http/context.d.ts +74 -0
- package/dist/http/context.d.ts.map +1 -0
- package/dist/http/context.js +64 -0
- package/dist/http/context.js.map +1 -0
- package/dist/http/decorators.d.ts +137 -0
- package/dist/http/decorators.d.ts.map +1 -0
- package/dist/http/decorators.js +127 -0
- package/dist/http/decorators.js.map +1 -0
- package/dist/http/enhancers.d.ts +121 -0
- package/dist/http/enhancers.d.ts.map +1 -0
- package/dist/http/enhancers.js +126 -0
- package/dist/http/enhancers.js.map +1 -0
- package/dist/http/errors.d.ts +24 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/http/errors.js +17 -0
- package/dist/http/errors.js.map +1 -0
- package/dist/http/exceptions.d.ts +260 -0
- package/dist/http/exceptions.d.ts.map +1 -0
- package/dist/http/exceptions.js +77 -0
- package/dist/http/exceptions.js.map +1 -0
- package/dist/http/index.d.ts +24 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +13 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/parsers.d.ts +47 -0
- package/dist/http/parsers.d.ts.map +1 -0
- package/dist/http/parsers.js +55 -0
- package/dist/http/parsers.js.map +1 -0
- package/dist/http/pipeline.d.ts +61 -0
- package/dist/http/pipeline.d.ts.map +1 -0
- package/dist/http/pipeline.js +165 -0
- package/dist/http/pipeline.js.map +1 -0
- package/dist/http/response.d.ts +21 -0
- package/dist/http/response.d.ts.map +1 -0
- package/dist/http/response.js +52 -0
- package/dist/http/response.js.map +1 -0
- package/dist/http/routes.d.ts +54 -0
- package/dist/http/routes.d.ts.map +1 -0
- package/dist/http/routes.js +191 -0
- package/dist/http/routes.js.map +1 -0
- package/dist/http/schema.d.ts +61 -0
- package/dist/http/schema.d.ts.map +1 -0
- package/dist/http/schema.js +64 -0
- package/dist/http/schema.js.map +1 -0
- package/dist/http/types.d.ts +32 -0
- package/dist/http/types.d.ts.map +1 -0
- package/dist/http/types.js +5 -0
- package/dist/http/types.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/routing/compiler.d.ts +34 -0
- package/dist/routing/compiler.d.ts.map +1 -0
- package/dist/routing/compiler.js +336 -0
- package/dist/routing/compiler.js.map +1 -0
- package/dist/routing/decode.d.ts +24 -0
- package/dist/routing/decode.d.ts.map +1 -0
- package/dist/routing/decode.js +107 -0
- package/dist/routing/decode.js.map +1 -0
- package/dist/routing/dispatch.d.ts +68 -0
- package/dist/routing/dispatch.d.ts.map +1 -0
- package/dist/routing/dispatch.js +207 -0
- package/dist/routing/dispatch.js.map +1 -0
- package/dist/routing/errors.d.ts +24 -0
- package/dist/routing/errors.d.ts.map +1 -0
- package/dist/routing/errors.js +18 -0
- package/dist/routing/errors.js.map +1 -0
- package/dist/routing/index.d.ts +10 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +8 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/path.d.ts +45 -0
- package/dist/routing/path.d.ts.map +1 -0
- package/dist/routing/path.js +182 -0
- package/dist/routing/path.js.map +1 -0
- package/dist/routing/shape.d.ts +65 -0
- package/dist/routing/shape.d.ts.map +1 -0
- package/dist/routing/shape.js +148 -0
- package/dist/routing/shape.js.map +1 -0
- package/dist/routing/types.d.ts +81 -0
- package/dist/routing/types.d.ts.map +1 -0
- package/dist/routing/types.js +19 -0
- package/dist/routing/types.js.map +1 -0
- package/dist/utils/name.d.ts +15 -0
- package/dist/utils/name.d.ts.map +1 -0
- package/dist/utils/name.js +15 -0
- package/dist/utils/name.js.map +1 -0
- package/dist/utils/object.d.ts +14 -0
- package/dist/utils/object.d.ts.map +1 -0
- package/dist/utils/object.js +14 -0
- package/dist/utils/object.js.map +1 -0
- package/dist/utils/promise.d.ts +9 -0
- package/dist/utils/promise.d.ts.map +1 -0
- package/dist/utils/promise.js +9 -0
- package/dist/utils/promise.js.map +1 -0
- package/package.json +55 -4
- package/src/app/app.ts +252 -0
- package/src/app/hot.ts +60 -0
- package/src/app/index.ts +7 -0
- package/src/di/container.ts +552 -0
- package/src/di/context.ts +74 -0
- package/src/di/decorators.ts +130 -0
- package/src/di/errors.ts +46 -0
- package/src/di/graph.ts +353 -0
- package/src/di/index.ts +18 -0
- package/src/di/lifecycle.ts +83 -0
- package/src/di/provider.ts +231 -0
- package/src/di/token.ts +73 -0
- package/src/di/utils.ts +44 -0
- package/src/di/visibility.ts +107 -0
- package/src/http/arguments.ts +93 -0
- package/src/http/body.ts +81 -0
- package/src/http/context.ts +118 -0
- package/src/http/decorators.ts +262 -0
- package/src/http/enhancers.ts +244 -0
- package/src/http/errors.ts +35 -0
- package/src/http/exceptions.ts +109 -0
- package/src/http/index.ts +58 -0
- package/src/http/parsers.ts +110 -0
- package/src/http/pipeline.ts +247 -0
- package/src/http/response.ts +63 -0
- package/src/http/routes.ts +287 -0
- package/src/http/schema.ts +132 -0
- package/src/http/types.ts +44 -0
- package/src/index.ts +37 -0
- package/src/routing/compiler.ts +453 -0
- package/src/routing/decode.ts +134 -0
- package/src/routing/dispatch.ts +339 -0
- package/src/routing/errors.ts +30 -0
- package/src/routing/index.ts +10 -0
- package/src/routing/path.ts +244 -0
- package/src/routing/shape.ts +185 -0
- package/src/routing/types.ts +88 -0
- package/src/utils/name.ts +16 -0
- package/src/utils/object.ts +14 -0
- package/src/utils/promise.ts +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 falsea3
|
|
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,77 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Soda
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A class-based web framework for [Bun](https://bun.sh): modules, dependency injection and decorators like [Nest](https://nestjs.com), running on Bun's native router.
|
|
4
|
+
|
|
5
|
+
- **Standard decorators.** TC39 decorators, no `experimentalDecorators`, no `reflect-metadata`.
|
|
6
|
+
- **Native routing.** Routes compile into `Bun.serve({ routes })`; a route without guards adds ~0.1 ns to calling the handler.
|
|
7
|
+
- **Modules and DI.** `inject()`, providers, async factories, global and dynamic modules, lifecycle hooks. Graph errors are reported at startup with the full resolution chain.
|
|
8
|
+
- **Typed requests.** Path parameters are typed from the route path; bodies are validated with any [Standard Schema](https://standardschema.dev) library (zod, valibot, arktype).
|
|
9
|
+
- **Request pipeline.** Guards, interceptors and exception filters, as classes, instances or functions.
|
|
10
|
+
- **Lifecycle.** Graceful shutdown that drains requests before destroying services, shutdown signals, `bun --hot` support.
|
|
11
|
+
|
|
12
|
+
> Soda is in early development (0.x): the API may change between minor versions.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add @getsoda/core
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Bun 1.4 or later. No TypeScript flags are needed for decorators.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { Controller, Get, Module, Post, body, createApp, inject, int, param } from '@getsoda/core'
|
|
26
|
+
import type { Context } from '@getsoda/core'
|
|
27
|
+
import { z } from 'zod'
|
|
28
|
+
|
|
29
|
+
const CreateUser = z.object({ name: z.string().min(1) })
|
|
30
|
+
|
|
31
|
+
class UsersService {
|
|
32
|
+
readonly #users = new Map<number, { id: number; name: string }>()
|
|
33
|
+
|
|
34
|
+
find(id: number) {
|
|
35
|
+
return this.#users.get(id)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
create(name: string) {
|
|
39
|
+
const user = { id: this.#users.size + 1, name }
|
|
40
|
+
|
|
41
|
+
this.#users.set(user.id, user)
|
|
42
|
+
|
|
43
|
+
return user
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Controller('users')
|
|
48
|
+
class UsersController {
|
|
49
|
+
readonly #users = inject(UsersService)
|
|
50
|
+
|
|
51
|
+
// Ready values, Nest style
|
|
52
|
+
@Get(':id', [param('id', int())])
|
|
53
|
+
findOne(id: number) {
|
|
54
|
+
return this.#users.find(id)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Or read from the context where needed
|
|
58
|
+
@Post()
|
|
59
|
+
async create(ctx: Context) {
|
|
60
|
+
const { name } = await ctx.body(CreateUser)
|
|
61
|
+
|
|
62
|
+
return this.#users.create(name)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Module({ providers: [UsersService], controllers: [UsersController] })
|
|
67
|
+
class AppModule {}
|
|
68
|
+
|
|
69
|
+
const app = await createApp(AppModule)
|
|
70
|
+
|
|
71
|
+
app.enableShutdownSignals()
|
|
72
|
+
await app.listen(3000)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* The application: container, routes and server, started and stopped together.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* const app = await createApp(AppModule, { prefix: '/api' })
|
|
7
|
+
*
|
|
8
|
+
* app.enableShutdownSignals()
|
|
9
|
+
* await app.listen(3000)
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Startup creates the container (every singleton, every `onInit`), then the
|
|
13
|
+
* guards, interceptors and filters (their `onInit` too), and only then accepts
|
|
14
|
+
* requests.
|
|
15
|
+
*
|
|
16
|
+
* Shutdown stops accepting requests first and waits for the ones in flight,
|
|
17
|
+
* then runs `onDestroy` (guards and the like, then the container). Nest closes
|
|
18
|
+
* the server after `onModuleDestroy`, so requests still arrive while services
|
|
19
|
+
* are being destroyed; here they cannot.
|
|
20
|
+
*/
|
|
21
|
+
import type { Container, ContainerOptions, ModuleImport } from '../di/index.ts';
|
|
22
|
+
import type { ControllerRoutesOptions } from '../http/index.ts';
|
|
23
|
+
import type { CompiledRoutes, CompileOptions } from '../routing/index.ts';
|
|
24
|
+
/**
|
|
25
|
+
* Options of `createApp`.
|
|
26
|
+
*/
|
|
27
|
+
export interface AppOptions extends ContainerOptions, ControllerRoutesOptions, CompileOptions {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Where to listen. `port: 0` picks a free port.
|
|
31
|
+
*/
|
|
32
|
+
export interface ListenOptions {
|
|
33
|
+
readonly port?: number;
|
|
34
|
+
readonly hostname?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A created application.
|
|
38
|
+
*/
|
|
39
|
+
export interface App {
|
|
40
|
+
readonly container: Container;
|
|
41
|
+
/** Routes for `Bun.serve()`, when serving them yourself instead of calling `listen()`. */
|
|
42
|
+
readonly routes: CompiledRoutes;
|
|
43
|
+
/** The server once `listen()` resolved. */
|
|
44
|
+
readonly server: Bun.Server<undefined> | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Starts serving. Under `bun --hot`, a new version of the application
|
|
47
|
+
* listening on the same port takes the server over from the previous one,
|
|
48
|
+
* which is then destroyed (its server is not stopped: it is shared).
|
|
49
|
+
*/
|
|
50
|
+
listen(options?: number | ListenOptions): Promise<Bun.Server<undefined>>;
|
|
51
|
+
/**
|
|
52
|
+
* Graceful shutdown: refuses new requests, waits for those in flight, then
|
|
53
|
+
* runs `onDestroy` hooks. Safe to call more than once, even from a hook.
|
|
54
|
+
*/
|
|
55
|
+
close(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Closes the application on `SIGTERM` and `SIGINT` (or `signals`), then lets
|
|
58
|
+
* the signal terminate the process. Off by default, as Nest's `enableShutdownHooks()`.
|
|
59
|
+
*/
|
|
60
|
+
enableShutdownSignals(signals?: readonly NodeJS.Signals[]): this;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates the container and the routes, and runs every `onInit` hook.
|
|
64
|
+
*
|
|
65
|
+
* If anything fails, what was created is destroyed and the error is rethrown;
|
|
66
|
+
* if destroying fails too, everything comes as one `AggregateError`, startup
|
|
67
|
+
* error first, as `createContainer` does.
|
|
68
|
+
*/
|
|
69
|
+
export declare const createApp: (root: ModuleImport, options?: AppOptions) => Promise<App>;
|
|
70
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/app/app.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAG/E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAA;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAGzE;;GAEG;AACH,MAAM,WAAW,UAAW,SAAQ,gBAAgB,EAAE,uBAAuB,EAAE,cAAc;CAAG;AAEhG;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAKD;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,0FAA0F;IAC1F,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,SAAS,CAAA;IAClD;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAA;IACxE;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB;;;OAGG;IACH,qBAAqB,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAA;CACjE;AAyJD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,SAAgB,YAAY,YAAW,UAAU,KAAQ,OAAO,CAAC,GAAG,CAoBzF,CAAA"}
|
package/dist/app/app.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* The application: container, routes and server, started and stopped together.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* const app = await createApp(AppModule, { prefix: '/api' })
|
|
7
|
+
*
|
|
8
|
+
* app.enableShutdownSignals()
|
|
9
|
+
* await app.listen(3000)
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Startup creates the container (every singleton, every `onInit`), then the
|
|
13
|
+
* guards, interceptors and filters (their `onInit` too), and only then accepts
|
|
14
|
+
* requests.
|
|
15
|
+
*
|
|
16
|
+
* Shutdown stops accepting requests first and waits for the ones in flight,
|
|
17
|
+
* then runs `onDestroy` (guards and the like, then the container). Nest closes
|
|
18
|
+
* the server after `onModuleDestroy`, so requests still arrive while services
|
|
19
|
+
* are being destroyed; here they cannot.
|
|
20
|
+
*/
|
|
21
|
+
import { createContainer } from '../di/index.js';
|
|
22
|
+
import { destroyAll, initializeAll } from '../di/lifecycle.js';
|
|
23
|
+
import { buildRoutes } from '../http/index.js';
|
|
24
|
+
import { compileRoutes } from '../routing/index.js';
|
|
25
|
+
import { release, takeOver } from './hot.js';
|
|
26
|
+
/** Signals `enableShutdownSignals()` listens to by default. */
|
|
27
|
+
const DEFAULT_SIGNALS = ['SIGTERM', 'SIGINT'];
|
|
28
|
+
class Application {
|
|
29
|
+
container;
|
|
30
|
+
routes;
|
|
31
|
+
#server;
|
|
32
|
+
/** Set when a new version took the server over: it must not be stopped. */
|
|
33
|
+
#handedOver = false;
|
|
34
|
+
#closing;
|
|
35
|
+
#enhancers;
|
|
36
|
+
#signals = new Map();
|
|
37
|
+
constructor(container, routes, enhancers) {
|
|
38
|
+
this.container = container;
|
|
39
|
+
this.routes = routes;
|
|
40
|
+
this.#enhancers = enhancers;
|
|
41
|
+
}
|
|
42
|
+
get server() {
|
|
43
|
+
return this.#server;
|
|
44
|
+
}
|
|
45
|
+
async listen(options = {}) {
|
|
46
|
+
if (this.#closing !== undefined) {
|
|
47
|
+
throw new Error('Cannot listen: the application is closed');
|
|
48
|
+
}
|
|
49
|
+
if (this.#server !== undefined) {
|
|
50
|
+
throw new Error(`The application already listens on ${this.#server.url.href}`);
|
|
51
|
+
}
|
|
52
|
+
const { port = 0, hostname } = typeof options === 'number' ? { port: options } : options;
|
|
53
|
+
const server = Bun.serve({ port, hostname, ...this.routes });
|
|
54
|
+
this.#server = server;
|
|
55
|
+
if (server.port !== undefined) {
|
|
56
|
+
await takeOver(server.port, this);
|
|
57
|
+
}
|
|
58
|
+
return server;
|
|
59
|
+
}
|
|
60
|
+
/** Destroys this version without stopping the server a newer version now uses. */
|
|
61
|
+
handOver() {
|
|
62
|
+
this.#handedOver = true;
|
|
63
|
+
return this.close();
|
|
64
|
+
}
|
|
65
|
+
close() {
|
|
66
|
+
if (this.#closing === undefined) {
|
|
67
|
+
// Assigned before anything runs, so a hook calling close() gets this promise.
|
|
68
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
69
|
+
this.#closing = promise;
|
|
70
|
+
this.#shutdown().then(resolve, reject);
|
|
71
|
+
}
|
|
72
|
+
return this.#closing;
|
|
73
|
+
}
|
|
74
|
+
enableShutdownSignals(signals = DEFAULT_SIGNALS) {
|
|
75
|
+
for (const signal of signals) {
|
|
76
|
+
if (this.#signals.has(signal)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const listener = () => {
|
|
80
|
+
// A second signal while closing is ignored: the first one finishes the job.
|
|
81
|
+
if (this.#closing !== undefined) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.close().then(() => {
|
|
85
|
+
this.#removeSignalListeners();
|
|
86
|
+
// Terminate as the signal would have, exit code included.
|
|
87
|
+
process.kill(process.pid, signal);
|
|
88
|
+
}, (error) => {
|
|
89
|
+
console.error('Error during shutdown', error);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
this.#signals.set(signal, listener);
|
|
94
|
+
process.on(signal, listener);
|
|
95
|
+
}
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
async #shutdown() {
|
|
99
|
+
const server = this.#server;
|
|
100
|
+
try {
|
|
101
|
+
if (server !== undefined && !this.#handedOver) {
|
|
102
|
+
await server.stop();
|
|
103
|
+
}
|
|
104
|
+
await destroyEverything(this.#enhancers, this.container);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
// Whatever happened, signals get their default behaviour back.
|
|
108
|
+
this.#removeSignalListeners();
|
|
109
|
+
if (server?.port !== undefined) {
|
|
110
|
+
release(server.port, this);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
#removeSignalListeners() {
|
|
115
|
+
for (const [signal, listener] of this.#signals) {
|
|
116
|
+
process.off(signal, listener);
|
|
117
|
+
}
|
|
118
|
+
this.#signals.clear();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const errorsOf = (error) => (error instanceof AggregateError ? error.errors : [error]);
|
|
122
|
+
/**
|
|
123
|
+
* Destroys guards and the like, then the container; every hook runs.
|
|
124
|
+
* One failure is rethrown as is, several as an `AggregateError`.
|
|
125
|
+
*/
|
|
126
|
+
const destroyEverything = async (enhancers, container) => {
|
|
127
|
+
const errors = [];
|
|
128
|
+
try {
|
|
129
|
+
await destroyAll(enhancers);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
errors.push(...errorsOf(error));
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
await container.close();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
errors.push(...errorsOf(error));
|
|
139
|
+
}
|
|
140
|
+
if (errors.length === 1) {
|
|
141
|
+
throw errors[0];
|
|
142
|
+
}
|
|
143
|
+
if (errors.length > 1) {
|
|
144
|
+
throw new AggregateError(errors, `${errors.length} onDestroy hooks failed`);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Creates the container and the routes, and runs every `onInit` hook.
|
|
149
|
+
*
|
|
150
|
+
* If anything fails, what was created is destroyed and the error is rethrown;
|
|
151
|
+
* if destroying fails too, everything comes as one `AggregateError`, startup
|
|
152
|
+
* error first, as `createContainer` does.
|
|
153
|
+
*/
|
|
154
|
+
export const createApp = async (root, options = {}) => {
|
|
155
|
+
const container = await createContainer(root, options);
|
|
156
|
+
let enhancers = [];
|
|
157
|
+
try {
|
|
158
|
+
const built = buildRoutes(container, options);
|
|
159
|
+
enhancers = built.enhancers;
|
|
160
|
+
await initializeAll(enhancers);
|
|
161
|
+
return new Application(container, compileRoutes(built.routes, options), enhancers);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
try {
|
|
165
|
+
await destroyEverything(enhancers, container);
|
|
166
|
+
}
|
|
167
|
+
catch (destroyError) {
|
|
168
|
+
throw new AggregateError([error, ...errorsOf(destroyError)], 'Startup failed, and onDestroy hooks failed while rolling back');
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/app/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Hot reload support for `bun --hot`.
|
|
4
|
+
*
|
|
5
|
+
* Under `--hot`, Bun re-evaluates the changed modules in the same process:
|
|
6
|
+
* `globalThis` survives, a second `Bun.serve()` on the same port reuses the
|
|
7
|
+
* running server instead of failing, and there is no hook telling the old
|
|
8
|
+
* module it is unloaded (`import.meta.hot` does not exist on the server).
|
|
9
|
+
* So the previous application, with its timers and connections, would stay
|
|
10
|
+
* alive forever.
|
|
11
|
+
*
|
|
12
|
+
* Applications that listen are registered here by port, in `globalThis`; a new
|
|
13
|
+
* application listening on a port takes it over and destroys the previous one
|
|
14
|
+
* without stopping the server they share. Without `--hot`, two applications
|
|
15
|
+
* cannot listen on the same port, so this never happens.
|
|
16
|
+
*
|
|
17
|
+
* Requests the old version is still serving at that moment may meet its
|
|
18
|
+
* destroyed services: a development-only window, as with any hot reload.
|
|
19
|
+
*/
|
|
20
|
+
/** What the registry needs from an application. */
|
|
21
|
+
export interface HotApp {
|
|
22
|
+
/** Destroys the application without stopping its server. */
|
|
23
|
+
handOver(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Registers `app` as listening on `port`, destroying the application that listened there before.
|
|
27
|
+
*/
|
|
28
|
+
export declare const takeOver: (port: number, app: HotApp) => Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Unregisters `app` from `port`, unless another application already took it over.
|
|
31
|
+
*/
|
|
32
|
+
export declare const release: (port: number, app: HotApp) => void;
|
|
33
|
+
//# sourceMappingURL=hot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hot.d.ts","sourceRoot":"","sources":["../../src/app/hot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,mDAAmD;AACnD,MAAM,WAAW,MAAM;IACrB,4DAA4D;IAC5D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAYD;;GAEG;AACH,eAAO,MAAM,QAAQ,SAAgB,MAAM,OAAO,MAAM,KAAG,OAAO,CAAC,IAAI,CAStE,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,SAAU,MAAM,OAAO,MAAM,KAAG,IAMnD,CAAA"}
|
package/dist/app/hot.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Hot reload support for `bun --hot`.
|
|
4
|
+
*
|
|
5
|
+
* Under `--hot`, Bun re-evaluates the changed modules in the same process:
|
|
6
|
+
* `globalThis` survives, a second `Bun.serve()` on the same port reuses the
|
|
7
|
+
* running server instead of failing, and there is no hook telling the old
|
|
8
|
+
* module it is unloaded (`import.meta.hot` does not exist on the server).
|
|
9
|
+
* So the previous application, with its timers and connections, would stay
|
|
10
|
+
* alive forever.
|
|
11
|
+
*
|
|
12
|
+
* Applications that listen are registered here by port, in `globalThis`; a new
|
|
13
|
+
* application listening on a port takes it over and destroys the previous one
|
|
14
|
+
* without stopping the server they share. Without `--hot`, two applications
|
|
15
|
+
* cannot listen on the same port, so this never happens.
|
|
16
|
+
*
|
|
17
|
+
* Requests the old version is still serving at that moment may meet its
|
|
18
|
+
* destroyed services: a development-only window, as with any hot reload.
|
|
19
|
+
*/
|
|
20
|
+
const REGISTRY = Symbol.for('soda.app.listening');
|
|
21
|
+
const registry = () => {
|
|
22
|
+
const holder = globalThis;
|
|
23
|
+
holder[REGISTRY] ??= new Map();
|
|
24
|
+
return holder[REGISTRY];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Registers `app` as listening on `port`, destroying the application that listened there before.
|
|
28
|
+
*/
|
|
29
|
+
export const takeOver = async (port, app) => {
|
|
30
|
+
const apps = registry();
|
|
31
|
+
const previous = apps.get(port);
|
|
32
|
+
apps.set(port, app);
|
|
33
|
+
if (previous !== undefined && previous !== app) {
|
|
34
|
+
await previous.handOver();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Unregisters `app` from `port`, unless another application already took it over.
|
|
39
|
+
*/
|
|
40
|
+
export const release = (port, app) => {
|
|
41
|
+
const apps = registry();
|
|
42
|
+
if (apps.get(port) === app) {
|
|
43
|
+
apps.delete(port);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=hot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hot.js","sourceRoot":"","sources":["../../src/app/hot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAQH,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;AAEjD,MAAM,QAAQ,GAAG,GAAwB,EAAE;IACzC,MAAM,MAAM,GAAG,UAAsE,CAAA;IAErF,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,GAAG,EAAE,CAAA;IAE9B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAA;AACzB,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAAE,IAAY,EAAE,GAAW,EAAiB,EAAE;IACzE,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAE/B,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAEnB,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;QAC/C,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAA;IAC3B,CAAC;AACH,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,GAAW,EAAQ,EAAE;IACzD,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IAEvB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACnB,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/app/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* The container: creates every singleton at startup, runs lifecycle hooks, resolves tokens.
|
|
4
|
+
*
|
|
5
|
+
* Startup, in `createContainer`:
|
|
6
|
+
*
|
|
7
|
+
* 1. Build the module graph and validate it (awaiting `Promise<DynamicModule>` imports).
|
|
8
|
+
* 2. Run singleton factories, awaiting async ones. Their dependencies are
|
|
9
|
+
* declared in `inject: [...]`, so the async part of the graph is static.
|
|
10
|
+
* 3. Create every other singleton (providers, controllers, module classes),
|
|
11
|
+
* synchronously; `inject()` resolves dependencies, creating them on demand.
|
|
12
|
+
* 4. Call `onInit` on everything created, dependencies first.
|
|
13
|
+
*
|
|
14
|
+
* Any failure destroys what was created, in reverse order, and rejects.
|
|
15
|
+
* Nothing is resolved lazily on the request path: once the container is
|
|
16
|
+
* returned, every singleton exists.
|
|
17
|
+
*/
|
|
18
|
+
import type { ModuleClass, ModuleImport } from './decorators.ts';
|
|
19
|
+
import type { Provider } from './provider.ts';
|
|
20
|
+
import type { Constructor, Token } from './token.ts';
|
|
21
|
+
/**
|
|
22
|
+
* Options of `createContainer`.
|
|
23
|
+
*/
|
|
24
|
+
export interface ContainerOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Providers replacing those of the same token everywhere in the graph, for tests.
|
|
27
|
+
* An override that matches no provider is an error.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* await createContainer(AppModule, { overrides: [provide(Database, { useValue: fakeDatabase })] })
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
readonly overrides?: readonly Provider[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Options of `container.get()` and `container.runInContext()`.
|
|
37
|
+
*/
|
|
38
|
+
export interface ResolveOptions {
|
|
39
|
+
/** Module to resolve from, instead of the root module. */
|
|
40
|
+
readonly from?: ModuleClass;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A controller created by the container, with the module that owns it.
|
|
44
|
+
*/
|
|
45
|
+
export interface ControllerEntry {
|
|
46
|
+
readonly module: ModuleClass;
|
|
47
|
+
readonly controller: Constructor<unknown>;
|
|
48
|
+
readonly instance: unknown;
|
|
49
|
+
/**
|
|
50
|
+
* Runs `fn` where `inject()` resolves from the controller's module, as Nest
|
|
51
|
+
* resolves guards and interceptors from the module of their controller.
|
|
52
|
+
*/
|
|
53
|
+
readonly runInContext: <R>(fn: () => R) => R;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A started container.
|
|
57
|
+
*/
|
|
58
|
+
export interface Container {
|
|
59
|
+
/** Resolves `token` as `inject()` would inside the root module, or inside `options.from`. */
|
|
60
|
+
get<T>(token: Token<T>, options?: ResolveOptions & {
|
|
61
|
+
readonly optional?: false;
|
|
62
|
+
}): T;
|
|
63
|
+
get<T>(token: Token<T>, options: ResolveOptions & {
|
|
64
|
+
readonly optional: true;
|
|
65
|
+
}): T | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Runs `fn` where `inject()` works, as if it were created by the root module
|
|
68
|
+
* (or `options.from`): for functional guards, middleware and the like.
|
|
69
|
+
*/
|
|
70
|
+
runInContext<R>(fn: () => R, options?: ResolveOptions): R;
|
|
71
|
+
/** Every controller, in module order. */
|
|
72
|
+
readonly controllers: readonly ControllerEntry[];
|
|
73
|
+
/**
|
|
74
|
+
* Calls `onDestroy` in reverse creation order, then releases every instance.
|
|
75
|
+
* Calling it again, even from an `onDestroy` hook, returns the same promise.
|
|
76
|
+
*/
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Builds the graph rooted at `root`, creates every singleton and runs `onInit` hooks.
|
|
81
|
+
*
|
|
82
|
+
* ```ts
|
|
83
|
+
* const container = await createContainer(AppModule)
|
|
84
|
+
* const users = container.get(UsersService)
|
|
85
|
+
* await container.close()
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* Rejects with `DiError` on graph problems. If startup fails after something was
|
|
89
|
+
* created, `onDestroy` runs for it first and the startup error is rethrown; if
|
|
90
|
+
* hooks fail too, everything is rethrown as one `AggregateError`, startup error first.
|
|
91
|
+
*/
|
|
92
|
+
export declare const createContainer: (root: ModuleImport, options?: ContainerOptions) => Promise<Container>;
|
|
93
|
+
//# sourceMappingURL=container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/di/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAKhE,OAAO,KAAK,EAAE,QAAQ,EAAsB,MAAM,eAAe,CAAA;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAIpD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAA;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IACzC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;CAC7C;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,6FAA6F;IAC7F,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG,CAAC,CAAA;IACpF,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG;QAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,CAAC,GAAG,SAAS,CAAA;IAC7F;;;OAGG;IACH,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,CAAC,CAAA;IACzD,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,CAAA;IAChD;;;OAGG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAwaD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,SAAgB,YAAY,YAAW,gBAAgB,KAAQ,OAAO,CAAC,SAAS,CA0B3G,CAAA"}
|