@owlmeans/server-app 0.1.18-rc.2 → 0.1.18-rc.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,13 +6,14 @@ The main entry point for OwlMeans backend services — aggregates server package
6
6
 
7
7
  - `makeContext(cfg)` creates a fully initialized server context with Fastify, auth, WebSocket, and static resource support
8
8
  - `main(ctx, entrypoints)` registers entrypoints, initializes the context, and starts the HTTP server
9
+ - `holdApiPort` + the boot-state module give a bind-first boot: the port answers for the app before it initializes
9
10
  - Re-exports the most commonly used symbols from lower-level packages so backend code has a single import target
10
11
  - Used by every service in the viable monorepo
11
12
 
12
13
  ## Installation
13
14
 
14
15
  ```bash
15
- bun add @owlmeans/server-app
16
+ bun add @owlmeans/server-app@^0.1.18-rc.17
16
17
  ```
17
18
 
18
19
  ## Usage
@@ -20,29 +21,71 @@ bun add @owlmeans/server-app
20
21
  Bootstrap a backend service:
21
22
 
22
23
  ```typescript
23
- import { makeContext, main, modules, config, service, AppType, Layer } from '@owlmeans/server-app'
24
+ import { makeContext, main, entrypoints, config, service, AppType } from '@owlmeans/server-app'
24
25
 
25
26
  const appConfig = config(
26
27
  AppType.Backend,
27
28
  'manager-api',
28
- service({ service: 'mongo', host: process.env.MONGO_HOST!, port: 27017 }),
29
- { layer: Layer.Service, port: 3000 }
29
+ service({ service: 'manager-api', host: process.env.API_HOST!, port: 3000 }),
30
+ { port: 3000, dbs: [{ service: 'mongo', alias: 'mongo', host: process.env.MONGO_HOST!, port: 27017 }] }
30
31
  )
31
32
 
32
33
  const context = makeContext(appConfig)
33
- await main(context, [...modules, ...appModules])
34
+ await main(context, [...entrypoints, ...appEntrypoints])
34
35
  ```
35
36
 
36
37
  Elevate an entrypoint with a handler:
37
38
 
38
39
  ```typescript
39
- import { elevate, handleBody, guard, GUARD_ED25519, modules } from '@owlmeans/server-app'
40
+ import { elevate, handleBody, guard, GUARD_ED25519 } from '@owlmeans/server-app'
41
+ import { appEntrypoints } from './entrypoints'
40
42
 
41
- elevate(modules, 'project-create', handleBody<CreateProject>(async (payload, ctx) => {
43
+ elevate(appEntrypoints, 'project-create', handleBody<CreateProject>(async (payload, ctx) => {
42
44
  return await (ctx as Context).project().create(payload)
43
45
  }), guard(GUARD_ED25519))
44
46
  ```
45
47
 
48
+ ### Bind-first boot
49
+
50
+ `main()` is the short path. An app that must explain a **failed** boot binds the port first instead,
51
+ so a startup failure has somewhere to be reported from — without a listener the edge answers a bare
52
+ connect error that names neither the app nor the cause:
53
+
54
+ ```typescript
55
+ import { holdApiPort, bootHold, setBootPhase, bootHealthPayload } from '@owlmeans/server-app'
56
+
57
+ const hold = await holdApiPort(ctx.cfg, bootHold(HEALTH_PATH)).catch((error: Error) => {
58
+ // A failure to BIND is fatal: carried past, the boot ends with nothing listening and exits 0
59
+ // while a predecessor keeps serving stale code. Exit non-zero so a supervisor reclaims the port.
60
+ console.error(`[boot] STARTUP FAILED — ${error.message}`)
61
+ process.exit(1)
62
+ })
63
+
64
+ try {
65
+ await ctx.configure().init()
66
+ } catch (error) {
67
+ // Stay alive: the hold is the only thing that can explain why nothing is serving.
68
+ setBootPhase('failed', (error as Error).message)
69
+ return
70
+ }
71
+
72
+ await hold.release() // awaited — `listen()` throws while a predecessor holds the socket
73
+ setBootPhase('ready')
74
+ await ctx.getApiServer().listen()
75
+ ```
76
+
77
+ The hold answers `HEALTH_PATH` with `bootHealthPayload()` and everything else with **503** (not 404
78
+ — the real routes do not exist yet). Once the app's own server takes the socket, its health handler
79
+ spreads the same `bootHealthPayload()` into its answer, so a consumer polling across the handover
80
+ never sees the body change under it:
81
+
82
+ ```typescript
83
+ export const handleHealtz = handleRequest(async (_req, ctx) => ({
84
+ ...bootHealthPayload(),
85
+ db: await pingDb(ctx)
86
+ }))
87
+ ```
88
+
46
89
  ## API
47
90
 
48
91
  ### `makeContext<C, T>(cfg, customize?): T`
@@ -53,6 +96,23 @@ Creates a server context with Fastify HTTP, WebSocket, static resources, and aut
53
96
 
54
97
  Registers entrypoints, calls `configure().init()`, then starts the Fastify server.
55
98
 
99
+ ### Boot state
100
+
101
+ Process-wide boot phase, shared by the port hold and the app's own health handler.
102
+
103
+ | Symbol | Description |
104
+ |---|---|
105
+ | `setBootPhase(phase, detail?)` | Record the phase; `detail` is free text kept for every phase |
106
+ | `getBootPhase(): BootPhase` / `getBootDetail()` | Current phase and its detail |
107
+ | `bootHealthPayload(): BootHealth` | `{ status, phase, ok, reason?, bootId, pid }` — the health body |
108
+ | `bootHold(okPath?): ApiPortHoldOptions` | `holdApiPort` options wired to this module |
109
+ | `BootPhase` | `'initializing' \| 'ready' \| 'failed'` |
110
+ | `BOOT_ID_ENV` | `'OWLMEANS_BOOT_ID'` — a supervisor stamps the child, `bootId` echoes it back |
111
+
112
+ `bootId` is identity, not health: an answer carrying an id other than the one the supervisor spawned
113
+ means a leftover process still owns the port and the health it reports describes code nobody asked
114
+ for.
115
+
56
116
  ### Re-exported symbols (for convenience)
57
117
 
58
118
  | Symbol | Source |
@@ -62,19 +122,21 @@ Registers entrypoints, calls `configure().init()`, then starts the Fastify serve
62
122
  | `celevate` | `@owlmeans/client-entrypoint` |
