@owlmeans/server-app 0.1.18-rc.17 → 0.1.18-rc.19
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 +61 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-app/SKILL.md +141 -19
- package/build/boot-state.d.ts +55 -0
- package/build/boot-state.d.ts.map +1 -0
- package/build/boot-state.js +46 -0
- package/build/boot-state.js.map +1 -0
- package/build/context.d.ts.map +1 -1
- package/build/context.js.map +1 -1
- package/build/exports.d.ts +2 -2
- package/build/exports.d.ts.map +1 -1
- package/build/exports.js +2 -2
- package/build/exports.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/main.d.ts.map +1 -1
- package/package.json +16 -16
- package/src/boot-state.ts +78 -0
- package/src/exports.ts +2 -2
- package/src/index.ts +1 -0
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
|
|
@@ -44,6 +45,47 @@ elevate(appEntrypoints, 'project-create', handleBody<CreateProject>(async (paylo
|
|
|
44
45
|
}), guard(GUARD_ED25519))
|
|
45
46
|
```
|
|
46
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
|
+
|
|
47
89
|
## API
|
|
48
90
|
|
|
49
91
|
### `makeContext<C, T>(cfg, customize?): T`
|
|
@@ -54,6 +96,23 @@ Creates a server context with Fastify HTTP, WebSocket, static resources, and aut
|
|
|
54
96
|
|
|
55
97
|
Registers entrypoints, calls `configure().init()`, then starts the Fastify server.
|
|
56
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
|
+
|
|
57
116
|
### Re-exported symbols (for convenience)
|
|
58
117
|
|
|
59
118
|
| Symbol | Source |
|
|
@@ -94,7 +153,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
94
153
|
your project's skill store (`.agents/skills/`):
|
|
95
154
|
|
|
96
155
|
```sh
|
|
97
|
-
npx @owlmeans/agent-skills
|
|
156
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.13
|
|
98
157
|
```
|
|
99
158
|
|
|
100
159
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-app",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.19",
|
|
5
|
+
"generatedAt": "2026-09-10T18:54:42.498Z",
|
|
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 entrypoint declarations,
|
|
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,7 +8,11 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/server-app
|
|
9
9
|
|
|
10
10
|
**Layer:** Server
|
|
11
|
-
**Install:** `"@owlmeans/server-app": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/server-app": "^0.1.18-rc.19"` 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
|
|
|
@@ -16,17 +20,56 @@ user-invocable: false
|
|
|
16
20
|
|--------|-------------|
|
|
17
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`) |
|
|
18
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
|
+
|--------|-------------|
|
|
19
53
|
| `handleRequest(fn)` | Wrap an async function as a server handler `(req, context) => result` |
|
|
20
54
|
| `handleBody<T>(fn)` | Wrap an async function with validated body — `(payload, context, req) => result` |
|
|
21
55
|
| `handleParams<T>(fn)` | The same, over validated URL params |
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
|
|
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
|
+
|--------|-------------|
|
|
27
65
|
| `holdApiPort(cfg, opts?)` | Own the app's port during boot |
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
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 |
|
|
30
73
|
|
|
31
74
|
## Usage
|
|
32
75
|
|
|
@@ -42,27 +85,90 @@ const context = makeContext(config)
|
|
|
42
85
|
main<{}, Config, Context>(context, appEntrypoints)
|
|
43
86
|
```
|
|
44
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
|
+
|
|
45
140
|
### Handlers
|
|
46
141
|
```typescript
|
|
47
142
|
import { handleRequest, handleBody } from '@owlmeans/server-app'
|
|
48
|
-
import {
|
|
143
|
+
import { requireEntityKey } from '@owlmeans/auth-common'
|
|
49
144
|
import type { ClientEntrypoint } from '@owlmeans/server-app'
|
|
50
145
|
|
|
51
146
|
export const list = handleRequest(async (req, context) => {
|
|
52
|
-
if (req.auth?.entityId == null) throw new AuthUnknown('entity')
|
|
53
147
|
const ctx = context as Context
|
|
54
|
-
const { items } = await ctx.project().list({ entityId: req
|
|
148
|
+
const { items } = await ctx.project().list({ entityId: requireEntityKey(req) })
|
|
55
149
|
return items
|
|
56
150
|
})
|
|
57
151
|
|
|
58
|
-
export const create = handleBody<CreateProject>(async (payload, context
|
|
152
|
+
export const create = handleBody<CreateProject>(async (payload, context) => {
|
|
59
153
|
const ctx = context as Context
|
|
60
154
|
return await ctx.entrypoint<ClientEntrypoint<Project>>(agent.project.create).call({
|
|
61
|
-
body: { prompt: payload.prompt
|
|
155
|
+
body: { prompt: payload.prompt }
|
|
62
156
|
})
|
|
63
157
|
})
|
|
64
158
|
```
|
|
65
159
|
|
|
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
|
+
|
|
66
172
|
A cross-service call from a handler uses `call()` — it resolves to the value and throws whatever the
|
|
67
173
|
peer replied. Reach for `invoke()` only where the outcome decides what happens next.
|
|
68
174
|
|
|
@@ -79,13 +185,29 @@ elevate(managerEntrypoints, manager.back.project.list, list)
|
|
|
79
185
|
export const appEntrypoints = [...systemEntrypoints, ...paymentEntrypoints, ...managerEntrypoints]
|
|
80
186
|
```
|
|
81
187
|
|
|
82
|
-
A backend alias that carries only a `guard()`/`gate()` for its children still needs a **bare**
|
|
83
|
-
`elevate(entrypoints, alias)` — that is what makes the declaration a live server route group.
|
|
84
188
|
Elevating is idempotent, so a later `elevate` of the same alias is legal and simply replaces the
|
|
85
189
|
element again; the guards it brings are added to the declared ones rather than replacing them.
|
|
86
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
|
+
|
|
87
198
|
## Depends On
|
|
88
199
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
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"}
|
package/build/context.d.ts.map
CHANGED
|
@@ -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,
|
|
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.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/build/exports.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ 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 { 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
8
|
export type { Criteria, ListOptions, ListQuery, ListResult, Sort } from '@owlmeans/resource';
|
|
@@ -12,7 +12,7 @@ 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, holdApiPort } from '@owlmeans/server-api';
|
|
15
|
+
export { handlers, handleRequest, handleBody, handleParams, holdApiPort } from '@owlmeans/server-api';
|
|
16
16
|
export type { ApiPortHold, ApiPortHoldOptions } from '@owlmeans/server-api';
|
|
17
17
|
export { route } from '@owlmeans/route';
|
|
18
18
|
export { DEFAULT_ALIAS as DAUTH_GUARD } from '@owlmeans/server-auth';
|
package/build/exports.d.ts.map
CHANGED
|
@@ -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,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,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 { 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
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, holdApiPort } 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';
|
package/build/exports.js.map
CHANGED
|
@@ -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,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,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
package/build/index.d.ts.map
CHANGED
|
@@ -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,kBAAkB,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
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,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.map
CHANGED
|
@@ -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,
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/server-app",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.19",
|
|
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.
|
|
25
|
-
"@owlmeans/client-config": "^0.1.18-rc.
|
|
26
|
-
"@owlmeans/client-entrypoint": "^0.1.18-rc.
|
|
27
|
-
"@owlmeans/config": "^0.1.18-rc.
|
|
28
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
29
|
-
"@owlmeans/kluster": "^0.1.18-rc.
|
|
30
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
31
|
-
"@owlmeans/route": "^0.1.18-rc.
|
|
32
|
-
"@owlmeans/server-api": "^0.1.18-rc.
|
|
33
|
-
"@owlmeans/server-auth": "^0.1.18-rc.
|
|
34
|
-
"@owlmeans/server-context": "^0.1.18-rc.
|
|
35
|
-
"@owlmeans/server-entrypoint": "^0.1.18-rc.
|
|
36
|
-
"@owlmeans/server-route": "^0.1.18-rc.
|
|
37
|
-
"@owlmeans/server-socket": "^0.1.18-rc.
|
|
38
|
-
"@owlmeans/static-resource": "^0.1.18-rc.
|
|
24
|
+
"@owlmeans/api": "^0.1.18-rc.14",
|
|
25
|
+
"@owlmeans/client-config": "^0.1.18-rc.13",
|
|
26
|
+
"@owlmeans/client-entrypoint": "^0.1.18-rc.14",
|
|
27
|
+
"@owlmeans/config": "^0.1.18-rc.13",
|
|
28
|
+
"@owlmeans/context": "^0.1.18-rc.9",
|
|
29
|
+
"@owlmeans/kluster": "^0.1.18-rc.13",
|
|
30
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.12",
|
|
31
|
+
"@owlmeans/route": "^0.1.18-rc.10",
|
|
32
|
+
"@owlmeans/server-api": "^0.1.18-rc.18",
|
|
33
|
+
"@owlmeans/server-auth": "^0.1.18-rc.19",
|
|
34
|
+
"@owlmeans/server-context": "^0.1.18-rc.13",
|
|
35
|
+
"@owlmeans/server-entrypoint": "^0.1.18-rc.12",
|
|
36
|
+
"@owlmeans/server-route": "^0.1.18-rc.10",
|
|
37
|
+
"@owlmeans/server-socket": "^0.1.18-rc.19",
|
|
38
|
+
"@owlmeans/static-resource": "^0.1.18-rc.10"
|
|
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/exports.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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 { 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
9
|
export type { Criteria, ListOptions, ListQuery, ListResult, Sort } from '@owlmeans/resource'
|
|
@@ -13,7 +13,7 @@ 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, holdApiPort } from '@owlmeans/server-api'
|
|
16
|
+
export { handlers, handleRequest, handleBody, handleParams, holdApiPort } from '@owlmeans/server-api'
|
|
17
17
|
export type { ApiPortHold, ApiPortHoldOptions } from '@owlmeans/server-api'
|
|
18
18
|
export { route } from '@owlmeans/route'
|
|
19
19
|
export { DEFAULT_ALIAS as DAUTH_GUARD } from '@owlmeans/server-auth'
|