@elysiumjs/core 0.10.0 → 0.11.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/CHANGELOG.md +155 -0
- package/README.md +61 -3
- package/dist/app.d.ts +120 -13
- package/dist/config.d.ts +74 -0
- package/dist/console.d.ts +46 -9
- package/dist/container.d.ts +71 -15
- 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 +147 -45
- package/dist/index.d.ts +23 -10
- 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} +25 -18
- package/dist/middleware.d.ts +24 -5
- 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 +37 -16
- package/package.json +12 -6
- package/dist/redis.d.ts +0 -86
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,160 @@
|
|
|
1
1
|
# @elysiumjs/core
|
|
2
2
|
|
|
3
|
+
## 0.11.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#26](https://github.com/na2axl/elysiumjs/pull/26) [`4cd1ef2`](https://github.com/na2axl/elysiumjs/commit/4cd1ef217826164e69cd641ca99f74c6f5a7ebe7) Thanks [@na2axl](https://github.com/na2axl)! - Type the query string of the query DSL. `Query.define` keeps the names of the filters, their operators and the sortable fields, `Query.Params<typeof spec>` types the parameter that `@Http.query()` decorates (`filter[status]`, `filter[status][in]`, `sort`, `cursor`, `limit`, `fields`, each an optional string), and `Query.schema(spec)` returns a schema of that type. Because the generated routes read the parameter's type, clients get typed query keys. Core re-exports TypeBox's `TOptional` and `TString` types.
|
|
8
|
+
|
|
9
|
+
## 0.11.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#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.
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
`@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.
|
|
18
|
+
|
|
19
|
+
- [#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`.
|
|
20
|
+
|
|
21
|
+
Breaking:
|
|
22
|
+
|
|
23
|
+
- `TSchema` and `Static` are re-exported from `typebox` instead of `@sinclair/typebox`; `Value` comes from `typebox/value` and `Value.Errors()` returns an array.
|
|
24
|
+
- `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).
|
|
25
|
+
- `AppHttpContext` is a plain interface with `decorator`, `store` and `derive` records (Elysia 2 renamed `resolve` to `derive`).
|
|
26
|
+
- Websocket controllers install Elysia's `websocket()` capability themselves.
|
|
27
|
+
|
|
28
|
+
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`.
|
|
29
|
+
|
|
30
|
+
`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.
|
|
31
|
+
|
|
32
|
+
- [#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 })`).
|
|
33
|
+
|
|
34
|
+
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`.
|
|
35
|
+
|
|
36
|
+
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.
|
|
37
|
+
|
|
38
|
+
`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.
|
|
39
|
+
|
|
40
|
+
- [#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.
|
|
41
|
+
|
|
42
|
+
- [#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.
|
|
43
|
+
|
|
44
|
+
- `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.
|
|
45
|
+
- Ready-made errors: `BadRequestError`, `ValidationFailedError`, `UnauthenticatedError`, `ForbiddenError`, `NotFoundError` and `ConflictError`.
|
|
46
|
+
- `ProblemCatalogue`, extended through declaration merging, lists the codes of an application; once it has keys, `DomainError.for()` takes only those and `ELYSIUM_PROBLEM_CODES`.
|
|
47
|
+
- 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`.
|
|
48
|
+
- 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.
|
|
49
|
+
|
|
50
|
+
- [#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.
|
|
51
|
+
|
|
52
|
+
- [#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.
|
|
53
|
+
|
|
54
|
+
- `@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`.
|
|
55
|
+
- `@Websocket.controller(path, options)`, where the options are those of the websocket server.
|
|
56
|
+
- `@Flow.controller(name, options)`, and `@Flow.step(name, options)` or `@Flow.step(options)` for a step named after its method.
|
|
57
|
+
- `@Lifecycle.on(event, options)` and `@Lifecycle.once(event, options)`, like their function forms `Lifecycle.on(event, handler)`.
|
|
58
|
+
- The styx controller stubs generate the same forms.
|
|
59
|
+
|
|
60
|
+
- [#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.
|
|
61
|
+
|
|
62
|
+
`Http.describe(controller)` reports the routes of an HTTP controller, and `Application.moduleGraph()` builds the module graph of an app class without booting it.
|
|
63
|
+
|
|
64
|
+
- [#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`.
|
|
65
|
+
|
|
66
|
+
`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`.
|
|
67
|
+
|
|
68
|
+
`HermesModule` registers the logger by the `HermesLogger` class and as `logger`.
|
|
69
|
+
|
|
70
|
+
- [#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.
|
|
71
|
+
|
|
72
|
+
Breaking changes:
|
|
73
|
+
|
|
74
|
+
- `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.
|
|
75
|
+
- The third parameter of `InteractsWithConsole.write` is now `'stdout' | 'stderr'`.
|
|
76
|
+
- The WebSocket options are narrowed to `idleTimeout`, `maxPayloadLength` and `perMessageDeflate`, and the `bun` WebSocket types are replaced by `ElysiumWebSocket`.
|
|
77
|
+
- Redis connections are `RedisConnection`s, a port any Redis client can implement, instead of `Bun.RedisClient`.
|
|
78
|
+
- `styx create @scope/name` creates the project in `name/`, and refuses project and module names with upper-case letters.
|
|
79
|
+
- 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.
|
|
80
|
+
|
|
81
|
+
Fixes:
|
|
82
|
+
|
|
83
|
+
- 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`.
|
|
84
|
+
- A failed module or service `onInit` no longer breaks later requests: the boot is retried, and the module's event listeners are unwired.
|
|
85
|
+
- `@elysiumjs/runtime-node` serves through `node:http`, keeping the Host header authoritative.
|
|
86
|
+
- `styx create` validates names before creating files, ships `.gitignore`, declares the dependencies of created projects and imports their default controllers.
|
|
87
|
+
|
|
88
|
+
- [#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.
|
|
89
|
+
|
|
90
|
+
Breaking changes:
|
|
91
|
+
|
|
92
|
+
- 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.
|
|
93
|
+
- Client errors (4xx) are no longer reported to `onError`, the `elysium:error` event or the trace.
|
|
94
|
+
- `Runtime.serve` may return a handle with `close()`, which the application calls on shutdown.
|
|
95
|
+
- Request bodies are limited to 1 MiB by default (`http.bodyLimit`), also when chunked.
|
|
96
|
+
- The environment schema is validated when the app is built or run, with every error reported at once, instead of in the constructor.
|
|
97
|
+
|
|
98
|
+
Features:
|
|
99
|
+
|
|
100
|
+
- `DomainError`, with `status`, `type`, `title`, `detail` and extensions, and shortcuts such as `DomainError.notFound()`.
|
|
101
|
+
- 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`.
|
|
102
|
+
- Typed configuration: `Secret` values, module `envSchema`s merged into the app's, `InvalidEnvironmentError`.
|
|
103
|
+
- `http` options: CORS, security headers, body limits and trusted proxies (`ctx.ip`). Runtimes report the client address through `clientAddress`.
|
|
104
|
+
- `createTestingHttpApp` and `createHttpClient` send requests to an app in memory, with `expectStatus()`.
|
|
105
|
+
|
|
106
|
+
Fixes:
|
|
107
|
+
|
|
108
|
+
- App-level `onRequest` middlewares run in the request scope.
|
|
109
|
+
- On Workers, the request scope of a streamed response is disposed when the body ends, not when `fetch` returns.
|
|
110
|
+
- `KeyvWorkersKV.clear()` stays within an operation budget (`maxOperations`).
|
|
111
|
+
- The Node.js bridge treats a destroyed socket as a client abort.
|
|
112
|
+
- Heracles performance metrics report through the Redis port (`connection` or `client`), not a Bun client.
|
|
113
|
+
- `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.
|
|
114
|
+
|
|
115
|
+
- [#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.
|
|
116
|
+
|
|
117
|
+
Breaking:
|
|
118
|
+
|
|
119
|
+
- 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.
|
|
120
|
+
|
|
121
|
+
Added:
|
|
122
|
+
|
|
123
|
+
- `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.
|
|
124
|
+
- 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.
|
|
125
|
+
- 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`.
|
|
126
|
+
- `ModelResource(model, { hidden, pick, omit, extend })` (`@elysiumjs/mnemosyne`) builds a response schema from a model.
|
|
127
|
+
- `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 }`).
|
|
128
|
+
- `DrizzleRepository.list(query)` and `count(query)` run a `ParsedQuery` with keyset pagination (`@elysiumjs/mnemosyne-drizzle`), and `compileQuery()` gives its `where` and `orderBy`.
|
|
129
|
+
- 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.
|
|
130
|
+
|
|
131
|
+
- [#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.
|
|
132
|
+
|
|
133
|
+
Breaking:
|
|
134
|
+
|
|
135
|
+
- 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.
|
|
136
|
+
- 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.
|
|
137
|
+
|
|
138
|
+
Added:
|
|
139
|
+
|
|
140
|
+
- 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.
|
|
141
|
+
- 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.
|
|
142
|
+
- `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.
|
|
143
|
+
- `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.
|
|
144
|
+
- 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.
|
|
145
|
+
- `@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.
|
|
146
|
+
- 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.
|
|
147
|
+
- `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`).
|
|
148
|
+
- `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.
|
|
149
|
+
|
|
150
|
+
- [#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`.
|
|
151
|
+
|
|
152
|
+
`MnemosyneModule` takes other Keyv stores in `cache.stores`, read with `Cache.store(name)`.
|
|
153
|
+
|
|
154
|
+
`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.
|
|
155
|
+
|
|
156
|
+
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.
|
|
157
|
+
|
|
3
158
|
## 0.10.0
|
|
4
159
|
|
|
5
160
|
### 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.
|
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,6 +62,39 @@ 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.
|
|
@@ -57,9 +104,11 @@ export type AppProps = {
|
|
|
57
104
|
* - `db:transactions`: the open database transactions, keyed by the connection they run on.
|
|
58
105
|
* - `db:tx`: the transaction open on the default database connection. Kept for existing callers;
|
|
59
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`).
|
|
60
109
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
61
110
|
*/
|
|
62
|
-
export type AppContext = AsyncLocalStorage<Map<'tenant' | 'http:context' | 'db:transactions' | 'db:tx', unknown>>;
|
|
111
|
+
export type AppContext = AsyncLocalStorage<Map<'tenant' | 'http:context' | 'db:transactions' | 'db:tx' | 'uow:frame', unknown>>;
|
|
63
112
|
/**
|
|
64
113
|
* Base class for the application main entry.
|
|
65
114
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
@@ -91,10 +140,13 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
91
140
|
*/
|
|
92
141
|
static start<T extends Application>(this: new () => T): Promise<void>;
|
|
93
142
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
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.
|
|
96
147
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
97
148
|
*/
|
|
149
|
+
static testing(options: TestingAppOptions): Promise<TestingHttpApp>;
|
|
98
150
|
static testing(options: TestingOptions): Promise<TestingApp>;
|
|
99
151
|
/**
|
|
100
152
|
* The kernel running this application's module graph. Available once `run()` has started.
|
|
@@ -118,7 +170,7 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
118
170
|
* @param _error The error context.
|
|
119
171
|
* @returns Whether to continue the error propagation.
|
|
120
172
|
*/
|
|
121
|
-
protected onError(_error:
|
|
173
|
+
protected onError(_error: ServerError): Promise<boolean>;
|
|
122
174
|
/**
|
|
123
175
|
* Hook that is executed when the application starts.
|
|
124
176
|
* @param _elysia The Elysia instance.
|
|
@@ -135,8 +187,15 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
135
187
|
* @param argv The command line arguments, without the executable and script.
|
|
136
188
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
137
189
|
*/
|
|
138
|
-
run(argv?: string[]): Promise<void>;
|
|
190
|
+
run(argv?: readonly string[]): Promise<void>;
|
|
139
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;
|
|
140
199
|
private dispatch;
|
|
141
200
|
/** Prints an error, shuts down what was started and exits with code 1. */
|
|
142
201
|
private fail;
|
|
@@ -166,18 +225,65 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
166
225
|
*/
|
|
167
226
|
private commandExec;
|
|
168
227
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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.
|
|
171
257
|
*/
|
|
258
|
+
boot(): Promise<void>;
|
|
259
|
+
/** Starts the server: boots the kernel, builds the root Elysia app and listens. */
|
|
172
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;
|
|
173
269
|
/** Lets every module hook into the root app, then mounts the controllers of each module. */
|
|
174
270
|
private serveModules;
|
|
175
271
|
/** Builds the Elysia plugin of a module: its middlewares and the plugins of its controllers. */
|
|
176
272
|
private mountModule;
|
|
177
273
|
/** Registers the error handler and the start and stop hooks of the root app. */
|
|
178
274
|
private setupServerHooks;
|
|
179
|
-
/**
|
|
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
|
+
*/
|
|
180
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;
|
|
181
287
|
/**
|
|
182
288
|
* Describes the CLI and available commands.
|
|
183
289
|
*/
|
|
@@ -185,3 +291,4 @@ export declare abstract class Application extends InteractsWithConsole {
|
|
|
185
291
|
private commandList;
|
|
186
292
|
private commandDescription;
|
|
187
293
|
}
|
|
294
|
+
export {};
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { TObject, TSchema } from 'typebox';
|
|
2
|
+
import type { ModuleGraph } from './module-graph';
|
|
3
|
+
import { t } from 'elysia';
|
|
4
|
+
import { ElysiumError } from './errors';
|
|
5
|
+
/**
|
|
6
|
+
* A value that must not leak: it prints as `[secret]` in logs, `JSON.stringify`, string
|
|
7
|
+
* conversion and error messages. Read it with `reveal()`.
|
|
8
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
9
|
+
* @template T The type of the value.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Secret<T = string> {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(value: T);
|
|
14
|
+
/** The secret value. Pass it where it is used, never to a log. */
|
|
15
|
+
reveal(): T;
|
|
16
|
+
toString(): string;
|
|
17
|
+
toJSON(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Marks an environment variable as a secret: once validated, it is read as a `Secret` from
|
|
20
|
+
* `Env.get()`.
|
|
21
|
+
* @param schema The schema of the value. Defaults to a string.
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* @Application.register({ env: t.Object({ DATABASE_URL: Secret.schema() }) })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
static schema<S extends TSchema = ReturnType<typeof t.String>>(schema?: S): S;
|
|
28
|
+
/** Whether a schema was made by {@link Secret.schema}. */
|
|
29
|
+
static isSchema(schema: unknown): boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One missing or invalid environment variable.
|
|
33
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
34
|
+
*/
|
|
35
|
+
export type EnvironmentIssue = {
|
|
36
|
+
key: string;
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Thrown at boot when environment variables are missing or invalid. It lists every issue, and
|
|
41
|
+
* never the values.
|
|
42
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
43
|
+
*/
|
|
44
|
+
export declare class InvalidEnvironmentError extends ElysiumError {
|
|
45
|
+
readonly issues: EnvironmentIssue[];
|
|
46
|
+
constructor(issues: EnvironmentIssue[]);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Thrown when two schemas declare the same environment variable differently.
|
|
50
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
51
|
+
*/
|
|
52
|
+
export declare class ConflictingEnvironmentSchemaError extends ElysiumError {
|
|
53
|
+
readonly key: string;
|
|
54
|
+
readonly owners: [string, string];
|
|
55
|
+
constructor(key: string, owners: [string, string]);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Merges the environment schema of the app with those of the active modules (`envSchema`).
|
|
59
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
60
|
+
* @param appSchema The `env` option of the application.
|
|
61
|
+
* @param graph The module graph, or `null` for the app schema alone.
|
|
62
|
+
* @throws {ConflictingEnvironmentSchemaError} When two schemas declare a key differently.
|
|
63
|
+
*/
|
|
64
|
+
export declare const mergeEnvironmentSchemas: (appSchema: TSchema | undefined, graph: ModuleGraph | null) => TObject | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Validates environment variables against a schema: applies defaults, converts strings to the
|
|
67
|
+
* declared types, keeps only the declared keys and wraps secrets.
|
|
68
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
69
|
+
* @param schema The merged schema.
|
|
70
|
+
* @param raw The variables from the runtime.
|
|
71
|
+
* @returns The validated variables.
|
|
72
|
+
* @throws {InvalidEnvironmentError} Listing every missing or invalid variable.
|
|
73
|
+
*/
|
|
74
|
+
export declare const validateEnvironment: (schema: TObject, raw: Record<string, unknown>) => Record<string, unknown>;
|