63
123
  | `route` | `@owlmeans/route` |
64
124
  | `broute` | `@owlmeans/server-route` |
65
- | `filter`, `body`, `params`, `parent`, `EntrypointOutcome` | `@owlmeans/entrypoint` |
125
+ | `filter`, `body`, `params`, `EntrypointOutcome` | `@owlmeans/entrypoint` |
66
126
  | `config`, `service`, `sservice`, `toConfigRecord` | various config packages |
67
- | `AppType`, `Layer`, `BASE`, `assertContext` | `@owlmeans/context` |
127
+ | `AppType`, `BASE`, `assertContext` | `@owlmeans/context` |
68
128
  | `DAUTH_GUARD` | `@owlmeans/server-auth` |
69
129
  | `GUARD_ED25519`, `BED255_CASHE_RESOURCE` | `@owlmeans/auth-common` |
70
130
  | `klusterize` | `@owlmeans/kluster` |
71
- | `Request`, `Response`, `CommonEntrypoint`, `ClientEntrypoint` | type re-exports |
131
+ | `createListSchema`, `filterObject` | `@owlmeans/resource` |
132
+ | `Request`, `Response`, `ClientEntrypoint`, `RefedEntrypointHandler` | type re-exports |
133
+ | `Criteria`, `ListOptions`, `ListQuery`, `ListResult`, `Sort` | type re-exports from `@owlmeans/resource` |
72
134
 
73
- ### `modules`
135
+ ### `entrypoints`
74
136
 
75
137
  Default entrypoint array providing auth and API config routes. Spread into `main()`:
76
138
  ```typescript
77
- await main(context, [...modules, ...myModules])
139
+ await main(context, [...entrypoints, ...myEntrypoints])
78
140
  ```
79
141
 
80
142
  ## Related Packages
@@ -91,7 +153,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
91
153
  your project's skill store (`.agents/skills/`):
92
154
 
93
155
  ```sh
94
- npx @owlmeans/agent-skills
156
+ npx @owlmeans/agent-skills@^0.1.18-rc.14
95
157
  ```
96
158
 
97
159
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/server-app",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.509Z",
4
+ "version": "0.1.18-rc.20",
5
+ "generatedAt": "2026-09-10T23:12:07.512Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: server-app
3
- description: How to use @owlmeans/server-app — main() entry point, handleRequest()/handleBody() handler wrappers, elevate()/celevate() to attach handlers to module declarations, sservice() to register backend services. Auto-invoked when working with the server entry point or request handlers.
3
+ description: How to use @owlmeans/server-app — main() entry point, makeContext(), the bind-first boot (holdApiPort + boot state), handleRequest()/handleBody() handler wrappers, elevate()/celevate() to attach handlers to entrypoint declarations, and the config and entrypoint builders it re-exports. Auto-invoked when working with the server entry point, the server context factory, or request handlers.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,20 +8,68 @@ user-invocable: false
8
8
  # @owlmeans/server-app
9
9
 
10
10
  **Layer:** Server
11
- **Install:** `"@owlmeans/server-app": "^0.1.18-rc.0"` in `dependencies`
11
+ **Install:** `"@owlmeans/server-app": "^0.1.18-rc.20"` in `dependencies`
12
+
13
+ The one package a backend application depends on directly. It assembles the server context, owns the
14
+ process entry point, and re-exports everything an app needs to declare its config, its entrypoints
15
+ and its handlers — so an app on it rarely imports the server layer packages by name.
12
16
 
13
17
  ## Key Exports
14
18
 
15
19
  | Export | Description |
16
20
  |--------|-------------|
17
- | `main<E, C, T>(context, modules)` | Entry point — boots Express, registers all entrypoints as routes |
21
+ | `main<R, C, T>(ctx, entrypoints)` | Entry point — registers all entrypoints, configures/inits the context, then boots the Fastify API server (`@owlmeans/server-api`) |
22
+ | `makeContext(cfg, customize?)` | Build the server context with API server, API client, sockets, static resource and auth appended |
23
+ | `AppConfig`, `AppContext<C>` | The config and context types an app extends |
24
+ | `entrypoints` | Built-in system entrypoints — the auth endpoints plus the api-config advertise endpoint; spread into `appEntrypoints` |
25
+
26
+ ### Building the config
27
+
28
+ | Export | Description |
29
+ |--------|-------------|
30
+ | `config(service, cfg?)` | Start a server config for this service |
31
+ | `sservice(service, cfg?)` | Declare a backend service route into `cfg.services` |
32
+ | `addWebService(service, alias?, cfg?)` | Declare a frontend service the app addresses |
33
+ | `service(route, cfg?)`, `toConfigRecord(obj)`, `PLUGINS` | Generic service declaration and config records |
34
+ | `AppType`, `BASE` | The app-type enum, and `base` — the alias of the intermediate area whose children require authentication |
35
+ | `PluginConfig` | Plugin config record shape |
36
+
37
+ ### Declaring and elevating entrypoints
38
+
39
+ | Export | Description |
40
+ |--------|-------------|
41
+ | `entrypoint(...)`, `route(...)`, `broute(...)` | Declaration builders — `entrypoint` from server-entrypoint (it builds a `ServerEntrypoint`), `route` from route, `broute` from server-route |
42
+ | `elevate(entrypoints, alias, handler?, opts?)` | Attach a handler to an entrypoint declaration |
43
+ | `celevate(entrypoints, alias, handler?, opts?)` | Make a declaration client-callable from the server side |
44
+ | `guard(alias, opts?)` | Options requiring a guard |
45
+ | `DAUTH_GUARD` | Alias of the default auth guard (`auth`) |
46
+ | `GUARD_ED25519`, `BED255_CASHE_RESOURCE` | The service-to-service signature guard and its nonce cache |
47
+ | `filter`, `body`, `params`, `EntrypointOutcome` | Validators and the outcome enum |
48
+
49
+ ### Writing handlers
50
+
51
+ | Export | Description |
52
+ |--------|-------------|
18
53
  | `handleRequest(fn)` | Wrap an async function as a server handler `(req, context) => result` |
19
54
  | `handleBody<T>(fn)` | Wrap an async function with validated body — `(payload, context, req) => result` |
