@elysiumjs/core 0.8.0 → 0.11.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/CHANGELOG.md +227 -0
- package/README.md +62 -4
- package/dist/app.d.ts +126 -13
- package/dist/config.d.ts +74 -0
- package/dist/console.d.ts +46 -9
- package/dist/container.d.ts +76 -14
- package/dist/errors.d.ts +37 -2
- package/dist/filter.d.ts +78 -0
- package/dist/http-client.d.ts +80 -0
- package/dist/http.d.ts +153 -45
- package/dist/index.d.ts +24 -11
- package/dist/index.js +10 -8
- package/dist/interceptor.d.ts +80 -0
- package/dist/kernel.d.ts +15 -1
- package/dist/{event-wiring.d.ts → lifecycle-wiring.d.ts} +4 -4
- package/dist/{event.d.ts → lifecycle.d.ts} +26 -18
- package/dist/logger.d.ts +15 -0
- package/dist/middleware.d.ts +30 -8
- package/dist/module-context.d.ts +6 -0
- package/dist/module-graph.d.ts +3 -2
- package/dist/module.d.ts +18 -0
- package/dist/problem.d.ts +815 -0
- package/dist/rate-limit.d.ts +194 -0
- package/dist/resource.d.ts +77 -0
- package/dist/runtime.d.ts +148 -0
- package/dist/security.d.ts +159 -0
- package/dist/service.d.ts +51 -15
- package/dist/testing.d.ts +45 -2
- package/dist/token.d.ts +22 -0
- package/dist/user.d.ts +13 -0
- package/dist/utils.d.ts +3 -3
- package/dist/websocket.d.ts +42 -16
- package/package.json +24 -8
- package/dist/redis.d.ts +0 -86
- package/tsconfig.json +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,232 @@
|
|
|
1
1
|
# @elysiumjs/core
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#8](https://github.com/na2axl/elysiumjs/pull/8) [`6bc48fe`](https://github.com/na2axl/elysiumjs/commit/6bc48febd51f7090c582e73f349eaeff7261241c) Thanks [@na2axl](https://github.com/na2axl)! - `Application.build()` builds the root Elysia app without listening, for hosts that own the server (Cloudflare Workers) and for ahead-of-time compilation with `elysia/plugin/aot`. Modules are registered but boot on the first request (once, shared by concurrent requests), so building runs no `onInit` work. A boot failure fails the requests.
|
|
8
|
+
|
|
9
|
+
HTTP and websocket controllers in the server scope, and middlewares, are now built on their first request instead of when the app is built. `serve` still boots every module before listening.
|
|
10
|
+
|
|
11
|
+
`@elysiumjs/runtime-workers` gets a `check:aot` script, run in CI: it compiles a sample app with the AOT plugin for workerd, checks that the bundle holds no `new Function` or `eval`, and serves a validated route and the OpenAPI document under Node.js with code generation from strings disallowed.
|
|
12
|
+
|
|
13
|
+
- [#7](https://github.com/na2axl/elysiumjs/pull/7) [`e9c3d98`](https://github.com/na2axl/elysiumjs/commit/e9c3d98dd2714b69100215f7c8a80046da463148) Thanks [@na2axl](https://github.com/na2axl)! - Elysium runs on Elysia 2 (`elysia@^2.0.0-rc.1`), with TypeBox 1 (`typebox`), `@elysia/openapi` and `@elysia/opentelemetry`.
|
|
14
|
+
|
|
15
|
+
Breaking:
|
|
16
|
+
|
|
17
|
+
- `TSchema` and `Static` are re-exported from `typebox` instead of `@sinclair/typebox`; `Value` comes from `typebox/value` and `Value.Errors()` returns an array.
|
|
18
|
+
- `Application.onError()` receives a `ServerError` (exported from core). Elysia now names its errors in kebab case; the default error response keeps the upper snake case names (`VALIDATION`, `NOT_FOUND`, `UNKNOWN` for other errors).
|
|
19
|
+
- `AppHttpContext` is a plain interface with `decorator`, `store` and `derive` records (Elysia 2 renamed `resolve` to `derive`).
|
|
20
|
+
- Websocket controllers install Elysia's `websocket()` capability themselves.
|
|
21
|
+
|
|
22
|
+
A module or controller `onRequest` middleware that answers now stops the request: Elysia 2 ignores values returned from a transform hook, so the response is thrown as a status and sent as is, without going through `onError`.
|
|
23
|
+
|
|
24
|
+
`mnemosyne` builds date columns with `t.Codec` (TypeBox 1 dropped `Transform`). `argus` traces every route of the app it is installed on and ends each span once, keeping the error status when the request failed.
|
|
25
|
+
|
|
26
|
+
- [#5](https://github.com/na2axl/elysiumjs/pull/5) [`557a3ca`](https://github.com/na2axl/elysiumjs/commit/557a3cab7af8053a8bab9944e492e5022d91aa82) Thanks [@na2axl](https://github.com/na2axl)! - Core runs outside Bun. Environment variables, timers, the HTTP server, process exit and shutdown signals now come from a `Runtime` set with the new `runtime` option of `@Application.register()` or with `Runtime.use()`. Runtimes ship as packages, each exporting `runtime`: `@elysiumjs/runtime-bun`, `@elysiumjs/runtime-node` and `@elysiumjs/runtime-workers` (which also exports `createRuntime({ env })`).
|
|
27
|
+
|
|
28
|
+
Breaking: an application needs a runtime (add `runtime` from `@elysiumjs/runtime-bun`), and `Redis` and `RedisModule` moved from core to the new `@elysiumjs/redis` package, which also accepts a `client` factory for runtimes without `Bun.RedisClient`.
|
|
29
|
+
|
|
30
|
+
Console colours are plain ANSI codes instead of `Bun.color`, `node:readline` is loaded only when a prompt runs, and core's dist is built for the `node` target so `node:` imports keep their prefix. `check:runtime` scripts in core and runtime-workers fail when their bundles touch the `Bun` global.
|
|
31
|
+
|
|
32
|
+
`create-elysium` gets a runtime step: it installs the chosen runtime package and sets it in the generated app. Bun is selectable today; Node.js and Cloudflare Workers are listed as coming later.
|
|
33
|
+
|
|
34
|
+
- [#24](https://github.com/na2axl/elysiumjs/pull/24) [`ccc3f5b`](https://github.com/na2axl/elysiumjs/commit/ccc3f5bb904d1bf52d8c72ebf7ea8d10f617a8c4) Thanks [@na2axl](https://github.com/na2axl)! - `Service.ownersOf(service)` lists the modules that declare a service in their own scope, and `ModuleContext.outside(fn)` runs code outside of any module, so that lookups only see the global scope.
|
|
35
|
+
|
|
36
|
+
- [#19](https://github.com/na2axl/elysiumjs/pull/19) [`6789879`](https://github.com/na2axl/elysiumjs/commit/678987976ecfd46b534b65276e0de3f448977562) Thanks [@na2axl](https://github.com/na2axl)! - Domain errors as classes with a code, returned as values.
|
|
37
|
+
|
|
38
|
+
- `DomainError` is an Elysia `HTTPError`. Each error is a class made with `DomainError.for(code, status)`, which keeps its code and status as literal types, with `errors`, `retryAfterSeconds` (served as `retryAfter` and the Retry-After header) and extensions.
|
|
39
|
+
- Ready-made errors: `BadRequestError`, `ValidationFailedError`, `UnauthenticatedError`, `ForbiddenError`, `NotFoundError` and `ConflictError`.
|
|
40
|
+
- `ProblemCatalogue`, extended through declaration merging, lists the codes of an application; once it has keys, `DomainError.for()` takes only those and `ELYSIUM_PROBLEM_CODES`.
|
|
41
|
+
- Every problem carries `code`, `requestId` (from `problems.requestId`, a valid `x-request-id` header, or a random UUID) and `instance`. Its `type` is the kebab-case slug of the code, under `problems.typeBase`.
|
|
42
|
+
- Errors as values: a handler, a flow step, `UnitOfWork.run()` or a `@Transactional()` method that returns a DomainError rolls back and sends or returns it like a thrown one. `Commands.execute()` and `Queries.ask()` return the DomainError that refused the call, and a returned error joins the route's response type for typed clients.
|
|
43
|
+
|
|
44
|
+
- [#20](https://github.com/na2axl/elysiumjs/pull/20) [`87299fd`](https://github.com/na2axl/elysiumjs/commit/87299fd9da82d1a408ced1891c0c3d42991444e5) Thanks [@na2axl](https://github.com/na2axl)! - The framework event bus is `Lifecycle`: `Lifecycle.on`, `Lifecycle.once`, `Lifecycle.emit`, `Lifecycle.off`, `@Lifecycle.on()` and `@Lifecycle.once()`, with the `LifecycleEvent` and `LifecycleHandler` types. It carries the framework's own signals (`elysium:error`, app and server lifecycle, heracles queue events) and in-process listeners.
|
|
45
|
+
|
|
46
|
+
- [#22](https://github.com/na2axl/elysiumjs/pull/22) [`bee66bd`](https://github.com/na2axl/elysiumjs/commit/bee66bdc874b8f23fea356f7d3adf7555a48ca64) Thanks [@na2axl](https://github.com/na2axl)! - Decorators take their main identifier first and their options second.
|
|
47
|
+
|
|
48
|
+
- `@Http.controller(path, options)` and the route decorators `@Http.get(path, options)`, `post`, `put`, `patch`, `del`, `head`, `options`, `trace` and `sse`. The path defaults to `/`. `@Http.custom(method, path, options)` takes its method first. The options are `HttpControllerOptions` and `HttpRouteOptions`.
|
|
49
|
+
- `@Websocket.controller(path, options)`, where the options are those of the websocket server.
|
|
50
|
+
- `@Flow.controller(name, options)`, and `@Flow.step(name, options)` or `@Flow.step(options)` for a step named after its method.
|
|
51
|
+
- `@Lifecycle.on(event, options)` and `@Lifecycle.once(event, options)`, like their function forms `Lifecycle.on(event, handler)`.
|
|
52
|
+
- The styx controller stubs generate the same forms.
|
|
53
|
+
|
|
54
|
+
- [#10](https://github.com/na2axl/elysiumjs/pull/10) [`0057b67`](https://github.com/na2axl/elysiumjs/commit/0057b67df0e3abecab8a9accff6b55a6a64c21df) Thanks [@na2axl](https://github.com/na2axl)! - `styx routes:generate` writes the route types of an app to `src/routes.gen.ts`: an `AppRoutes` type for Eden (`treaty<AppRoutes>(url)`), built from the controllers of every module. Request types come from the method parameters decorated with `@Http.param`, `@Http.query` and `@Http.body`, and response types from the method return types. The file holds types only; requests are served by the decorated controllers. New projects run the command in their `typecheck` script and keep the file out of git.
|
|
55
|
+
|
|
56
|
+
`Http.describe(controller)` reports the routes of an HTTP controller, and `Application.moduleGraph()` builds the module graph of an app class without booting it.
|
|
57
|
+
|
|
58
|
+
- [#11](https://github.com/na2axl/elysiumjs/pull/11) [`632d77b`](https://github.com/na2axl/elysiumjs/commit/632d77b561fc43ade605fb69b06ac1b9b9a2ffc4) Thanks [@na2axl](https://github.com/na2axl)! - Services are identified by their class, or by the name given to `@Service.register({ name })`. Class names are never used as identifiers, so services resolve the same way in minified builds; `bun run check:minified` in core bundles a minified app and checks that every service resolves. `@Service.inject()` without an argument injects the class of the parameter type, and throws when the type is unknown. Factory services in a module's `services` take a class or a name in `name`.
|
|
59
|
+
|
|
60
|
+
`ServiceScope.REQUEST` gives one instance per HTTP request, shared by the request's middlewares, controller and handler. `Service.runInRequestScope(fn)` opens a request scope outside a route. Resolving a request-scoped service outside a request throws `OutsideRequestScopeError`, and a singleton (or a server-scoped controller) depending on one throws `ScopeMismatchError`.
|
|
61
|
+
|
|
62
|
+
`HermesModule` registers the logger by the `HermesLogger` class and as `logger`.
|
|
63
|
+
|
|
64
|
+
- [#13](https://github.com/na2axl/elysiumjs/pull/13) [`1ea417e`](https://github.com/na2axl/elysiumjs/commit/1ea417e3583250bc2f975d795f398b7ff736115d) Thanks [@na2axl](https://github.com/na2axl)! - Hardening of the runtimes, the container and the CLI, checked by a conformance suite that runs on Bun, Node.js and workerd.
|
|
65
|
+
|
|
66
|
+
Breaking changes:
|
|
67
|
+
|
|
68
|
+
- `Runtime` gains the required `argv()`, `write(stream, text)` and `terminal()` members: core reaches the host's arguments, output streams and terminal only through the runtime. Custom runtimes must implement them.
|
|
69
|
+
- The third parameter of `InteractsWithConsole.write` is now `'stdout' | 'stderr'`.
|
|
70
|
+
- The WebSocket options are narrowed to `idleTimeout`, `maxPayloadLength` and `perMessageDeflate`, and the `bun` WebSocket types are replaced by `ElysiumWebSocket`.
|
|
71
|
+
- Redis connections are `RedisConnection`s, a port any Redis client can implement, instead of `Bun.RedisClient`.
|
|
72
|
+
- `styx create @scope/name` creates the project in `name/`, and refuses project and module names with upper-case letters.
|
|
73
|
+
- Queue handlers no longer reject the batch: after the handlers ran, each message is acked or retried on its own. `@Workers.queue(name, { each: true })` receives one message at a time, so a failure retries only that message.
|
|
74
|
+
|
|
75
|
+
Fixes:
|
|
76
|
+
|
|
77
|
+
- Request-scoped services are destroyed when the request ends, also on failure, and an async `onInit` is awaited. A second dispose of a request scope waits for the one in progress, so a Worker's `waitUntil` covers an async `onDestroy`.
|
|
78
|
+
- A failed module or service `onInit` no longer breaks later requests: the boot is retried, and the module's event listeners are unwired.
|
|
79
|
+
- `@elysiumjs/runtime-node` serves through `node:http`, keeping the Host header authoritative.
|
|
80
|
+
- `styx create` validates names before creating files, ships `.gitignore`, declares the dependencies of created projects and imports their default controllers.
|
|
81
|
+
|
|
82
|
+
- [#14](https://github.com/na2axl/elysiumjs/pull/14) [`0956186`](https://github.com/na2axl/elysiumjs/commit/0956186db0ca5a83c0a6636aeb2a4c81a0033de4) Thanks [@na2axl](https://github.com/na2axl)! - The request pipeline: problem details, exception filters, interceptors, typed configuration, HTTP security options and an HTTP test client, checked on Bun, Node.js, workerd and AOT-compiled Workers.
|
|
83
|
+
|
|
84
|
+
Breaking changes:
|
|
85
|
+
|
|
86
|
+
- Error responses are RFC 9457 problem details (`application/problem+json`). Request validation errors answer 422 with an `errors` list of JSON pointers (`problems.validationStatus: 400` keeps 400). Server errors only show their message and stack in development. Thrown statuses (`c.status(...)`) are sent as they are.
|
|
87
|
+
- Client errors (4xx) are no longer reported to `onError`, the `elysium:error` event or the trace.
|
|
88
|
+
- `Runtime.serve` may return a handle with `close()`, which the application calls on shutdown.
|
|
89
|
+
- Request bodies are limited to 1 MiB by default (`http.bodyLimit`), also when chunked.
|
|
90
|
+
- The environment schema is validated when the app is built or run, with every error reported at once, instead of in the constructor.
|
|
91
|
+
|
|
92
|
+
Features:
|
|
93
|
+
|
|
94
|
+
- `DomainError`, with `status`, `type`, `title`, `detail` and extensions, and shortcuts such as `DomainError.notFound()`.
|
|
95
|
+
- Exception filters (`@Catch()`, `filters` on routes, controllers, modules and the app) and interceptors (`interceptors` on the same levels), with `TimingInterceptor`, `TransformInterceptor` and mnemosyne's `CacheInterceptor`.
|
|
96
|
+
- Typed configuration: `Secret` values, module `envSchema`s merged into the app's, `InvalidEnvironmentError`.
|
|
97
|
+
- `http` options: CORS, security headers, body limits and trusted proxies (`ctx.ip`). Runtimes report the client address through `clientAddress`.
|
|
98
|
+
- `createTestingHttpApp` and `createHttpClient` send requests to an app in memory, with `expectStatus()`.
|
|
99
|
+
|
|
100
|
+
Fixes:
|
|
101
|
+
|
|
102
|
+
- App-level `onRequest` middlewares run in the request scope.
|
|
103
|
+
- On Workers, the request scope of a streamed response is disposed when the body ends, not when `fetch` returns.
|
|
104
|
+
- `KeyvWorkersKV.clear()` stays within an operation budget (`maxOperations`).
|
|
105
|
+
- The Node.js bridge treats a destroyed socket as a client abort.
|
|
106
|
+
- Heracles performance metrics report through the Redis port (`connection` or `client`), not a Bun client.
|
|
107
|
+
- `styx routes:generate` names anonymous default controllers after their file; `styx create` pins `@elysiumjs/*` packages to the versions it was released with; invalid names are reported as Elysium errors.
|
|
108
|
+
|
|
109
|
+
- [#15](https://github.com/na2axl/elysiumjs/pull/15) [`fbcc7da`](https://github.com/na2axl/elysiumjs/commit/fbcc7dad127db29da42e7a4a5c2124966731cd88) Thanks [@na2axl](https://github.com/na2axl)! - Resources, queries, units of work and rate limits.
|
|
110
|
+
|
|
111
|
+
Breaking:
|
|
112
|
+
|
|
113
|
+
- A route with a `response` schema serializes its handler result through it: fields the schema does not declare are dropped, a `Date` becomes an ISO string (`YYYY-MM-DD` for `format: 'date'`), and an object with `toJSON()` becomes what it returns. Set `serialize: false` on a route to send the result as it is.
|
|
114
|
+
|
|
115
|
+
Added:
|
|
116
|
+
|
|
117
|
+
- `Resource.hidden(schema)` marks a field that never reaches responses nor the OpenAPI document. `Resource.serialize()` and `Resource.project()` are exported for use outside routes.
|
|
118
|
+
- Sparse fieldsets: `?fields=id,title` keeps only those fields of an object, of each item of an array, or of each item of a `data` array. An unknown or hidden field answers a 400 problem.
|
|
119
|
+
- Rate limits with `@RateLimit({ window, max, key?, store?, name? })` on routes and controllers (`@RateLimit(false)` turns one off), and a default limit and store in the app's `rateLimit` option, with fixed windows. Responses carry `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy`; a client over the limit gets a 429 problem with `Retry-After`. Counts are kept by a `RateLimitStore`: `MemoryRateLimitStore` (core), `RedisRateLimitStore` (`@elysiumjs/redis`) or `WorkersRateLimitStore` over a Cloudflare rate limiting binding (`@elysiumjs/runtime-workers`), which only decides whether a hit is over the limit, so its responses carry no `RateLimit-Reset`.
|
|
120
|
+
- `ModelResource(model, { hidden, pick, omit, extend })` (`@elysiumjs/mnemosyne`) builds a response schema from a model.
|
|
121
|
+
- `Query.define(model, spec)` declares the filters (`filter[field][op]=value`), sorts, page size and cursor a list accepts; `Query.schema()` gives its query schema, `Query.parse()` turns a request query into a `ParsedQuery` and answers a 400 problem with a pointer per mistake, and `Query.page()` / `Query.pageSchema()` build cursor pages (`{ data, nextCursor }`).
|
|
122
|
+
- `DrizzleRepository.list(query)` and `count(query)` run a `ParsedQuery` with keyset pagination (`@elysiumjs/mnemosyne-drizzle`), and `compileQuery()` gives its `where` and `orderBy`.
|
|
123
|
+
- Units of work: `UnitOfWork.run()` and the `@Transactional()` method decorator run work in one transaction, and `UnitOfWork.afterCommit()` / `afterRollback()` defer work, such as sending a message, until the outermost transaction has ended. `transactional` routes open a unit of work too.
|
|
124
|
+
|
|
125
|
+
- [#16](https://github.com/na2axl/elysiumjs/pull/16) [`ac96e55`](https://github.com/na2axl/elysiumjs/commit/ac96e55874b0ffe350b287c4278cd85afecb7576) Thanks [@na2axl](https://github.com/na2axl)! - Redis, databases, logs, jobs and builds on Bun, Node.js and Cloudflare Workers.
|
|
126
|
+
|
|
127
|
+
Breaking:
|
|
128
|
+
|
|
129
|
+
- The level shortcuts of `InteractsWithConsole` (`info`, `success`, `warning`, `error` and `debug`) write a tag (`[I]`, `[S]`, `[W]`, `[E]` and `[D]`, in color) before the message, which `InteractsWithConsole.useTags()` changes. `trace()` writes to stderr.
|
|
130
|
+
- Outside Bun, a Drizzle connection needs a `driver`. Without one, the `connection` and `client` props are a Bun SQL connection, which is only available on Bun; elsewhere the connection throws `MissingDriverError`, which names the drivers.
|
|
131
|
+
|
|
132
|
+
Added:
|
|
133
|
+
|
|
134
|
+
- Redis adapters (`@elysiumjs/redis`): `ioredis(create)` plugs an ioredis client into the Redis port on Node.js, and `upstash({ token, url?, fetch? })` talks to Upstash over its REST API, for Workers (no pub/sub). `bunRedis` explains which adapter to use when it runs outside Bun.
|
|
135
|
+
- Drizzle drivers (`@elysiumjs/mnemosyne-drizzle`): a `DrizzleDriver` port set with the `driver` prop of a connection, and drivers on subpaths so that only the chosen database client is imported: `/bun-sql` (`bunSql()`), `/postgres-js` (`postgresJs()`), `/node-postgres` (`nodePostgres()`) and `/hyperdrive` (`hyperdrive({ binding, driver })`, a client per Worker invocation over a Cloudflare Hyperdrive binding). `DrizzleModule` closes connections through their driver, and `migration:run` runs on any driver.
|
|
136
|
+
- `InteractsWithConsole.write()` and `Runtime.write()` take the level of the line (`OutputLevel`), which the level shortcuts pass. On Cloudflare Workers, a line is written with the console method of its level, so Workers Logs keep it.
|
|
137
|
+
- `JsonConsoleLogger` (`@elysiumjs/hermes`): JSON lines through the console of the runtime (warnings and errors on stderr), with levels, error serialization and `child()`. `HermesModule.with({ transport: 'console' | 'pino' })` picks it, and it is the default on Workers. `RequestLogInterceptor` writes one structured line per request: method, route, status, duration, client address and request id.
|
|
138
|
+
- Scheduled jobs (`@elysiumjs/heracles`): `@Schedule(cron, { timezone, args })` on a job class, a five-field cron parser (`Cron`, with lists, ranges, steps and names), and a `Scheduler` that dispatches the jobs on time on Bun and Node.js, taking a Redis lock per tick when a Redis connection is set. The `heracles:schedule` command runs it.
|
|
139
|
+
- `@elysiumjs/heracles-workers`: `CloudflareQueueTransport` sends jobs to a Cloudflare Queue (dispatch delays become `delaySeconds`), and `HeraclesWorkersModule` consumes them in the Worker's `queue` handler (ack on success, retry with a delay up to the job's retries) and dispatches the `@Schedule()` jobs whose expression is the cron of a trigger.
|
|
140
|
+
- Queue handlers (`@elysiumjs/runtime-workers`) can settle a message themselves with `ack()` or `retry({ delaySeconds })`; the messages they leave are acked when no handler failed on them.
|
|
141
|
+
- `styx build --target node` (`@elysiumjs/hephaestus`) bundles the app into one ES module for Node.js, with decorator metadata emitted at build time, run with `node dist/server.js serve`. `styx build --target workers` runs from the CLI, resolves packages to their browser entry, and leaves Node.js modules imported by bare names to workerd (`nodejs_compat`).
|
|
142
|
+
- `styx create` (`@elysiumjs/styx`) takes `--runtime bun|node|workers`, `--database`, `--cache` and `--package-manager bun|npm|pnpm`. Each runtime gets its entry, tsconfig types, scripts and drivers: postgres.js and ioredis on Node.js; Hyperdrive, Upstash, console logs and a `wrangler.jsonc` on Workers.
|
|
143
|
+
|
|
144
|
+
- [#12](https://github.com/na2axl/elysiumjs/pull/12) [`98a16aa`](https://github.com/na2axl/elysiumjs/commit/98a16aae889d46f7a9fb5573188f4cbe3629dd3b) Thanks [@na2axl](https://github.com/na2axl)! - `createWorker(app)` from `@elysiumjs/runtime-workers` builds an application and returns the handlers a Cloudflare Worker exports: `fetch` serves the routes, `queue` hands each batch to the service methods marked `@Workers.queue(name)`, and `scheduled` calls the methods marked `@Workers.cron(expression)`. Each invocation runs with its own env and execution context: `Workers.env()`, `Workers.context()` and `Workers.binding(name)` read them, and `WorkersModule` provides them, and the bindings it lists, as request-scoped services injected with `@Workers.inject(name)`. `KeyvWorkersKV` stores mnemosyne caches in a KV namespace. `bun run check:wrangler` runs a sample Worker in `wrangler dev`.
|
|
145
|
+
|
|
146
|
+
`MnemosyneModule` takes other Keyv stores in `cache.stores`, read with `Cache.store(name)`.
|
|
147
|
+
|
|
148
|
+
`styx build --target workers` (Hephaestus `hephaestus:build --target workers`, `WorkersBuilder`) bundles a Worker with its routes compiled ahead of time and refuses a bundle that generates code at runtime.
|
|
149
|
+
|
|
150
|
+
In core, `Application.boot()` initialises the modules once, for hosts that handle more than requests; `Kernel.serviceClasses(node)` lists the service classes of a module; console output falls back to `console` where the host has no output streams; and a singleton depending on a request-scoped service throws `ScopeMismatchError` even when it is built outside a request.
|
|
151
|
+
|
|
152
|
+
## 0.10.0
|
|
153
|
+
|
|
154
|
+
### Minor Changes
|
|
155
|
+
|
|
156
|
+
- [`68a0cdd`](https://github.com/na2axl/elysiumjs/commit/68a0cdd8b2e4a003d6e0af2d78985623cae40428) Thanks [@na2axl](https://github.com/na2axl)! - Route the framework's own diagnostics through the logger service.
|
|
157
|
+
|
|
158
|
+
Breaking changes:
|
|
159
|
+
|
|
160
|
+
- `@Event.listen()` with a mode other than `on` or `once` now throws an `ElysiumError` when the class is defined, instead of logging to the console and ignoring the listener.
|
|
161
|
+
|
|
162
|
+
Fixes and additions:
|
|
163
|
+
|
|
164
|
+
- `getLogger(context?)` and `LOGGER_SERVICE` (`'logger'`) are exported by `@elysiumjs/core`. `getLogger()` returns the `logger` service when one is registered (such as the one `HermesModule` provides), and a `ConsoleLogger` otherwise or when the service cannot be built or fails to create a child logger. It is resolved at call time from the global scope only, so a module's own `logger` service does not replace it, and returns a child logger bound to `context` when one is given.
|
|
165
|
+
- `Middleware.logger` (used by `RequestLoggerMiddleware`) falls back to a `ConsoleLogger` when no `logger` service is registered. It threw before.
|
|
166
|
+
- Core diagnostics go through the logger with structured context instead of `console.*`: the debug-mode request log (`debug`, with `method` and `url`), `onDestroy` failures on shutdown (`error`, with `target` and `error`), WAMP connection attempts (`warn` while retrying, `error` when giving up, with `connection`, `attempt`/`attempts`, `maxRetries` and `error`), and WAMP registrations and subscriptions (`info`, with `connection`, `topic` and the registration or subscription id).
|
|
167
|
+
- In debug mode, incoming requests are logged through the logger at `debug` level instead of printed with `console.log`. With `HermesModule`, they show only when its `level` is `debug` or `trace`.
|
|
168
|
+
- A failed WAMP registration or subscription is logged at `error` level and emitted as `elysium:error` with an error naming the topic and the connection, instead of becoming an unhandled rejection.
|
|
169
|
+
- The `elysium:error` emitted for a WAMP client error names the connection and its URL and includes the client's message (or the value it reported, when it is not an `Error`), with that value as `cause` when there is one.
|
|
170
|
+
- `HermesLogger` serialises an `error` field of the log context like pino's `err` (type, message and stack), so logged errors keep their stack trace. Elysium logs errors under `error`.
|
|
171
|
+
- `@Logged` resolves its fallback logger with `getLogger()`, and logs a thrown error whole under `error` (it logged only its message), so its stack trace is kept.
|
|
172
|
+
- The `elysium:error` emitted when a WAMP connection gives up states the number of attempts actually made (the first one plus the retries), as the logged `attempts` does. It stated `maxRetries` before.
|
|
173
|
+
|
|
174
|
+
- [`76ba0ff`](https://github.com/na2axl/elysiumjs/commit/76ba0ff18f3c196cebc2189426328f67cb699f46) Thanks [@na2axl](https://github.com/na2axl)! - `onRequest` middlewares registered on a module or a controller now run only for its routes. Elysia runs `onRequest` hooks on every request of the app, so such a middleware could reject requests meant for other modules.
|
|
175
|
+
|
|
176
|
+
They now run in the transform step of their own routes, so after routing and body parsing, and each one runs right before its own `onTransform`: module `onRequest`, module `onTransform`, then controller `onRequest`, controller `onTransform`. Any value they return still stops the request and is sent as the response. Application middlewares still run `onRequest` before routing, for every request.
|
|
177
|
+
|
|
178
|
+
- [`bacce71`](https://github.com/na2axl/elysiumjs/commit/bacce7146f667a1a46bf474dc0344fb0111ae12a) Thanks [@na2axl](https://github.com/na2axl)! - WebSocket controllers now run the middlewares registered on them with `@Middleware.register()`, on the upgrade request. `onRequest`, `onTransform` and `onBeforeHandle` can refuse the connection by returning a response. `onAfterHandle` and `onAfterResponse` run after the upgrade, with no response to read.
|
|
179
|
+
|
|
180
|
+
### Patch Changes
|
|
181
|
+
|
|
182
|
+
- [`2c49acd`](https://github.com/na2axl/elysiumjs/commit/2c49acd3b552aa43765d70339fa216961499e1b5) Thanks [@na2axl](https://github.com/na2axl)! - The tenant set by a middleware, such as a `TenantMiddleware` reading its own header, is no longer replaced by the `x-tenant-id` header (or `public`) before the handler runs.
|
|
183
|
+
|
|
184
|
+
- [`4ebf029`](https://github.com/na2axl/elysiumjs/commit/4ebf029611e6d753a4f1bfdbe247a75d954d9ded) Thanks [@na2axl](https://github.com/na2axl)! - WAMP clients use Bun's own `WebSocket` again. The subclass that faked the `protocol` field is gone, since Bun now fills it (oven-sh/bun#18744).
|
|
185
|
+
|
|
186
|
+
## 0.9.0
|
|
187
|
+
|
|
188
|
+
### Minor Changes
|
|
189
|
+
|
|
190
|
+
- [`5fef4a7`](https://github.com/na2axl/elysiumjs/commit/5fef4a7199f502fc4fe035299e7670739459a108) Thanks [@na2axl](https://github.com/na2axl)! - Fix tenant isolation, transactions and query caching in the database layer.
|
|
191
|
+
|
|
192
|
+
Breaking changes:
|
|
193
|
+
|
|
194
|
+
- `TenantMiddleware.validateTenant()` now rejects every tenant unless you override it, and tenant identifiers must match `TENANT_ID_PATTERN` (1 to 63 ASCII letters, digits, `_` or `-`, not starting with `pg_` and other than `information_schema`, in any case) before it is called. Requests that send any other tenant now fail with `Invalid tenant`. Requests without a tenant still resolve to `public` on `SimpleTenantMiddleware`. To migrate, check the tenant against your own data:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
class OrganizationMiddleware extends SimpleTenantMiddleware {
|
|
198
|
+
protected async validateTenant(tenant: string): Promise<boolean> {
|
|
199
|
+
return Organization.exists(tenant);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- `getTenantSchema()`, `registerTenantSchema()` and `wrapTenantSchema()` throw on tenant identifiers that do not match `TENANT_ID_PATTERN`.
|
|
205
|
+
- `setConnectionTenant()` is removed. It set the tenant on a pooled connection for the whole session, so the tenant leaked to other requests. Use `withRLS()`, or `Database.transaction()` in RLS mode, which scope the tenant to a transaction.
|
|
206
|
+
- `RepositoryInterface.update()` and `delete()`, and the `DrizzleRepository` implementations, return `null` when no row has the id. They returned `undefined` while typed as a row. `DrizzleRepository` also requires a model whose table has an `id` column.
|
|
207
|
+
- `@transactional` handlers run through the `db.transaction` service that `DrizzleModule` provides (its name is exported by `@elysiumjs/core` as `DATABASE_TRANSACTION_SERVICE`, and still re-exported by `@elysiumjs/mnemosyne`). A transactional handler without a database module now fails with an error naming the service.
|
|
208
|
+
- `@transactional` generator handlers commit only when the whole stream was sent. A stream closed early, or whose client disconnects (even while the handler is waiting), rolls back.
|
|
209
|
+
- In RLS mode, a transaction is bound to one tenant for its whole life: the tenant set (with a transaction-local `set_config`) when `Database.transaction()`, a `@transactional` handler, `withRLS()` or `withIsolation()` opens it, from the explicit tenant or the current one (`public` when there is none). Work that finds a transaction open on its connection (`withRLS()`, `withIsolation()`, `Database.transaction()`, repository calls) joins it only when its tenant is the transaction's tenant, and throws `TenantMismatchError` otherwise, without running anything: the tenant of an open transaction never changes. Inside a transaction, `Database.getConnection()` only returns it to work for its tenant (the current tenant, or `public`), and throws `TenantMismatchError` otherwise, so raw queries never run for another tenant; queries on the `tx` handle a callback received run as the transaction's tenant. Work for several tenants, such as an admin fan-out, must use one `withRLS()` per tenant outside any transaction, not nested in one.
|
|
210
|
+
- In RLS mode, each repository call made outside a transaction runs in its own transaction bound to the current tenant: `BEGIN`, `set_config` of the tenant, the query, `COMMIT`. These calls do not use the query cache, since the cache is bypassed inside transactions. Group calls in `Database.transaction()` or `withRLS()` to share one transaction.
|
|
211
|
+
- In RLS mode, code running without a tenant in context (background jobs, CLI commands, scripts) is scoped to the `public` tenant. Set the tenant with `withTenant(tenant, () => ...)` around the work (repository calls and `Database.transaction()` then use it), or with `withRLS(tenant, (tx) => ...)`.
|
|
212
|
+
- `migration:run` validates `--tenant` against `TENANT_ID_PATTERN`. Without a terminal it no longer creates a missing schema: pass `--create-schema` (which also skips the question in a terminal), or it fails.
|
|
213
|
+
- Query cache keys start with the connection name, and each connection has its own cache transaction scope. When several applications or environments share one cache store and use the same connection names for different databases, set `cache: { prefix }` on each connection: the prefix comes before the connection name in every key (empty by default).
|
|
214
|
+
- `DrizzleDatabaseCache` keys and values changed: entries written by earlier versions are ignored. The default TTL of cached queries (including with `cache: true`) is now 60 seconds instead of 1 second, and is set with `cache: { ttl }` in seconds.
|
|
215
|
+
- `Cache.memory` keeps a separate LRU per tag namespace, so `lruSize` applies to each namespace.
|
|
216
|
+
|
|
217
|
+
Fixes and additions:
|
|
218
|
+
|
|
219
|
+
- In RLS mode, repository operations outside a transaction run in one bound to the current tenant, through the strategy's `withIsolation()`. Transactions opened with `Database.transaction()` or by `@transactional` handlers set the tenant variable when they open. `withIsolation()` and `withRLS()` expose their transaction to repositories and `Database.getConnection()`. `TenantMismatchError` is exported.
|
|
220
|
+
- `Database.transaction(callback, connection?)` opens a transaction and registers it for the callback. When a transaction is already open on the same connection, the callback joins it (no savepoint: an error rolls back the whole transaction); in RLS mode only if the current tenant is the transaction's tenant (see above). Transactions are tracked by connection, so every name of a connection (`default` and the name it aliases) sees them, and named connections take part in transactions. `Database.getBaseConnection()`, `Database.inTransaction()` and `Database.runInTransaction()` are added. The `db:tx` context entry is still set for the default connection.
|
|
221
|
+
- `@transactional` generator handlers keep their transaction open until the stream ends, then commit (or roll back if the handler throws). They previously committed before running and failed with `generator.next is not a function`. Generator handlers also see the request context (`tenant`, `http:context`).
|
|
222
|
+
- The query cache is scoped to the connection and the current tenant, is shared correctly by several app instances (table versions stored in the cache), is not read or written inside transactions, and invalidates the tables a transaction changed once it commits. A result is only stored when no mutation happened since its query started, including when concurrent queries of the same key finish out of order. Cached queries honour Drizzle's `ex`, `px`, `exat`, `pxat` and `keepTtl` options.
|
|
223
|
+
- Cache namespaces (`Cache.redis.tags(...)`, `Cache.memory.tags(...)`) no longer overwrite each other: reads, deletes and `clear()` stay within their namespace.
|
|
224
|
+
- `getSessionTenant()` reads the Bun SQL result correctly, and `getCurrentTenant()` works without an application instance (for example under `Application.testing`).
|
|
225
|
+
- `migration:run` takes an advisory lock per tenant, adds a unique index on `elysium_migrations.name` (also to existing tables), finds the migrations from the project root (`.elysiumrc`), sets the `search_path` for the transaction only, asks before opening the transaction whether to create a missing schema, and always closes its client. Existing duplicate migration names must be removed before the unique index can be created.
|
|
226
|
+
- `migration:generate` only removes `"public".` qualifiers from the migration it generates, and leaves string literals, comments and dollar-quoted bodies (`DO` blocks, functions) untouched.
|
|
227
|
+
- `DatabaseDriver.createConnection()` receives the connection name as an optional second argument.
|
|
228
|
+
- `KeyvRedis` deletes with a single `UNLINK` (or `DEL`) per call or batch and reports failed deletes in `clear()` as `error` events.
|
|
229
|
+
|
|
3
230
|
## 0.8.0
|
|
4
231
|
|
|
5
232
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ bun install @elysiumjs/core
|
|
|
19
19
|
- WebSocket support
|
|
20
20
|
- WAMP protocol integration
|
|
21
21
|
- Command-line interface (`@elysiumjs/styx`), with support for custom commands
|
|
22
|
-
-
|
|
22
|
+
- Lifecycle events
|
|
23
23
|
- Caching (memory and Redis)
|
|
24
24
|
- PostgreSQL database support with Drizzle ORM
|
|
25
25
|
- Model classes and repositories for database interactions
|
|
@@ -33,9 +33,9 @@ Create a controller class:
|
|
|
33
33
|
```typescript
|
|
34
34
|
import { Http } from '@elysiumjs/core';
|
|
35
35
|
|
|
36
|
-
@Http.controller(
|
|
36
|
+
@Http.controller('/users')
|
|
37
37
|
export class UserController {
|
|
38
|
-
@Http.get(
|
|
38
|
+
@Http.get('/:id')
|
|
39
39
|
public getUser(id: string) {
|
|
40
40
|
return { id };
|
|
41
41
|
}
|
|
@@ -103,6 +103,40 @@ Or, if using `styx`, run:
|
|
|
103
103
|
bun styx serve
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
## Services
|
|
107
|
+
|
|
108
|
+
A service is identified by its class. Give it a name with `@Service.register({ name })` to look it up by that string instead. Class names are never used as identifiers, so services resolve the same way after a bundler renames classes (`bun run check:minified` builds a minified app and checks it).
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { Service, ServiceScope } from '@elysiumjs/core';
|
|
112
|
+
|
|
113
|
+
@Service.register()
|
|
114
|
+
export class UserRepository {}
|
|
115
|
+
|
|
116
|
+
@Service.register({ name: 'mailer' })
|
|
117
|
+
export class MailerService {}
|
|
118
|
+
|
|
119
|
+
@Service.register()
|
|
120
|
+
export class UserService {
|
|
121
|
+
constructor(
|
|
122
|
+
@Service.inject(UserRepository) private readonly users: UserRepository,
|
|
123
|
+
@Service.inject('mailer') private readonly mailer: MailerService
|
|
124
|
+
) {}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`@Service.inject()` without an argument injects the class of the parameter type, read from `emitDecoratorMetadata`. Pass the class or the name when the type is an interface or is missing; Elysium throws at declaration time when it cannot tell which service to inject.
|
|
129
|
+
|
|
130
|
+
A service has one of three scopes:
|
|
131
|
+
|
|
132
|
+
| Scope | Instances |
|
|
133
|
+
| ------------------------ | ----------------------------------------------------------- |
|
|
134
|
+
| `ServiceScope.SINGLETON` | One for the app (the default) |
|
|
135
|
+
| `ServiceScope.FACTORY` | A new one each time the service is resolved |
|
|
136
|
+
| `ServiceScope.REQUEST` | One per HTTP request, shared by its middlewares and handler |
|
|
137
|
+
|
|
138
|
+
A request-scoped service resolves only inside a request: resolving it elsewhere throws `OutsideRequestScopeError`. Code outside a route, such as a job or a test, opens a scope with `Service.runInRequestScope(fn)`; every request-scoped service resolved inside `fn`, including after an `await`, is shared for that run. A singleton, or a controller with the default server scope, cannot depend on a request-scoped service, since it would keep the first request's instance: Elysium throws `ScopeMismatchError`. Use a request-scoped controller (`scope: HttpControllerScope.REQUEST`), or inject the service in the route handler's parameters.
|
|
139
|
+
|
|
106
140
|
## Lazy boot
|
|
107
141
|
|
|
108
142
|
A CLI command boots lazily: running `app:cmd` initialises the command's own module and the modules it imports, not the whole graph. `help <cmd>` is always lazy, even for commands with `static boot = true`.
|
|
@@ -112,9 +146,33 @@ A CLI command boots lazily: running `app:cmd` initialises the command's own modu
|
|
|
112
146
|
- **Use `static boot = true`** on a command that needs every module initialised before it runs (workers, servers). It initialises every module and builds every service first.
|
|
113
147
|
- Service factories receive a synchronous `get`; it may only read services whose owning modules initialise synchronously, otherwise it throws `ServiceNotReadyError`.
|
|
114
148
|
|
|
149
|
+
## Runtimes
|
|
150
|
+
|
|
151
|
+
Core never calls a host API directly. Environment variables, timers, the HTTP server, process exit and shutdown signals come from a runtime, which ships as its own package:
|
|
152
|
+
|
|
153
|
+
| Package | Exports | Notes |
|
|
154
|
+
| ---------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
155
|
+
| `@elysiumjs/runtime-bun` | `runtime` | `Bun.env`, `Bun.sleep`, `listen`, process exit and signals |
|
|
156
|
+
| `@elysiumjs/runtime-node` | `runtime` | Same on Node.js; set `server: { adapter: node() }` from `@elysiajs/node` |
|
|
157
|
+
| `@elysiumjs/runtime-workers` | `runtime`, `createRuntime({ env })` | Env from `process.env` (`nodejs_compat`) or `env`; no `serve` (export the app's `fetch`), exit or signals |
|
|
158
|
+
|
|
159
|
+
Each package exports `runtime` (rename it at import if you like). Pass it to the application, or call `Runtime.use()` before anything needs it:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { Application } from '@elysiumjs/core';
|
|
163
|
+
import { runtime } from '@elysiumjs/runtime-bun';
|
|
164
|
+
|
|
165
|
+
@Application.register({ runtime, modules: [] })
|
|
166
|
+
class App extends Application {}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Without a runtime, `Runtime.get()` throws `RuntimeNotConfiguredError`; only the environment name falls back to `development`. `bun run check:runtime` bundles core, fails if it touches the `Bun` global, and boots an application from the bundle under Node.js.
|
|
170
|
+
|
|
115
171
|
## Migrating from the pre-module API
|
|
116
172
|
|
|
117
173
|
- The `database`, `redis`, `wamp` and `elysium:hermes` keys of `@Application.register()` are gone. Add `DrizzleModule.with()`, `RedisModule.with()`, `WampModule.with()` and `HermesModule.with()` to `modules` instead.
|
|
174
|
+
- `Redis` and `RedisModule` moved to `@elysiumjs/redis`.
|
|
175
|
+
- An application needs a runtime: add `runtime` from `@elysiumjs/runtime-bun` to `@Application.register()`.
|
|
118
176
|
- `plugins` no longer takes package plugins. Add the package's module (for example `SentryModule`) to `modules`.
|
|
119
177
|
- Importing a file no longer registers its commands or services. List commands in a module's `commands`, services in `services`, and jobs in `jobs`.
|
|
120
178
|
- `new App()` is replaced by `await App.start()`. `Application.testing()` boots a module graph for tests.
|
|
@@ -133,4 +191,4 @@ Feel free to open issues for any bugs or questions you may have. Pull requests a
|
|
|
133
191
|
|
|
134
192
|
## License
|
|
135
193
|
|
|
136
|
-
This project is licensed under the Apache License, Version 2.0. See the [LICENSE](https://github.com/
|
|
194
|
+
This project is licensed under the Apache License, Version 2.0. See the [LICENSE](https://github.com/na2axl/elysiumjs/blob/main/LICENSE) file for more information.
|
package/dist/app.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import type { ElysiaOpenAPIConfig } from '@
|
|
2
|
-
import type { AnyElysia,
|
|
1
|
+
import type { ElysiaOpenAPIConfig } from '@elysia/openapi';
|
|
2
|
+
import type { AnyElysia, ErrorContext } from 'elysia';
|
|
3
|
+
import type { TSchema } from 'typebox';
|
|
4
|
+
import type { FilterClass } from './filter';
|
|
3
5
|
import type { Route } from './http';
|
|
6
|
+
import type { InterceptorClass } from './interceptor';
|
|
4
7
|
import type { ConfiguredModule, ModuleClass } from './module';
|
|
5
|
-
import type {
|
|
8
|
+
import type { ModuleGraph } from './module-graph';
|
|
9
|
+
import type { ProblemOptions } from './problem';
|
|
10
|
+
import type { AppRateLimitOptions } from './rate-limit';
|
|
11
|
+
import type { Runtime as RuntimePort } from './runtime';
|
|
12
|
+
import type { HttpOptions } from './security';
|
|
13
|
+
import type { TestingApp, TestingAppOptions, TestingHttpApp, TestingOptions } from './testing';
|
|
6
14
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
7
15
|
import { Elysia } from 'elysia';
|
|
8
16
|
import { InteractsWithConsole } from './console';
|
|
@@ -24,12 +32,18 @@ export type AppProps = {
|
|
|
24
32
|
/**
|
|
25
33
|
* The Elysia server configuration.
|
|
26
34
|
*/
|
|
27
|
-
server?: ElysiaConfig
|
|
35
|
+
server?: ElysiaConfig & {
|
|
28
36
|
/**
|
|
29
37
|
* The port to listen on.
|
|
30
38
|
*/
|
|
31
39
|
port?: number;
|
|
32
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* The runtime the application runs on, from `@elysiumjs/runtime-bun`, `@elysiumjs/runtime-node`
|
|
43
|
+
* or `@elysiumjs/runtime-workers`. Can be omitted when `Runtime.use()` was called before.
|
|
44
|
+
* The runtime is process-wide: when several applications are constructed, the last one wins.
|
|
45
|
+
*/
|
|
46
|
+
runtime?: RuntimePort;
|
|
33
47
|
/**
|
|
34
48
|
* The modules the application is made of. They act as the imports of the root module.
|
|
35
49
|
*/
|
|
@@ -39,7 +53,7 @@ export type AppProps = {
|
|
|
39
53
|
*
|
|
40
54
|
* Set it to `false` to disable OpenAPI documentation.
|
|
41
55
|
*/
|
|
42
|
-
openapi?: ElysiaOpenAPIConfig | false;
|
|
56
|
+
openapi?: ElysiaOpenAPIConfig<boolean, string> | false;
|
|
43
57
|
/**
|
|
44
58
|
* The validation schema for the application environment.
|
|
45
59
|
*/
|
|
@@ -48,12 +62,53 @@ export type AppProps = {
|
|
|
48
62
|
* The list of plugins to load.
|
|
49
63
|
*/
|
|
50
64
|
plugins?: ElysiumPlugin[];
|
|
65
|
+
/**
|
|
66
|
+
* How errors are rendered as RFC 9457 problem details: the base of relative problem types, and
|
|
67
|
+
* the status of validation errors.
|
|
68
|
+
*/
|
|
69
|
+
problems?: ProblemOptions;
|
|
70
|
+
/**
|
|
71
|
+
* Exception filters for every request, run after the filters of the route, the controller and
|
|
72
|
+
* the module, and before the default problem details rendering.
|
|
73
|
+
*/
|
|
74
|
+
filters?: FilterClass[];
|
|
75
|
+
/** Interceptors around every request handler, outside those of the modules. */
|
|
76
|
+
interceptors?: InterceptorClass[];
|
|
77
|
+
/**
|
|
78
|
+
* HTTP security: CORS, security headers, the request body limit (1 MiB by default) and the
|
|
79
|
+
* trusted proxies that give `ctx.ip`.
|
|
80
|
+
*/
|
|
81
|
+
http?: HttpOptions;
|
|
82
|
+
/**
|
|
83
|
+
* Rate limiting: the store of every limit (in memory by default), and a limit for every route
|
|
84
|
+
* whose controller and route set none, when `window` and `max` are given.
|
|
85
|
+
*/
|
|
86
|
+
rateLimit?: AppRateLimitOptions;
|
|
87
|
+
};
|
|
88
|
+
/** The options of the root Elysia app. */
|
|
89
|
+
type ElysiaConfig = NonNullable<ConstructorParameters<typeof Elysia>[0]>;
|
|
90
|
+
/**
|
|
91
|
+
* An error raised while handling a request, as given to the Elysia error hook. `code` is set for
|
|
92
|
+
* the errors Elysia raises ('validation', 'not-found', ...).
|
|
93
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
94
|
+
*/
|
|
95
|
+
export type ServerError = ErrorContext & {
|
|
96
|
+
code?: string;
|
|
97
|
+
error: any;
|
|
51
98
|
};
|
|
52
99
|
/**
|
|
53
100
|
* Type for the application context.
|
|
101
|
+
*
|
|
102
|
+
* - `tenant`: the current tenant.
|
|
103
|
+
* - `http:context`: the context of the HTTP request being handled.
|
|
104
|
+
* - `db:transactions`: the open database transactions, keyed by the connection they run on.
|
|
105
|
+
* - `db:tx`: the transaction open on the default database connection. Kept for existing callers;
|
|
106
|
+
* prefer `db:transactions`.
|
|
107
|
+
* - `uow:frame`: the outermost unit of work, which collects the hooks run after it commits or
|
|
108
|
+
* rolls back (`@elysiumjs/mnemosyne`).
|
|
54
109
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
55
110
|
*/
|
|
56
|
-
export type AppContext = AsyncLocalStorage<Map<'tenant' | 'http:context' | 'db:tx', unknown>>;
|
|
111
|
+
export type AppContext = AsyncLocalStorage<Map<'tenant' | 'http:context' | 'db:transactions' | 'db:tx' | 'uow:frame', unknown>>;
|
|
57
112
|
/**
|
|
58
113
|
* Base class for the application main entry.
|
|
59
114
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
@@ -85,10 +140,13 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
85
140
|
*/
|
|
86
141
|
static start<T extends Application>(this: new () => T): Promise<void>;
|
|
87
142
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
143
|
+
* Sets up code under test. Given `app`, builds that application and returns an HTTP client that
|
|
144
|
+
* drives it in memory (`http.get('/x').send()`). Given `modules`, boots a module graph without a
|
|
145
|
+
* CLI or an HTTP server.
|
|
146
|
+
* @param options The application or the modules, service overrides, environment and boot mode.
|
|
90
147
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
91
148
|
*/
|
|
149
|
+
static testing(options: TestingAppOptions): Promise<TestingHttpApp>;
|
|
92
150
|
static testing(options: TestingOptions): Promise<TestingApp>;
|
|
93
151
|
/**
|
|
94
152
|
* The kernel running this application's module graph. Available once `run()` has started.
|
|
@@ -112,7 +170,7 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
112
170
|
* @param _error The error context.
|
|
113
171
|
* @returns Whether to continue the error propagation.
|
|
114
172
|
*/
|
|
115
|
-
protected onError(_error:
|
|
173
|
+
protected onError(_error: ServerError): Promise<boolean>;
|
|
116
174
|
/**
|
|
117
175
|
* Hook that is executed when the application starts.
|
|
118
176
|
* @param _elysia The Elysia instance.
|
|
@@ -129,8 +187,15 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
129
187
|
* @param argv The command line arguments, without the executable and script.
|
|
130
188
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
131
189
|
*/
|
|
132
|
-
run(argv?: string[]): Promise<void>;
|
|
190
|
+
run(argv?: readonly string[]): Promise<void>;
|
|
133
191
|
private props;
|
|
192
|
+
/**
|
|
193
|
+
* Builds the module graph of an application class without creating or booting anything.
|
|
194
|
+
* Used by tools that read the app's structure, such as the route type generator.
|
|
195
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
196
|
+
* @param environment The environment to build the graph for. Defaults to the current one.
|
|
197
|
+
*/
|
|
198
|
+
static moduleGraph(environment?: string): ModuleGraph;
|
|
134
199
|
private dispatch;
|
|
135
200
|
/** Prints an error, shuts down what was started and exits with code 1. */
|
|
136
201
|
private fail;
|
|
@@ -160,18 +225,65 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
160
225
|
*/
|
|
161
226
|
private commandExec;
|
|
162
227
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
228
|
+
* Builds the root Elysia app without listening, for hosts that own the server (Cloudflare
|
|
229
|
+
* Workers) and for ahead-of-time compilation with `elysia/plugin/aot`, which evaluates the
|
|
230
|
+
* entry at build time.
|
|
231
|
+
*
|
|
232
|
+
* Modules are registered but not initialised: they boot on the first request, so building the
|
|
233
|
+
* app runs no `onInit` work and builds no service.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* export const app = await new App().build();
|
|
238
|
+
* export default { fetch: (request: Request) => app.fetch(request) };
|
|
239
|
+
* ```
|
|
240
|
+
* @returns The root Elysia app.
|
|
241
|
+
* @throws When the module graph is invalid or a module config is missing.
|
|
242
|
+
*/
|
|
243
|
+
build(): Promise<Elysia<Route>>;
|
|
244
|
+
/**
|
|
245
|
+
* Validates the environment against the schemas of the app and its active modules, then makes
|
|
246
|
+
* the validated values available through `Env.get()`.
|
|
247
|
+
* @throws {InvalidEnvironmentError} Listing every missing or invalid variable.
|
|
248
|
+
*/
|
|
249
|
+
private configureEnvironment;
|
|
250
|
+
/**
|
|
251
|
+
* Initialises every module and builds every singleton service, once. An app from
|
|
252
|
+
* {@link build} calls it on its first request; hosts call it before other work, such as a
|
|
253
|
+
* queue batch or a scheduled event.
|
|
254
|
+
* @throws When a module or a singleton fails to initialise. Later calls retry the failed ones,
|
|
255
|
+
* so that a transient failure does not break every later request. A permanent failure is
|
|
256
|
+
* retried, and fails, on every request.
|
|
165
257
|
*/
|
|
258
|
+
boot(): Promise<void>;
|
|
259
|
+
/** Starts the server: boots the kernel, builds the root Elysia app and listens. */
|
|
166
260
|
private commandServe;
|
|
261
|
+
/**
|
|
262
|
+
* Builds the root Elysia app: lets modules hook into it and mounts their controllers.
|
|
263
|
+
* @param bootOnFirstRequest Whether the first request boots the modules, when they were only
|
|
264
|
+
* registered.
|
|
265
|
+
*/
|
|
266
|
+
private buildServer;
|
|
267
|
+
/** Maps each route of the built server to the filter levels of its handler. */
|
|
268
|
+
private indexRouteFilters;
|
|
167
269
|
/** Lets every module hook into the root app, then mounts the controllers of each module. */
|
|
168
270
|
private serveModules;
|
|
169
271
|
/** Builds the Elysia plugin of a module: its middlewares and the plugins of its controllers. */
|
|
170
272
|
private mountModule;
|
|
171
273
|
/** Registers the error handler and the start and stop hooks of the root app. */
|
|
172
274
|
private setupServerHooks;
|
|
173
|
-
/**
|
|
275
|
+
/**
|
|
276
|
+
* Turns an error raised while handling a request into the response sent to the client: a
|
|
277
|
+
* thrown status as is, anything else as problem details. Only server errors are reported
|
|
278
|
+
* (`onError`, the `elysium:error` event and the trace); client errors are part of normal
|
|
279
|
+
* operation.
|
|
280
|
+
*/
|
|
174
281
|
private handleServerError;
|
|
282
|
+
/**
|
|
283
|
+
* Reports a server error: calls `onError`, emits `elysium:error` and traces it.
|
|
284
|
+
* @returns `false` when `onError` handled the error itself.
|
|
285
|
+
*/
|
|
286
|
+
private reportError;
|
|
175
287
|
/**
|
|
176
288
|
* Describes the CLI and available commands.
|
|
177
289
|
*/
|
|
@@ -179,3 +291,4 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
179
291
|
private commandList;
|
|
180
292
|
private commandDescription;
|
|
181
293
|
}
|
|
294
|
+
export {};
|