20
- | `elevate(modules, alias, handler)` | Attach a handler to an entrypoint declaration |
21
- | `celevate(modules, alias, handler)` | Conditional elevate — only if entrypoint exists |
22
- | `sservice(options, cfg)` | Register a server-side service entry in the config |
23
- | `modules` | Built-in system entrypoints — spread into `appModules` |
24
- | `Context`, `Config`, `ClientEntrypoint` re-exports | Common types |
55
+ | `handleParams<T>(fn)` | The same, over validated URL params |
56
+ | `Request`, `Response`, `Module`, `RefedEntrypointHandler`, `ClientEntrypoint` | Handler and entrypoint types |
57
+ | `createListSchema(schema)`, `filterObject(obj, keep?)` | List-result schema and object whitelisting |
58
+ | `Criteria`, `ListOptions`, `ListQuery`, `ListResult`, `Sort` | Resource query types |
59
+ | `assertContext(ctx, location?)` | Narrow a possibly-undefined context, throwing where it is missing |
60
+
61
+ ### Booting
62
+
63
+ | Export | Description |
64
+ |--------|-------------|
65
+ | `holdApiPort(cfg, opts?)` | Own the app's port during boot |
66
+ | `ApiPortHold`, `ApiPortHoldOptions` | The hold handle and its options |
67
+ | `setBootPhase(phase, detail?)` | Record the process boot phase — `'initializing' \| 'ready' \| 'failed'` |
68
+ | `getBootPhase()` / `getBootDetail()` | Read it back |
69
+ | `bootHealthPayload()` | The health body: `{ status, phase, ok, reason?, bootId, pid }` |
70
+ | `bootHold(okPath?)` | `holdApiPort` options wired to the boot state |
71
+ | `BootPhase`, `BootHealth`, `BOOT_ID_ENV` | Boot-state types and the supervisor's boot-id env var |
72
+ | `klusterize(ctx, alias?)` | Register the cluster client and its config-directive middleware |
25
73
 
26
74
  ## Usage
27
75
 
@@ -31,48 +79,135 @@ user-invocable: false
31
79
  import { main } from '@owlmeans/server-app'
32
80
  import config from './config.js'
33
81
  import { makeContext } from './context.js'
34
- import { appModules } from './modules.js'
82
+ import { appEntrypoints } from './entrypoints.js'
35
83
 
36
84
  const context = makeContext(config)
37
- main<{}, Config, Context>(context, appModules)
85
+ main<{}, Config, Context>(context, appEntrypoints)
38
86
  ```
39
87
 
88
+ `makeContext(cfg, customize)` appends the API server, the API client, the socket service and its
89
+ middleware, and the static resource. Passing `customize: true` — or having already registered the
90
+ auth cache resource — leaves the auth service and its cache out, which is what an app that supplies
91
+ its own authentication does.
92
+
93
+ ### Bind-first boot
94
+
95
+ An app that must be able to explain a *failed* boot does not call `main()`; it binds the port
96
+ first, so the failure has somewhere to be reported from. Never hand-roll the phase module —
97
+ `boot-state` is it.
98
+
99
+ ```typescript
100
+ import { holdApiPort, bootHold, setBootPhase, bootHealthPayload } from '@owlmeans/server-app'
101
+
102
+ const hold = await holdApiPort(ctx.cfg, bootHold(HEALTH_PATH)).catch((error: Error) => {
103
+ console.error(`[boot] STARTUP FAILED — ${error.message}`)
104
+ process.exit(1) // a bind failure is fatal — see below
105
+ })
106
+
107
+ try {
108
+ await ctx.configure().init()
109
+ } catch (error) {
110
+ setBootPhase('failed', (error as Error).message)
111
+ return // stay alive: the hold is the only thing that can explain the failure
112
+ }
113
+
114
+ await hold.release()
115
+ setBootPhase('ready')
116
+ await ctx.getApiServer().listen()
117
+ ```
118
+
119
+ Rules this encodes:
120
+
121
+ - **Bind before you initialize.** Everything after the bind can fail, and a failure before it is
122
+ invisible — the edge answers a bare connect error naming neither the app nor the reason.
123
+ - **A bind failure exits non-zero.** Carried past, the boot ends with nothing listening and nothing
124
+ holding the event loop: a clean exit 0 while a predecessor keeps serving stale code.
125
+ - **A failed init returns rather than exits.** The hold keeps answering `HEALTH_PATH` with
126
+ `phase: 'failed'` and the reason; everything else gets 503.
127
+ - **`hold.release()` is awaited** — `listen()` throws while a predecessor still owns the socket.
128
+ - **The app's own health handler spreads `bootHealthPayload()`**, so the body does not change shape
129
+ under a consumer polling across the handover:
130
+
131
+ ```typescript
132
+ export const handleHealtz = handleRequest(async (_req, ctx) => ({
133
+ ...bootHealthPayload(), db: await pingDb(ctx)
134
+ }))
135
+ ```
136
+
137
+ `bootId` is identity, not health: a supervisor stamps the child it spawns via `BOOT_ID_ENV`
138
+ (`OWLMEANS_BOOT_ID`) and compares it here — another id means a leftover process holds the port.
139
+
40
140
  ### Handlers
41
141
  ```typescript
42
142
  import { handleRequest, handleBody } from '@owlmeans/server-app'
43
- import { AuthUnknown } from '@owlmeans/auth'
143
+ import { requireEntityKey } from '@owlmeans/auth-common'
44
144
  import type { ClientEntrypoint } from '@owlmeans/server-app'
45
145
 
46
146
  export const list = handleRequest(async (req, context) => {
47
- if (req.auth?.entityId == null) throw new AuthUnknown('entity')
48
147
  const ctx = context as Context
49
- const { items } = await ctx.project().list({ entityId: req.auth.entityId })
148
+ const { items } = await ctx.project().list({ entityId: requireEntityKey(req) })
50
149
  return items
51
150
  })
52
151
 
53
- export const create = handleBody<CreateProject>(async (payload, context, req) => {
152
+ export const create = handleBody<CreateProject>(async (payload, context) => {
54
153
  const ctx = context as Context
55
- const [result] = await ctx.entrypoint<ClientEntrypoint<Project>>(agent.project.create).call({
56
- body: { prompt: payload.prompt, entity: req.auth?.entityId }
154
+ return await ctx.entrypoint<ClientEntrypoint<Project>>(agent.project.create).call({
155
+ body: { prompt: payload.prompt }
57
156
  })
58
- return result
59
157
  })
60
158
  ```
61
159
 
62
- ### Module elevation
160
+ **Key organization-scoped records by `requireEntityKey(req)` / `entityKeyOf(req)` from
161
+ `@owlmeans/auth-common`.** The token carries only `entitySlug`, which is renameable — there is no id
162
+ on it. The guard resolves that slug once, at the boundary, and hands the result to the handler as
163
+ `req.entity` (`{ id, slug, iamKey }`); those two helpers return `req.entity.id` and fall back to the
164
+ slug for a deployment that registers no entity resolver, and `requireEntityKey` throws
165
+ `AuthorizationError` when the request names no organization at all. `requireEntity(req)` gives the
166
+ whole record when the slug or the frozen `iamKey` is wanted too.
167
+
168
+ The id stays inside the process: it is what records, grants and permission checks key on, and it
169
+ never goes onto the wire. A cross-service call carries the caller's token, so the peer resolves the
170
+ organization for itself; anything that must name one in a payload names it by `entitySlug`.
171
+
172
+ A cross-service call from a handler uses `call()` — it resolves to the value and throws whatever the
173
+ peer replied. Reach for `invoke()` only where the outcome decides what happens next.
174
+
175
+ ### Entrypoint elevation
63
176
  ```typescript
64
- import { elevate, modules } from '@owlmeans/server-app'
65
- import { managerModules } from 'my-common'
177
+ // entrypoints.ts
178
+ import { elevate, entrypoints as systemEntrypoints } from '@owlmeans/server-app'
179
+ import { managerEntrypoints } from 'my-common'
66
180
  import { create, list } from './app/project/index.js'
67
181
 
68
- elevate(managerModules, manager.back.project.create, create)
69
- elevate(managerModules, manager.back.project.list, list)
182
+ elevate(managerEntrypoints, manager.back.project.create, create)
183
+ elevate(managerEntrypoints, manager.back.project.list, list)
70
184
 
71
- export const appModules = [...modules, ...paymentModules, ...managerModules]
185
+ export const appEntrypoints = [...systemEntrypoints, ...paymentEntrypoints, ...managerEntrypoints]
72
186
  ```
73
187
 
188
+ Elevating is idempotent, so a later `elevate` of the same alias is legal and simply replaces the
189
+ element again; the guards it brings are added to the declared ones rather than replacing them.
190
+
191
+ A parent alias needs no elevation for its `guard()` to reach its children — `getGuards()` walks the
192
+ registered declarations whether or not they were elevated. A **bare** `elevate(entrypoints, alias)`
193
+ only turns the declaration into a server entrypoint; with no handler it is never bound as a route,
194
+ and it runs in the intermediate chain only when the same call passes `true` (or
195
+ `{ intermediate: true }`) along with a `handleIntermediate` handler from `@owlmeans/server-api`.
196
+ `guard()` is re-exported here; `gate()` is not — take it from `@owlmeans/entrypoint`.
197
+
74
198
  ## Depends On
75
199
 
76
- - `@owlmeans/server-context`, `@owlmeans/server-entrypoint`, `@owlmeans/server-route`, `@owlmeans/server-api`
77
- - `@owlmeans/entrypoint`, `@owlmeans/route`, `@owlmeans/auth`, `@owlmeans/error`
78
- - `express` / `fastify` (server runtime)
200
+ Declared:
201
+
202
+ - `@owlmeans/server-context`, `@owlmeans/server-entrypoint`, `@owlmeans/server-route`, `@owlmeans/server-api`, `@owlmeans/server-auth`, `@owlmeans/server-socket`
203
+ - `@owlmeans/api`, `@owlmeans/static-resource`, `@owlmeans/kluster`
204
+ - `@owlmeans/entrypoint`, `@owlmeans/route`, `@owlmeans/config`, `@owlmeans/client-config`, `@owlmeans/client-entrypoint`, `@owlmeans/context`
205
+
206
+ Imported but **not** declared in this package's manifest: `@owlmeans/api-config-server` (its
207
+ advertise endpoint is in `entrypoints`), `@owlmeans/auth-common` (`GUARD_ED25519`),
208
+ `@owlmeans/resource` (the list schema and the query types) and `@owlmeans/server-config`
209
+ (`sservice`). A workspace resolves them anyway; a standalone install has to name them in its own
210
+ dependencies.
211
+
212
+ The HTTP server is Fastify, built by `@owlmeans/server-api` — this package neither depends on
213
+ `fastify` nor exposes it. There is no Express anywhere in this layer.
@@ -0,0 +1,55 @@
1
+ import type { ApiPortHoldOptions } from '@owlmeans/server-api';
2
+ /**
3
+ * Boot phase of the process.
4
+ *
5
+ * An app binds its port before it initializes, so that a failure has somewhere to be reported
6
+ * from. Without a listener the only thing anyone sees is a connection refused at the edge — an
7
+ * opaque 503 that names neither the app nor the cause. This phase is what the health path answers
8
+ * with, and what the platform reads to tell "still starting" from "gave up, and here is why".
9
+ */
10
+ export type BootPhase = 'initializing' | 'ready' | 'failed';
11
+ export interface BootHealth extends Record<string, unknown> {
12
+ status: 'OK' | 'FAILED';
13
+ phase: BootPhase;
14
+ ok: boolean;
15
+ /** Whatever `setBootPhase` was given — the failure message, or the step being waited on. */
16
+ reason?: string;
17
+ /**
18
+ * Identity, not health: a supervisor passes the id to the child it spawns and compares it
19
+ * here, so an answer carrying another id means the port is held by a leftover process and the
20
+ * health it reports describes code nobody asked for. Empty when nothing supplied one.
21
+ */
22
+ bootId: string;
23
+ pid: number;
24
+ }
25
+ /** Env var a supervisor stamps the spawned process with; surfaced as `bootId`. */
26
+ export declare const BOOT_ID_ENV = "OWLMEANS_BOOT_ID";
27
+ export declare const getBootPhase: () => BootPhase;
28
+ export declare const getBootDetail: () => string | undefined;
29
+ /**
30
+ * `detail` is free text, and is kept for every phase — a boot that is merely slow is as worth
31
+ * explaining as one that failed.
32
+ */
33
+ export declare const setBootPhase: (phase: BootPhase, detail?: string) => void;
34
+ /**
35
+ * The body the health path answers with, from either server.
36
+ *
37
+ * The port hold and the app's own handler both build it here, so a consumer polling through the
38
+ * handover never sees the shape change under it — which is why the keys are unconditional and
39
+ * only `reason` comes and goes with the state it describes.
40
+ */
41
+ export declare const bootHealthPayload: () => BootHealth;
42
+ /**
43
+ * Hold options wired to this module — the bind-first boot in one line:
44
+ *
45
+ * const hold = await holdApiPort(ctx.cfg, bootHold(HEALTH_PATH))
46
+ * try { await init() } catch (e) { setBootPhase('failed', message); return }
47
+ * await hold.release()
48
+ * setBootPhase('ready')
49
+ * await ctx.getApiServer().listen()
50
+ *
51
+ * The hold evaluates `payload` per request, so the phase set after a failure is reported live by
52
+ * the listener that is already bound rather than needing a server that will never start.
53
+ */
54
+ export declare const bootHold: (okPath?: string) => ApiPortHoldOptions;
55
+ //# sourceMappingURL=boot-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot-state.d.ts","sourceRoot":"","sources":["../src/boot-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAE9D;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE3D,MAAM,WAAW,UAAW,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzD,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAA;IACvB,KAAK,EAAE,SAAS,CAAA;IAChB,EAAE,EAAE,OAAO,CAAA;IACX,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,kFAAkF;AAClF,eAAO,MAAM,WAAW,qBAAqB,CAAA;AAK7C,eAAO,MAAM,YAAY,QAAO,SAAmB,CAAA;AAEnD,eAAO,MAAM,aAAa,QAAO,MAAM,GAAG,SAAoB,CAAA;AAE9D;;;GAGG;AACH,eAAO,MAAM,YAAY,UAAW,SAAS,WAAW,MAAM,KAAG,IAGhE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAO,UAOnC,CAAA;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,QAAQ,YAAa,MAAM,KAAG,kBAGzC,CAAA"}
@@ -0,0 +1,46 @@
1
+ /** Env var a supervisor stamps the spawned process with; surfaced as `bootId`. */
2
+ export const BOOT_ID_ENV = 'OWLMEANS_BOOT_ID';
3
+ let _phase = 'initializing';
4
+ let _detail;
5
+ export const getBootPhase = () => _phase;
6
+ export const getBootDetail = () => _detail;
7
+ /**
8
+ * `detail` is free text, and is kept for every phase — a boot that is merely slow is as worth
9
+ * explaining as one that failed.
10
+ */
11
+ export const setBootPhase = (phase, detail) => {
12
+ _phase = phase;
13
+ _detail = detail;
14
+ };
15
+ /**
16
+ * The body the health path answers with, from either server.
17
+ *
18
+ * The port hold and the app's own handler both build it here, so a consumer polling through the
19
+ * handover never sees the shape change under it — which is why the keys are unconditional and
20
+ * only `reason` comes and goes with the state it describes.
21
+ */
22
+ export const bootHealthPayload = () => ({
23
+ status: _phase === 'failed' ? 'FAILED' : 'OK',
24
+ phase: _phase,
25
+ ok: _phase === 'ready',
26
+ ...(_detail != null ? { reason: _detail } : {}),
27
+ bootId: process.env[BOOT_ID_ENV] ?? '',
28
+ pid: process.pid
29
+ });
30
+ /**
31
+ * Hold options wired to this module — the bind-first boot in one line:
32
+ *
33
+ * const hold = await holdApiPort(ctx.cfg, bootHold(HEALTH_PATH))
34
+ * try { await init() } catch (e) { setBootPhase('failed', message); return }
35
+ * await hold.release()
36
+ * setBootPhase('ready')
37
+ * await ctx.getApiServer().listen()
38
+ *
39
+ * The hold evaluates `payload` per request, so the phase set after a failure is reported live by
40
+ * the listener that is already bound rather than needing a server that will never start.
41
+ */
42
+ export const bootHold = (okPath) => ({
43
+ ...(okPath != null ? { okPath } : {}),
44
+ payload: bootHealthPayload
45
+ });
46
+ //# sourceMappingURL=boot-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot-state.js","sourceRoot":"","sources":["../src/boot-state.ts"],"names":[],"mappings":"AA2BA,kFAAkF;AAClF,MAAM,CAAC,MAAM,WAAW,GAAG,kBAAkB,CAAA;AAE7C,IAAI,MAAM,GAAc,cAAc,CAAA;AACtC,IAAI,OAA2B,CAAA;AAE/B,MAAM,CAAC,MAAM,YAAY,GAAG,GAAc,EAAE,CAAC,MAAM,CAAA;AAEnD,MAAM,CAAC,MAAM,aAAa,GAAG,GAAuB,EAAE,CAAC,OAAO,CAAA;AAE9D;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAgB,EAAE,MAAe,EAAQ,EAAE;IACtE,MAAM,GAAG,KAAK,CAAA;IACd,OAAO,GAAG,MAAM,CAAA;AAClB,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAe,EAAE,CAAC,CAAC;IAClD,MAAM,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI;IAC7C,KAAK,EAAE,MAAM;IACb,EAAE,EAAE,MAAM,KAAK,OAAO;IACtB,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/C,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE;IACtC,GAAG,EAAE,OAAO,CAAC,GAAG;CACjB,CAAC,CAAA;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAe,EAAsB,EAAE,CAAC,CAAC;IAChE,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACrC,OAAO,EAAE,iBAAiB;CAC3B,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAMvD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,cAAa,OAAO,MAkBnG,CAAA"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAMvD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,cAAa,OAAO,MAgBnG,CAAA"}
package/build/context.js CHANGED
@@ -15,7 +15,6 @@ export const makeContext = (cfg, customize = false) => {
15
15
  appendStaticResource(context, AUTH_CACHE);
16
16
  appendAuthService(context);
17
17
  }
18
- context.makeContext = cfg => makeContext(cfg, customize);
19
18
  return context;
20
19
  };
21
20
  //# sourceMappingURL=context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAErF,MAAM,CAAC,MAAM,WAAW,GAAG,CAA+C,GAAM,EAAE,SAAS,GAAY,KAAK,EAAE,EAAE;IAC9G,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAM,CAAA;IAE3C,eAAe,CAAO,OAAO,CAAC,CAAA;IAC9B,eAAe,CAAO,OAAO,CAAC,CAAA;IAC9B,mBAAmB,CAAO,OAAO,CAAC,CAAA;IAElC,OAAO,CAAC,kBAAkB,CAAC,sBAAsB,EAAE,CAAC,CAAA;IAEpD,oBAAoB,CAAO,OAAO,CAAC,CAAA;IACnC,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;QACnD,oBAAoB,CAAO,OAAO,EAAE,UAAU,CAAC,CAAA;QAC/C,iBAAiB,CAAO,OAAO,CAAC,CAAA;IAClC,CAAC;IAED,OAAO,CAAC,WAAW,GAAG,GAAG,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,SAAS,CAAQ,CAAA;IAE/D,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAErF,MAAM,CAAC,MAAM,WAAW,GAAG,CAA+C,GAAM,EAAE,SAAS,GAAY,KAAK,EAAE,EAAE;IAC9G,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAM,CAAA;IAE3C,eAAe,CAAO,OAAO,CAAC,CAAA;IAC9B,eAAe,CAAO,OAAO,CAAC,CAAA;IAC9B,mBAAmB,CAAO,OAAO,CAAC,CAAA;IAElC,OAAO,CAAC,kBAAkB,CAAC,sBAAsB,EAAE,CAAC,CAAA;IAEpD,oBAAoB,CAAO,OAAO,CAAC,CAAA;IACnC,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;QACnD,oBAAoB,CAAO,OAAO,EAAE,UAAU,CAAC,CAAA;QAC/C,iBAAiB,CAAO,OAAO,CAAC,CAAA;IAClC,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare const entrypoints: import("@owlmeans/server-entrypoint").ServerEntrypoint<unknown>[];
2
+ //# sourceMappingURL=entrypoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entrypoints.d.ts","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,WAAW,mEAA0B,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { entrypoints as apiConfig } from '@owlmeans/api-config-server';
2
+ import { entrypoints as list } from '@owlmeans/server-auth';
3
+ export const entrypoints = [...list, ...apiConfig];
4
+ //# sourceMappingURL=entrypoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entrypoints.js","sourceRoot":"","sources":["../src/entrypoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,SAAS,EAAE,MAAM,6BAA6B,CAAA;AACtE,OAAO,EAAE,WAAW,IAAI,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAE3D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC,CAAA"}
@@ -2,17 +2,18 @@ export { config } from '@owlmeans/server-context';
2
2
  export { sservice } from '@owlmeans/server-config';
3
3
  export { addWebService } from '@owlmeans/client-config';
4
4
  export { service, toConfigRecord, PLUGINS } from '@owlmeans/config';
5
- export { parent, filter, body, params, EntrypointOutcome } from '@owlmeans/entrypoint';
5
+ export { contract, filter, body, params, EntrypointOutcome, protocol, typed } from '@owlmeans/entrypoint';
6
6
  export type { AbstractRequest as Request, AbstractResponse as Response } from '@owlmeans/entrypoint';
7
7
  export { createListSchema, filterObject } from '@owlmeans/resource';
8
- export type { ListResult } from '@owlmeans/resource';
9
- export { AppType, BASE, Layer, assertContext } from '@owlmeans/context';
8
+ export type { Criteria, ListOptions, ListQuery, ListResult, Sort } from '@owlmeans/resource';
9
+ export { AppType, BASE, assertContext } from '@owlmeans/context';
10
10
  export { elevate, entrypoint, guard } from '@owlmeans/server-entrypoint';
11
11
  export { elevate as celevate } from '@owlmeans/client-entrypoint';
12
12
  export type { ServerEntrypoint as Module, RefedEntrypointHandler } from '@owlmeans/server-entrypoint';
13
13
  export type { ClientEntrypoint } from '@owlmeans/client-entrypoint';
14
14
  export { route as broute } from '@owlmeans/server-route';
15
- export { handleRequest, handleBody, handleParams } from '@owlmeans/server-api';
15
+ export { handlers, handleRequest, handleBody, handleParams, holdApiPort } from '@owlmeans/server-api';
16
+ export type { ApiPortHold, ApiPortHoldOptions } from '@owlmeans/server-api';
16
17
  export { route } from '@owlmeans/route';
17
18
  export { DEFAULT_ALIAS as DAUTH_GUARD } from '@owlmeans/server-auth';
18
19
  export { klusterize } from '@owlmeans/kluster';
@@ -1 +1 @@
1
- {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACtF,YAAY,EAAE,eAAe,IAAI,OAAO,EAAE,gBAAgB,IAAI,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpG,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACnE,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACvE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AACjE,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACrG,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AACzG,YAAY,EAAE,eAAe,IAAI,OAAO,EAAE,gBAAgB,IAAI,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpG,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACnE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAC5F,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AACjE,YAAY,EAAE,gBAAgB,IAAI,MAAM,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACrG,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACrG,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA"}
package/build/exports.js CHANGED
@@ -2,13 +2,13 @@ export { config } from '@owlmeans/server-context';
2
2
  export { sservice } from '@owlmeans/server-config';
3
3
  export { addWebService } from '@owlmeans/client-config';
4
4
  export { service, toConfigRecord, PLUGINS } from '@owlmeans/config';
5
- export { parent, filter, body, params, EntrypointOutcome } from '@owlmeans/entrypoint';
5
+ export { contract, filter, body, params, EntrypointOutcome, protocol, typed } from '@owlmeans/entrypoint';
6
6
  export { createListSchema, filterObject } from '@owlmeans/resource';
7
- export { AppType, BASE, Layer, assertContext } from '@owlmeans/context';
7
+ export { AppType, BASE, assertContext } from '@owlmeans/context';
8
8
  export { elevate, entrypoint, guard } from '@owlmeans/server-entrypoint';
9
9
  export { elevate as celevate } from '@owlmeans/client-entrypoint';
10
10
  export { route as broute } from '@owlmeans/server-route';
11
- export { handleRequest, handleBody, handleParams } from '@owlmeans/server-api';
11
+ export { handlers, handleRequest, handleBody, handleParams, holdApiPort } from '@owlmeans/server-api';
12
12
  export { route } from '@owlmeans/route';
13
13
  export { DEFAULT_ALIAS as DAUTH_GUARD } from '@owlmeans/server-auth';
14
14
  export { klusterize } from '@owlmeans/kluster';
@@ -1 +1 @@
1
- {"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAEtF,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEnE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACvE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AAGjE,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"exports.js","sourceRoot":"","sources":["../src/exports.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAEzG,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEnE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAA;AACxE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AAGjE,OAAO,EAAE,KAAK,IAAI,MAAM,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAErG,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,aAAa,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA"}
package/build/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './exports.js';
2
2
  export * from './main.js';
3
+ export * from './boot-state.js';
3
4
  export type * from './types.js';
4
5
  export * from './context.js';
5
- export * from './modules.js';
6
+ export * from './entrypoints.js';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,mBAAmB,YAAY,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAC/B,mBAAmB,YAAY,CAAA;AAC/B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA"}
package/build/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './exports.js';
2
2
  export * from './main.js';
3
+ export * from './boot-state.js';
3
4
  export * from './context.js';
4
- export * from './modules.js';
5
+ export * from './entrypoints.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AAEzB,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA;AAE/B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA"}
package/build/main.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ServerEntrypoint } from '@owlmeans/server-entrypoint';
2
2
  import type { CommonEntrypoint } from '@owlmeans/entrypoint';
3
3
  import type { AppContext, AppConfig } from './types.js';
4
- export declare const main: <R, C extends AppConfig, T extends AppContext<C>>(ctx: T, modules: (CommonEntrypoint | ServerEntrypoint<R>)[]) => Promise<void>;
4
+ export declare const main: <R, C extends AppConfig, T extends AppContext<C>>(ctx: T, entrypoints: (CommonEntrypoint | ServerEntrypoint<R>)[]) => Promise<void>;
5
5
  //# sourceMappingURL=main.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAIvD,eAAO,MAAM,IAAI,GAAU,CAAC,EAAE,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,OACnE,CAAC,WAAW,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,kBAK5D,CAAA"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAIvD,eAAO,MAAM,IAAI,GAAU,CAAC,EAAE,CAAC,SAAS,SAAS,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,OACnE,CAAC,eAAe,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,kBAKhE,CAAA"}
package/build/main.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @TODO Remove or refactor request types. We actually don't use them and this make
2
2
  // a mess with <R
3
- export const main = async (ctx, modules) => {
4
- ctx.registerEntrypoints(modules);
3
+ export const main = async (ctx, entrypoints) => {
4
+ ctx.registerEntrypoints(entrypoints);
5
5
  await ctx.configure().init();
6
6
  await ctx.getApiServer().listen();
7
7
  };
package/build/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAMA,mFAAmF;AACnF,iBAAiB;AACjB,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,EACvB,GAAM,EAAE,OAAmD,EAC3D,EAAE;IACF,GAAG,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAChC,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,CAAA;IAC5B,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,CAAA;AACnC,CAAC,CAAA"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAMA,mFAAmF;AACnF,iBAAiB;AACjB,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,EACvB,GAAM,EAAE,WAAuD,EAC/D,EAAE;IACF,GAAG,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAA;IACpC,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,CAAA;IAC5B,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,CAAA;AACnC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-app",
3
- "version": "0.1.18-rc.2",
3
+ "version": "0.1.18-rc.20",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,21 +21,21 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@owlmeans/api": "^0.1.18-rc.2",
25
- "@owlmeans/client-config": "^0.1.18-rc.2",
26
- "@owlmeans/client-entrypoint": "^0.1.18-rc.2",
27
- "@owlmeans/config": "^0.1.18-rc.2",
28
- "@owlmeans/context": "^0.1.18-rc.2",
29
- "@owlmeans/kluster": "^0.1.18-rc.2",
30
- "@owlmeans/entrypoint": "^0.1.18-rc.2",
31
- "@owlmeans/route": "^0.1.18-rc.2",
32
- "@owlmeans/server-api": "^0.1.18-rc.2",
33
- "@owlmeans/server-auth": "^0.1.18-rc.2",
34
- "@owlmeans/server-context": "^0.1.18-rc.2",
35
- "@owlmeans/server-entrypoint": "^0.1.18-rc.2",
36
- "@owlmeans/server-route": "^0.1.18-rc.2",
37
- "@owlmeans/server-socket": "^0.1.18-rc.2",
38
- "@owlmeans/static-resource": "^0.1.18-rc.2"
24
+ "@owlmeans/api": "^0.1.18-rc.15",
25
+ "@owlmeans/client-config": "^0.1.18-rc.14",
26
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.15",
27
+ "@owlmeans/config": "^0.1.18-rc.14",
28
+ "@owlmeans/context": "^0.1.18-rc.10",
29
+ "@owlmeans/kluster": "^0.1.18-rc.14",
30
+ "@owlmeans/entrypoint": "^0.1.18-rc.13",
31
+ "@owlmeans/route": "^0.1.18-rc.11",
32
+ "@owlmeans/server-api": "^0.1.18-rc.19",
33
+ "@owlmeans/server-auth": "^0.1.18-rc.20",
34
+ "@owlmeans/server-context": "^0.1.18-rc.14",
35
+ "@owlmeans/server-entrypoint": "^0.1.18-rc.13",
36
+ "@owlmeans/server-route": "^0.1.18-rc.11",
37
+ "@owlmeans/server-socket": "^0.1.18-rc.20",
38
+ "@owlmeans/static-resource": "^0.1.18-rc.11"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@owlmeans/dep-config": "workspace:*",
@@ -0,0 +1,78 @@
1
+ import type { ApiPortHoldOptions } from '@owlmeans/server-api'
2
+
3
+ /**
4
+ * Boot phase of the process.
5
+ *
6
+ * An app binds its port before it initializes, so that a failure has somewhere to be reported
7
+ * from. Without a listener the only thing anyone sees is a connection refused at the edge — an
8
+ * opaque 503 that names neither the app nor the cause. This phase is what the health path answers
9
+ * with, and what the platform reads to tell "still starting" from "gave up, and here is why".
10
+ */
11
+ export type BootPhase = 'initializing' | 'ready' | 'failed'
12
+
13
+ export interface BootHealth extends Record<string, unknown> {
14
+ status: 'OK' | 'FAILED'
15
+ phase: BootPhase
16
+ ok: boolean
17
+ /** Whatever `setBootPhase` was given — the failure message, or the step being waited on. */
18
+ reason?: string
19
+ /**
20
+ * Identity, not health: a supervisor passes the id to the child it spawns and compares it
21
+ * here, so an answer carrying another id means the port is held by a leftover process and the
22
+ * health it reports describes code nobody asked for. Empty when nothing supplied one.
23
+ */
24
+ bootId: string
25
+ pid: number
26
+ }
27
+
28
+ /** Env var a supervisor stamps the spawned process with; surfaced as `bootId`. */
29
+ export const BOOT_ID_ENV = 'OWLMEANS_BOOT_ID'
30
+
31
+ let _phase: BootPhase = 'initializing'
32
+ let _detail: string | undefined
33
+
34
+ export const getBootPhase = (): BootPhase => _phase
35
+
36
+ export const getBootDetail = (): string | undefined => _detail
37
+
38
+ /**
39
+ * `detail` is free text, and is kept for every phase — a boot that is merely slow is as worth
40
+ * explaining as one that failed.
41
+ */
42
+ export const setBootPhase = (phase: BootPhase, detail?: string): void => {
43
+ _phase = phase
44
+ _detail = detail
45
+ }
46
+
47
+ /**
48
+ * The body the health path answers with, from either server.
49
+ *
50
+ * The port hold and the app's own handler both build it here, so a consumer polling through the
51
+ * handover never sees the shape change under it — which is why the keys are unconditional and
52
+ * only `reason` comes and goes with the state it describes.
53
+ */
54
+ export const bootHealthPayload = (): BootHealth => ({
55
+ status: _phase === 'failed' ? 'FAILED' : 'OK',
56
+ phase: _phase,
57
+ ok: _phase === 'ready',
58
+ ...(_detail != null ? { reason: _detail } : {}),
59
+ bootId: process.env[BOOT_ID_ENV] ?? '',
60
+ pid: process.pid
61
+ })
62
+
63
+ /**
64
+ * Hold options wired to this module — the bind-first boot in one line:
65
+ *
66
+ * const hold = await holdApiPort(ctx.cfg, bootHold(HEALTH_PATH))
67
+ * try { await init() } catch (e) { setBootPhase('failed', message); return }
68
+ * await hold.release()
69
+ * setBootPhase('ready')
70
+ * await ctx.getApiServer().listen()
71
+ *
72
+ * The hold evaluates `payload` per request, so the phase set after a failure is reported live by
73
+ * the listener that is already bound rather than needing a server that will never start.
74
+ */
75
+ export const bootHold = (okPath?: string): ApiPortHoldOptions => ({
76
+ ...(okPath != null ? { okPath } : {}),
77
+ payload: bootHealthPayload
78
+ })
package/src/context.ts CHANGED
@@ -21,7 +21,5 @@ export const makeContext = <C extends AppConfig, T extends AppContext<C>>(cfg: C
21
21
  appendAuthService<C, T>(context)
22
22
  }
23
23
 
24
- context.makeContext = cfg => makeContext(cfg, customize) as any
25
-
26
24
  return context
27
25
  }
@@ -0,0 +1,4 @@
1
+ import { entrypoints as apiConfig } from '@owlmeans/api-config-server'
2
+ import { entrypoints as list } from '@owlmeans/server-auth'
3
+
4
+ export const entrypoints = [...list, ...apiConfig]
package/src/exports.ts CHANGED
@@ -3,17 +3,18 @@ export { config } from '@owlmeans/server-context'
3
3
  export { sservice } from '@owlmeans/server-config'
4
4
  export { addWebService } from '@owlmeans/client-config'
5
5
  export { service, toConfigRecord, PLUGINS } from '@owlmeans/config'
6
- export { parent, filter, body, params, EntrypointOutcome } from '@owlmeans/entrypoint'
6
+ export { contract, filter, body, params, EntrypointOutcome, protocol, typed } from '@owlmeans/entrypoint'
7
7
  export type { AbstractRequest as Request, AbstractResponse as Response } from '@owlmeans/entrypoint'
8
8
  export { createListSchema, filterObject } from '@owlmeans/resource'
9
- export type { ListResult } from '@owlmeans/resource'
10
- export { AppType, BASE, Layer, assertContext } from '@owlmeans/context'
9
+ export type { Criteria, ListOptions, ListQuery, ListResult, Sort } from '@owlmeans/resource'
10
+ export { AppType, BASE, assertContext } from '@owlmeans/context'
11
11
  export { elevate, entrypoint, guard } from '@owlmeans/server-entrypoint'
12
12
  export { elevate as celevate } from '@owlmeans/client-entrypoint'
13
13
  export type { ServerEntrypoint as Module, RefedEntrypointHandler } from '@owlmeans/server-entrypoint'
14
14
  export type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
15
15
  export { route as broute } from '@owlmeans/server-route'
16
- export { handleRequest, handleBody, handleParams } from '@owlmeans/server-api'
16
+ export { handlers, handleRequest, handleBody, handleParams, holdApiPort } from '@owlmeans/server-api'
17
+ export type { ApiPortHold, ApiPortHoldOptions } from '@owlmeans/server-api'
17
18
  export { route } from '@owlmeans/route'
18
19
  export { DEFAULT_ALIAS as DAUTH_GUARD } from '@owlmeans/server-auth'
19
20
  export { klusterize } from '@owlmeans/kluster'
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
 
2
2
  export * from './exports.js'
3
3
  export * from './main.js'
4
+ export * from './boot-state.js'
4
5
  export type * from './types.js'
5
6
  export * from './context.js'
6
- export * from './modules.js'
7
+ export * from './entrypoints.js'
package/src/main.ts CHANGED
@@ -7,9 +7,9 @@ import type { AppContext, AppConfig } from './types.js'
7
7
  // @TODO Remove or refactor request types. We actually don't use them and this make
8
8
  // a mess with <R
9
9
  export const main = async <R, C extends AppConfig, T extends AppContext<C>>(
10
- ctx: T, modules: (CommonEntrypoint | ServerEntrypoint<R>)[]
10
+ ctx: T, entrypoints: (CommonEntrypoint | ServerEntrypoint<R>)[]
11
11
  ) => {
12
- ctx.registerEntrypoints(modules)
12
+ ctx.registerEntrypoints(entrypoints)
13
13
  await ctx.configure().init()
14
14
  await ctx.getApiServer().listen()
15
15
  }
package/build/.gitkeep DELETED
File without changes
@@ -1,2 +0,0 @@
1
- export declare const modules: import("@owlmeans/server-entrypoint").ServerEntrypoint<unknown>[];
2
- //# sourceMappingURL=modules.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,mEAA0B,CAAA"}
package/build/modules.js DELETED
@@ -1,4 +0,0 @@
1
- import { modules as apiConfig } from '@owlmeans/api-config-server';
2
- import { modules as list } from '@owlmeans/server-auth';
3
- export const modules = [...list, ...apiConfig];
4
- //# sourceMappingURL=modules.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"modules.js","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,6BAA6B,CAAA;AAClE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAEvD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC,CAAA"}
package/src/modules.ts DELETED
@@ -1,4 +0,0 @@
1
- import { modules as apiConfig } from '@owlmeans/api-config-server'
2
- import { modules as list } from '@owlmeans/server-auth'
3
-
4
- export const modules = [...list, ...apiConfig]