@luckystack/server 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,87 +1,102 @@
1
- # Changelog
2
-
3
- All notable changes to `@luckystack/server` are documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [Unreleased]
9
-
10
- ### Fixed
11
-
12
- - **Redis secret-manager pointer boot** (ADR 0026): fixes the `WRONGPASS ... REDIS_PASSWORD_V<n>`
13
- boot failure with no consumer code — the `registerRedisClient(...)` workaround can be removed.
14
- The rebuild is triggered by `@luckystack/secret-manager` firing core's secrets-resolved channel
15
- at resolve time (see the `@luckystack/core` changelog). An intermediate 0.6.3/0.6.4 attempt did
16
- this from `createLuckyStackServer` gated on `config.secretManager.url`, but that gate is always
17
- falsy (the scaffold doesn't register `secretManager` into `projectConfig`) that vestigial gate
18
- is now REMOVED; the channel is the single trigger.
19
-
20
- ## [0.6.0] - 2026-07-12
21
-
22
- ### Added
23
-
24
- - **Email-code login + 2FA routes** (ADR 0024, `authSecondFactorRoutes.ts`):
25
- POST `/auth/api/email-code/request|verify`, `/auth/api/2fa` (completes a
26
- pending challenge through the session-cookie seam), `/auth/api/2fa/email-code`,
27
- and the authenticated enrollment routes `/auth/api/2fa/setup|enable|disable|
28
- recovery-codes` (fresh user re-read via the UserAdapter — the session copy is
29
- sanitized). Registered before the `/auth/api/*` catch-all; per-IP shields.
30
- - `/auth/providers` now advertises `emailCodeLogin` so the login form can show
31
- the passwordless entry point.
32
-
33
- ### Changed
34
-
35
- - `/auth/api/credentials` relays the 2FA challenge envelope
36
- (`requiresTwoFactor`, `challengeToken`, `twoFactorMethods` — no session
37
- transport) when the account has a second factor enrolled.
38
- - CSRF middleware: the login-completing email-code/2FA routes joined the
39
- auth-bootstrap exemption set; the authed enrollment routes stay enforced.
40
-
41
- ## [0.5.0] - 2026-07-11
42
-
43
- ### Added
44
-
45
- - `OVERLAY_ORDER` exported (consumed by the consumer's `bundleServer.mjs` at
46
- build time — kills the hardcoded-copy drift that silently dropped overlay
47
- slots from prod bundles). New `cron` overlay slot + `@luckystack/cron` in
48
- `OPTIONAL_PACKAGES` (boot auto-wiring).
49
- - `/readyz` database check is pluggable (core `registerDbHealthCheck`):
50
- registered probe → built-in Prisma ping (when Prisma is present) →
51
- `'skipped'` for deliberately DB-less projects. Response gains the
52
- tri-state `checks.database`; `checks.prisma` kept for compatibility.
53
-
54
- ### Changed
55
-
56
- - Overlay files that fail to import abort boot with an actionable error
57
- naming the file (was: raw ERR_MODULE_NOT_FOUND).
58
- - Dev SIGINT/SIGTERM dispatches `preServerStop` (2s cap) before exiting so
59
- subscribers (e.g. the cron leader lease) release cleanly.
60
- - `@prisma/client` peer dependency is now optional (ADR 0020).
61
-
62
- ## [0.1.5]
63
-
64
- ### Changed
65
-
66
- - **OAuth post-login redirect no longer reads the `DNS` env var.** `authCallbackRoute`
67
- now redirects to `projectConfig.app.publicUrl` (the public origin where users
68
- browse) after a callback, instead of `process.env.DNS || app.publicUrl`. The
69
- callback is handled on the backend origin but must send the browser back to the
70
- public origin. Set `app.publicUrl` in your `config.ts` (the scaffold does this).
71
-
72
- ### Fixed
73
-
74
- - **CSRF no longer blocks credentials login/register when a session cookie already
75
- exists.** `POST /auth/api/credentials` is the session bootstrap, so requiring a
76
- pre-existing session's CSRF token to authenticate is circular and broke
77
- legitimate same-site re-login/register (403 `auth.csrfMismatch`). That endpoint
78
- is now exempt from CSRF enforcement. This removes no real protection: the session
79
- cookie is `SameSite=Strict`, so a cross-site POST never carries it and the guard
80
- wouldn't have fired anyway. All other `/auth/api/*`, `/api/*`, and `/sync/*`
81
- state-changing routes remain protected.
82
-
83
- ## [0.1.0]
84
-
85
- ### Added
86
-
87
- - Initial public release as part of the LuckyStack package split.
1
+ # Changelog
2
+
3
+ All notable changes to `@luckystack/server` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.7.0] - 2026-07-16
11
+
12
+ ### Fixed
13
+
14
+ - **Under Bun, EVERY optional package was reported as absent.** `capabilities.ts`
15
+ cached a detached `import.meta.resolve`. Node calls a detached reference happily;
16
+ Bun throws `import.meta.resolve must be bound to an import.meta object`, and the
17
+ detection catches that and returns `false`. So on Bun, `@luckystack/login`,
18
+ `sync`, `presence`, `cron`, `docs-ui`, `error-tracking` and `devkit` were all
19
+ silently disabled while the server booted and served a green `/_health`.
20
+
21
+ Node-only deployments were never affected. Fixed by calling `resolve` as a member
22
+ of the `import.meta` object rather than caching the method — `obj.method()` binds
23
+ `this`, `const m = obj.method; m()` does not.
24
+
25
+ ### Fixed
26
+
27
+ - **Redis secret-manager pointer boot** (ADR 0026): fixes the `WRONGPASS ... REDIS_PASSWORD_V<n>`
28
+ boot failure with no consumer code — the `registerRedisClient(...)` workaround can be removed.
29
+ The rebuild is triggered by `@luckystack/secret-manager` firing core's secrets-resolved channel
30
+ at resolve time (see the `@luckystack/core` changelog). An intermediate 0.6.3/0.6.4 attempt did
31
+ this from `createLuckyStackServer` gated on `config.secretManager.url`, but that gate is always
32
+ falsy (the scaffold doesn't register `secretManager` into `projectConfig`) — that vestigial gate
33
+ is now REMOVED; the channel is the single trigger.
34
+
35
+ ## [0.6.0] - 2026-07-12
36
+
37
+ ### Added
38
+
39
+ - **Email-code login + 2FA routes** (ADR 0024, `authSecondFactorRoutes.ts`):
40
+ POST `/auth/api/email-code/request|verify`, `/auth/api/2fa` (completes a
41
+ pending challenge through the session-cookie seam), `/auth/api/2fa/email-code`,
42
+ and the authenticated enrollment routes `/auth/api/2fa/setup|enable|disable|
43
+ recovery-codes` (fresh user re-read via the UserAdapter — the session copy is
44
+ sanitized). Registered before the `/auth/api/*` catch-all; per-IP shields.
45
+ - `/auth/providers` now advertises `emailCodeLogin` so the login form can show
46
+ the passwordless entry point.
47
+
48
+ ### Changed
49
+
50
+ - `/auth/api/credentials` relays the 2FA challenge envelope
51
+ (`requiresTwoFactor`, `challengeToken`, `twoFactorMethods` no session
52
+ transport) when the account has a second factor enrolled.
53
+ - CSRF middleware: the login-completing email-code/2FA routes joined the
54
+ auth-bootstrap exemption set; the authed enrollment routes stay enforced.
55
+
56
+ ## [0.5.0] - 2026-07-11
57
+
58
+ ### Added
59
+
60
+ - `OVERLAY_ORDER` exported (consumed by the consumer's `bundleServer.mjs` at
61
+ build time — kills the hardcoded-copy drift that silently dropped overlay
62
+ slots from prod bundles). New `cron` overlay slot + `@luckystack/cron` in
63
+ `OPTIONAL_PACKAGES` (boot auto-wiring).
64
+ - `/readyz` database check is pluggable (core `registerDbHealthCheck`):
65
+ registered probe → built-in Prisma ping (when Prisma is present) →
66
+ `'skipped'` for deliberately DB-less projects. Response gains the
67
+ tri-state `checks.database`; `checks.prisma` kept for compatibility.
68
+
69
+ ### Changed
70
+
71
+ - Overlay files that fail to import abort boot with an actionable error
72
+ naming the file (was: raw ERR_MODULE_NOT_FOUND).
73
+ - Dev SIGINT/SIGTERM dispatches `preServerStop` (2s cap) before exiting so
74
+ subscribers (e.g. the cron leader lease) release cleanly.
75
+ - `@prisma/client` peer dependency is now optional (ADR 0020).
76
+
77
+ ## [0.1.5]
78
+
79
+ ### Changed
80
+
81
+ - **OAuth post-login redirect no longer reads the `DNS` env var.** `authCallbackRoute`
82
+ now redirects to `projectConfig.app.publicUrl` (the public origin where users
83
+ browse) after a callback, instead of `process.env.DNS || app.publicUrl`. The
84
+ callback is handled on the backend origin but must send the browser back to the
85
+ public origin. Set `app.publicUrl` in your `config.ts` (the scaffold does this).
86
+
87
+ ### Fixed
88
+
89
+ - **CSRF no longer blocks credentials login/register when a session cookie already
90
+ exists.** `POST /auth/api/credentials` is the session bootstrap, so requiring a
91
+ pre-existing session's CSRF token to authenticate is circular and broke
92
+ legitimate same-site re-login/register (403 `auth.csrfMismatch`). That endpoint
93
+ is now exempt from CSRF enforcement. This removes no real protection: the session
94
+ cookie is `SameSite=Strict`, so a cross-site POST never carries it and the guard
95
+ wouldn't have fired anyway. All other `/auth/api/*`, `/api/*`, and `/sync/*`
96
+ state-changing routes remain protected.
97
+
98
+ ## [0.1.0]
99
+
100
+ ### Added
101
+
102
+ - Initial public release as part of the LuckyStack package split.
package/CLAUDE.md CHANGED
@@ -1,87 +1,87 @@
1
- # @luckystack/server
2
-
3
- > AI summary + function INDEX. For deep specs see `docs/` next to this file.
4
-
5
- ## What this package does
6
-
7
- One-call server bootstrap for a LuckyStack project. Wires together a raw Node.js HTTP server, Socket.io (with optional Redis adapter), framework routes (`/api/*`, `/sync/*`, `/_health`, `/livez`, `/readyz`, `/_test/reset`, `/auth/*`, `/uploads/*`, `/csrf-token`), CSRF middleware, CORS + security headers, presence broadcasting, and dev-only hot reload. Consumer's `server.ts` shrinks to roughly twenty lines. Boots are gated by `verifyBootstrap` so missing registrations surface a single readable error instead of mid-request crashes.
8
-
9
- ## When to USE this package
10
-
11
- - Project needs the full LuckyStack runtime (HTTP + sockets + framework routes wired in one call).
12
- - Multi-bundle or multi-service deploys that need positional argv parsing (`npm run server -- <bundles> <port>`) and merged runtime maps across presets.
13
- - Consumers that want framework-shipped health / liveness / readiness probes, CSRF protection, and dev hot reload without building plumbing themselves.
14
- - Adding project-specific HTTP endpoints via `registerCustomRoute(...)` or the `customRoutes` option without forking the package.
15
- - Plugins / framework packages that ship socket-lifecycle behavior via the exported hook payload types (e.g. `@luckystack/presence`).
16
-
17
- ## When to NOT suggest this (yet)
18
-
19
- - Standalone microservices that do not use the LuckyStack registries (project config, deploy config, runtime maps, localized normalizer) — there is nothing for `verifyBootstrap` to verify, and the wiring overhead buys nothing.
20
- - A pure router / load-balancer process — use `@luckystack/router` instead; `@luckystack/server` is for the actual service nodes behind the router.
21
- - Build-time tooling, code generation, or migration scripts that never accept HTTP / socket traffic.
22
- - Replacing the HTTP layer with Express / Fastify — the package owns the raw Node HTTP server and dispatch table; bringing a second framework on top breaks the route handler contract.
23
-
24
- ## Function Index
25
-
26
- | Function / Export | One-liner | Deep doc |
27
- |---|---|---|
28
- | `createLuckyStackServer(options)` | Lower-level factory that returns `{ httpServer, ioServer, listen, stop, close }`. Wires HTTP + Socket.io + framework routes; installs a persistent `httpServer.on('error')` listener and (in prod) SIGTERM/SIGINT → graceful `stop()`. | -> docs/create-server.md |
29
- | `RunningLuckyStackServer.stop(options?)` / `.close(options?)` | Graceful shutdown (MIS-016). Stops accepting new connections, dispatches the core `preServerStop` hook, flushes error-trackers (bounded by a per-step timeout race), then closes io + http + the Redis-adapter pub/sub clients. Each step isolated + time-bounded so one hanging step can't stall shutdown. Idempotent. `options`: `{ reason?, timeoutMs? }` (default `timeoutMs` 10000). | -> docs/create-server.md |
30
- | `bootstrapLuckyStack(options)` | High-level entry: auto-imports `luckystack/<pkg>/*.ts` overlay in topological order, then delegates to `createLuckyStackServer`. An overlay file that fails to import (classic: it imports a package removed via `luckystack remove`) aborts boot with an ACTIONABLE error naming the file, instead of a raw ERR_MODULE_NOT_FOUND. | -> docs/create-server.md |
31
- | `OVERLAY_ORDER` (exported const) | The canonical overlay walk order (`core, deploy, login, email, sentry, presence, cron, docs-ui, server`). Exported so the consumer's `scripts/bundleServer.mjs` imports it at build time — a hardcoded bundler copy once silently dropped the cron slot from prod; `overlayOrderParity.test.ts` pins the bundlers' fallback lists to this. | -> docs/create-server.md |
32
- | `registerOverlayLoader(loader)` | Production-bundle seam: the server bundler (`scripts/bundleServer.mjs`) generates an entry that statically imports the overlay files and registers this loader; `bootstrapLuckyStack` then runs it INSTEAD of the runtime `.ts` folder walk (plain `node` cannot import `.ts` — ERR_UNKNOWN_FILE_EXTENSION). | -> docs/create-server.md |
33
- | `verifyBootstrap(requirements?)` | Pre-flight check for ProjectConfig / DeployConfig / ServicesConfig / OAuth / RuntimeMapsProvider / LocalizedNormalizer. Throws one descriptive `Error`. | -> docs/create-server.md |
34
- | `parseServerArgv(argv)` | Pure parser: validates positional `<bundles> [port]` and returns `{ bundles, port }`. Throws on malformed input. | -> docs/argv-parsing.md |
35
- | `applyServerArgv()` | Side-effect runner: parses `process.argv.slice(2)`, stores bundles + port, writes `process.env.SERVER_PORT` for downstream env readers. Idempotent. | -> docs/argv-parsing.md |
36
- | `getParsedBundles()` | Returns the preset list parsed by `applyServerArgv()` (empty array before first call). | -> docs/argv-parsing.md |
37
- | `getParsedPort()` | Returns the port parsed by `applyServerArgv()` (`null` if argv omitted it). | -> docs/argv-parsing.md |
38
- | `@luckystack/server/parseArgv` (side-effect import) | First-line import that runs `applyServerArgv()` before any module reads `process.env.SERVER_PORT` (notably `config.ts`). | -> docs/argv-parsing.md |
39
- | `createProdRuntimeMapsProvider(options)` | Build a `RuntimeMapsProvider` that loads generated maps in prod and delegates to devkit discovery in dev. Returns the provider without registering. | -> docs/runtime-maps.md |
40
- | `registerProdRuntimeMapsProvider(options)` | Convenience wrapper: builds the provider AND calls `registerRuntimeMapsProvider`. Most consumers want this. | -> docs/runtime-maps.md |
41
- | `registerCustomRoute(handler, options?)` | Append a custom HTTP route handler. `options.phase`: `'post-params'` (default, runs after body parse) or `'pre-params'` (runs before `getParams`, raw `req` stream intact — webhooks + streaming uploads). | -> /docs/ARCHITECTURE_HTTP.md |
42
- | `getCustomRoutes()` | Read the `'post-params'` registry snapshot (the default phase). | -> /docs/ARCHITECTURE_HTTP.md |
43
- | `getPreParamsCustomRoutes()` | Read the `'pre-params'` registry snapshot. | -> /docs/ARCHITECTURE_HTTP.md |
44
- | `clearCustomRoutes()` | Clear both phases (used by test resets). | -> /docs/ARCHITECTURE_HTTP.md |
45
- | `registerOriginExemptPath({ pathPrefix })` | Exempt a path prefix from the browser origin gate so a server-to-server webhook (no `Origin`/`Referer`) reaches its handler. Empty/fail-closed by default. **Exemption ≠ auth** — the handler MUST verify a signature/secret. | -> /docs/ARCHITECTURE_HTTP.md |
46
- | `getOriginExemptPaths()` / `clearOriginExemptPaths()` / `isOriginExemptPath(routePath)` | Read / clear / test the exempt-path registry. | -> /docs/ARCHITECTURE_HTTP.md |
47
- | `registerSecurityHeaders(builder)` | Override / extend the security-headers builder applied to every HTTP response. | -> docs/security-defaults.md |
48
- | `getSecurityHeadersBuilder()` | Read the currently registered builder (defaults to framework headers when no override set). | -> docs/security-defaults.md |
49
- | `registerErrorFormatter(formatter)` | Override the JSON error shape returned by framework error responses. | -> docs/security-defaults.md |
50
- | `getErrorFormatter()` | Read the currently registered formatter. | -> docs/security-defaults.md |
51
- | Route handler: `handleLivezRoute` | Liveness probe at `projectConfig.http.liveEndpoint`. Always 200 when reachable. | -> docs/http-routes.md |
52
- | Route handler: `handleReadyzRoute` | Readiness probe: boot UUID + Redis ping + database check (ADR 0020: a registered `registerDbHealthCheck` probe wins; else the built-in Prisma ping when Prisma is registered/resolvable; else `'skipped'` so a deliberately DB-less project can go ready). Response `checks.database` = tri-state, `checks.prisma` kept for compat. | -> docs/http-routes.md |
53
- | Route handler: `handleHealthRoute` | Health endpoint: returns boot UUID + env hashes for router topology checks. | -> docs/http-routes.md |
54
- | Route handler: `handleTestResetRoute` | Destructive test reset. Fail-closed on `NODE_ENV` and `TEST_RESET_TOKEN`. | -> docs/security-defaults.md |
55
- | Hook payload: `OnSocketConnectPayload` | Payload type for `onSocketConnect` lifecycle hook handlers. | -> docs/create-server.md |
56
- | Hook payload: `OnSocketDisconnectPayload` | Payload type for `onSocketDisconnect` lifecycle hook handlers. | -> docs/create-server.md |
57
- | Hook payload: `PreRoomJoinPayload` | Payload type for `preRoomJoin` hook handlers. | -> docs/create-server.md |
58
- | Hook payload: `PostRoomJoinPayload` | Payload type for `postRoomJoin` hook handlers. | -> docs/create-server.md |
59
- | Hook payload: `PreRoomLeavePayload` | Payload type for `preRoomLeave` hook handlers. | -> docs/create-server.md |
60
- | Hook payload: `PostRoomLeavePayload` | Payload type for `postRoomLeave` hook handlers. | -> docs/create-server.md |
61
- | Hook payload: `OnLocationUpdatePayload` | Payload type for `onLocationUpdate` hook handlers. | -> docs/create-server.md |
62
- | Types: `CreateLuckyStackServerOptions`, `BootstrapLuckyStackOptions`, `BootstrapRequirements`, `RunningLuckyStackServer`, `StopLuckyStackServerOptions`, `RouteContext`, `StaticFileHandler`, `FaviconHandler`, `CustomRouteHandler`, `ProdRuntimeMapsLoaderOptions`, `ParsedServerArgv`, `SecurityHeadersBuilder`, `ErrorFormatter`, `ErrorFormatterContext` | Handler + option typing. | -> docs/create-server.md |
63
-
64
- ## Config keys (env vars + registerProjectConfig slots)
65
-
66
- - `SERVER_PORT` (env, optional) — fallback when neither `options.port` nor positional argv supplies one. Written back by `applyServerArgv()` when argv carries a port. In dev, the ACTUALLY-bound port (after any `SERVER_PORT_AUTO_INCREMENT` hop off a busy port) is advertised to `node_modules/.luckystack/dev-server.json` so the template Vite proxy follows the real port instead of the stale `.env` one (written by `devServerInfo.ts`; skipped in prod + tests, removed on exit).
67
- - `SERVER_IP` (env, optional, default `127.0.0.1`) — bind address fallback when `options.ip` is omitted.
68
- - `NODE_ENV` (env, required for security-sensitive branches) — `development` / `test` toggle devkit hot reload + REPL and gate `/_test/reset`.
69
- - `TEST_RESET_TOKEN` (env, required for `/_test/reset` to be reachable at all) — must match the `x-test-reset-token` request header. No fallback "no auth" mode.
70
- - `projectConfig.http.healthEndpoint` (config) — path served by `handleHealthRoute`. Default `/_health`.
71
- - `projectConfig.http.liveEndpoint` (config) — path served by `handleLivezRoute`. Default `/livez`.
72
- - `projectConfig.http.readyEndpoint` (config) — path served by `handleReadyzRoute`. Default `/readyz`.
73
- - `projectConfig.http.testResetEndpoint` (config) — path served by `handleTestResetRoute`. Default `/_test/reset`.
74
- - `projectConfig.logging.socketStartup` / `projectConfig.logging.devLogs` (config) — gate the boot log line emitted by `listen()`.
75
- - Positional argv `<bundles> [port]` — preset list (merged in `createProdRuntimeMapsProvider`) and listen port.
76
-
77
- ## Peer dependencies
78
-
79
- - **Required (runtime deps)**: `@luckystack/api`, `@luckystack/core`.
80
- - **Peer (canonical ranges)**: `@prisma/client@^6.19.0` (transitive via core), `socket.io@^4.8.0`.
81
- - **Optional (all auto-detected by `bootstrapLuckyStack`; the server degrades gracefully when absent — `auth.disabled` / `sync.disabled`, presence skipped)**: `@luckystack/login`, `@luckystack/presence`, `@luckystack/sync`, `@luckystack/error-tracking`, `@luckystack/email`, `@luckystack/cron`, `@luckystack/docs-ui`, `@luckystack/devkit` (dev-only, dynamically imported by the `enableDevTools` branch).
82
- - **0.2.0 install-anything-anytime**: `bootstrapLuckyStack` runs an auto-detect phase BEFORE the consumer overlay that imports each installed optional package's `@luckystack/<pkg>/register` side-effect subpath (`OPTIONAL_PACKAGES` in `capabilities.ts` = `login, email, error-tracking, presence, cron, docs-ui`). So `npm i @luckystack/<pkg>` + env + restart self-wires the BACKEND with zero code edits. Detection uses `import.meta.resolve` (the `@luckystack/*` exports maps are import-only — a CJS `require.resolve` reports them absent). Client-side mounts (presence JSX, login pages) need `npx luckystack add <feature>` (`@luckystack/cli`) because Vite can't statically import an uninstalled package and file-routing only scans `src/`.
83
-
84
- ## Related
85
-
86
- - Architecture deep-dives: `/docs/ARCHITECTURE_API.md`, `/docs/ARCHITECTURE_SOCKET.md`, `/docs/ARCHITECTURE_PACKAGING.md`, `/docs/HOSTING.md`.
87
- - README (consumer quickstart): `./README.md`.
1
+ # @luckystack/server
2
+
3
+ > AI summary + function INDEX. For deep specs see `docs/` next to this file.
4
+
5
+ ## What this package does
6
+
7
+ One-call server bootstrap for a LuckyStack project. Wires together a raw Node.js HTTP server, Socket.io (with optional Redis adapter), framework routes (`/api/*`, `/sync/*`, `/_health`, `/livez`, `/readyz`, `/_test/reset`, `/auth/*`, `/uploads/*`, `/csrf-token`), CSRF middleware, CORS + security headers, presence broadcasting, and dev-only hot reload. Consumer's `server.ts` shrinks to roughly twenty lines. Boots are gated by `verifyBootstrap` so missing registrations surface a single readable error instead of mid-request crashes.
8
+
9
+ ## When to USE this package
10
+
11
+ - Project needs the full LuckyStack runtime (HTTP + sockets + framework routes wired in one call).
12
+ - Multi-bundle or multi-service deploys that need positional argv parsing (`npm run server -- <bundles> <port>`) and merged runtime maps across presets.
13
+ - Consumers that want framework-shipped health / liveness / readiness probes, CSRF protection, and dev hot reload without building plumbing themselves.
14
+ - Adding project-specific HTTP endpoints via `registerCustomRoute(...)` or the `customRoutes` option without forking the package.
15
+ - Plugins / framework packages that ship socket-lifecycle behavior via the exported hook payload types (e.g. `@luckystack/presence`).
16
+
17
+ ## When to NOT suggest this (yet)
18
+
19
+ - Standalone microservices that do not use the LuckyStack registries (project config, deploy config, runtime maps, localized normalizer) — there is nothing for `verifyBootstrap` to verify, and the wiring overhead buys nothing.
20
+ - A pure router / load-balancer process — use `@luckystack/router` instead; `@luckystack/server` is for the actual service nodes behind the router.
21
+ - Build-time tooling, code generation, or migration scripts that never accept HTTP / socket traffic.
22
+ - Replacing the HTTP layer with Express / Fastify — the package owns the raw Node HTTP server and dispatch table; bringing a second framework on top breaks the route handler contract.
23
+
24
+ ## Function Index
25
+
26
+ | Function / Export | One-liner | Deep doc |
27
+ |---|---|---|
28
+ | `createLuckyStackServer(options)` | Lower-level factory that returns `{ httpServer, ioServer, listen, stop, close }`. Wires HTTP + Socket.io + framework routes; installs a persistent `httpServer.on('error')` listener and (in prod) SIGTERM/SIGINT → graceful `stop()`. | -> docs/create-server.md |
29
+ | `RunningLuckyStackServer.stop(options?)` / `.close(options?)` | Graceful shutdown (MIS-016). Stops accepting new connections, dispatches the core `preServerStop` hook, flushes error-trackers (bounded by a per-step timeout race), then closes io + http + the Redis-adapter pub/sub clients. Each step isolated + time-bounded so one hanging step can't stall shutdown. Idempotent. `options`: `{ reason?, timeoutMs? }` (default `timeoutMs` 10000). | -> docs/create-server.md |
30
+ | `bootstrapLuckyStack(options)` | High-level entry: auto-imports `luckystack/<pkg>/*.ts` overlay in topological order, then delegates to `createLuckyStackServer`. An overlay file that fails to import (classic: it imports a package removed via `luckystack remove`) aborts boot with an ACTIONABLE error naming the file, instead of a raw ERR_MODULE_NOT_FOUND. | -> docs/create-server.md |
31
+ | `OVERLAY_ORDER` (exported const) | The canonical overlay walk order (`core, deploy, login, email, sentry, presence, cron, docs-ui, server`). Exported so the consumer's `scripts/bundleServer.mjs` imports it at build time — a hardcoded bundler copy once silently dropped the cron slot from prod; `overlayOrderParity.test.ts` pins the bundlers' fallback lists to this. | -> docs/create-server.md |
32
+ | `registerOverlayLoader(loader)` | Production-bundle seam: the server bundler (`scripts/bundleServer.mjs`) generates an entry that statically imports the overlay files and registers this loader; `bootstrapLuckyStack` then runs it INSTEAD of the runtime `.ts` folder walk (plain `node` cannot import `.ts` — ERR_UNKNOWN_FILE_EXTENSION). | -> docs/create-server.md |
33
+ | `verifyBootstrap(requirements?)` | Pre-flight check for ProjectConfig / DeployConfig / ServicesConfig / OAuth / RuntimeMapsProvider / LocalizedNormalizer. Throws one descriptive `Error`. | -> docs/create-server.md |
34
+ | `parseServerArgv(argv)` | Pure parser: validates positional `<bundles> [port]` and returns `{ bundles, port }`. Throws on malformed input. | -> docs/argv-parsing.md |
35
+ | `applyServerArgv()` | Side-effect runner: parses `process.argv.slice(2)`, stores bundles + port, writes `process.env.SERVER_PORT` for downstream env readers. Idempotent. | -> docs/argv-parsing.md |
36
+ | `getParsedBundles()` | Returns the preset list parsed by `applyServerArgv()` (empty array before first call). | -> docs/argv-parsing.md |
37
+ | `getParsedPort()` | Returns the port parsed by `applyServerArgv()` (`null` if argv omitted it). | -> docs/argv-parsing.md |
38
+ | `@luckystack/server/parseArgv` (side-effect import) | First-line import that runs `applyServerArgv()` before any module reads `process.env.SERVER_PORT` (notably `config.ts`). | -> docs/argv-parsing.md |
39
+ | `createProdRuntimeMapsProvider(options)` | Build a `RuntimeMapsProvider` that loads generated maps in prod and delegates to devkit discovery in dev. Returns the provider without registering. | -> docs/runtime-maps.md |
40
+ | `registerProdRuntimeMapsProvider(options)` | Convenience wrapper: builds the provider AND calls `registerRuntimeMapsProvider`. Most consumers want this. | -> docs/runtime-maps.md |
41
+ | `registerCustomRoute(handler, options?)` | Append a custom HTTP route handler. `options.phase`: `'post-params'` (default, runs after body parse) or `'pre-params'` (runs before `getParams`, raw `req` stream intact — webhooks + streaming uploads). | -> /docs/ARCHITECTURE_HTTP.md |
42
+ | `getCustomRoutes()` | Read the `'post-params'` registry snapshot (the default phase). | -> /docs/ARCHITECTURE_HTTP.md |
43
+ | `getPreParamsCustomRoutes()` | Read the `'pre-params'` registry snapshot. | -> /docs/ARCHITECTURE_HTTP.md |
44
+ | `clearCustomRoutes()` | Clear both phases (used by test resets). | -> /docs/ARCHITECTURE_HTTP.md |
45
+ | `registerOriginExemptPath({ pathPrefix })` | Exempt a path prefix from the browser origin gate so a server-to-server webhook (no `Origin`/`Referer`) reaches its handler. Empty/fail-closed by default. **Exemption ≠ auth** — the handler MUST verify a signature/secret. | -> /docs/ARCHITECTURE_HTTP.md |
46
+ | `getOriginExemptPaths()` / `clearOriginExemptPaths()` / `isOriginExemptPath(routePath)` | Read / clear / test the exempt-path registry. | -> /docs/ARCHITECTURE_HTTP.md |
47
+ | `registerSecurityHeaders(builder)` | Override / extend the security-headers builder applied to every HTTP response. | -> docs/security-defaults.md |
48
+ | `getSecurityHeadersBuilder()` | Read the currently registered builder (defaults to framework headers when no override set). | -> docs/security-defaults.md |
49
+ | `registerErrorFormatter(formatter)` | Override the JSON error shape returned by framework error responses. | -> docs/security-defaults.md |
50
+ | `getErrorFormatter()` | Read the currently registered formatter. | -> docs/security-defaults.md |
51
+ | Route handler: `handleLivezRoute` | Liveness probe at `projectConfig.http.liveEndpoint`. Always 200 when reachable. | -> docs/http-routes.md |
52
+ | Route handler: `handleReadyzRoute` | Readiness probe: boot UUID + Redis ping + database check (ADR 0020: a registered `registerDbHealthCheck` probe wins; else the built-in Prisma ping when Prisma is registered/resolvable; else `'skipped'` so a deliberately DB-less project can go ready). Response `checks.database` = tri-state, `checks.prisma` kept for compat. | -> docs/http-routes.md |
53
+ | Route handler: `handleHealthRoute` | Health endpoint: returns boot UUID + env hashes for router topology checks. | -> docs/http-routes.md |
54
+ | Route handler: `handleTestResetRoute` | Destructive test reset. Fail-closed on `NODE_ENV` and `TEST_RESET_TOKEN`. | -> docs/security-defaults.md |
55
+ | Hook payload: `OnSocketConnectPayload` | Payload type for `onSocketConnect` lifecycle hook handlers. | -> docs/create-server.md |
56
+ | Hook payload: `OnSocketDisconnectPayload` | Payload type for `onSocketDisconnect` lifecycle hook handlers. | -> docs/create-server.md |
57
+ | Hook payload: `PreRoomJoinPayload` | Payload type for `preRoomJoin` hook handlers. | -> docs/create-server.md |
58
+ | Hook payload: `PostRoomJoinPayload` | Payload type for `postRoomJoin` hook handlers. | -> docs/create-server.md |
59
+ | Hook payload: `PreRoomLeavePayload` | Payload type for `preRoomLeave` hook handlers. | -> docs/create-server.md |
60
+ | Hook payload: `PostRoomLeavePayload` | Payload type for `postRoomLeave` hook handlers. | -> docs/create-server.md |
61
+ | Hook payload: `OnLocationUpdatePayload` | Payload type for `onLocationUpdate` hook handlers. | -> docs/create-server.md |
62
+ | Types: `CreateLuckyStackServerOptions`, `BootstrapLuckyStackOptions`, `BootstrapRequirements`, `RunningLuckyStackServer`, `StopLuckyStackServerOptions`, `RouteContext`, `StaticFileHandler`, `FaviconHandler`, `CustomRouteHandler`, `ProdRuntimeMapsLoaderOptions`, `ParsedServerArgv`, `SecurityHeadersBuilder`, `ErrorFormatter`, `ErrorFormatterContext` | Handler + option typing. | -> docs/create-server.md |
63
+
64
+ ## Config keys (env vars + registerProjectConfig slots)
65
+
66
+ - `SERVER_PORT` (env, optional) — fallback when neither `options.port` nor positional argv supplies one. Written back by `applyServerArgv()` when argv carries a port. In dev, the ACTUALLY-bound port (after any `SERVER_PORT_AUTO_INCREMENT` hop off a busy port) is advertised to `node_modules/.luckystack/dev-server.json` so the template Vite proxy follows the real port instead of the stale `.env` one (written by `devServerInfo.ts`; skipped in prod + tests, removed on exit).
67
+ - `SERVER_IP` (env, optional, default `127.0.0.1`) — bind address fallback when `options.ip` is omitted.
68
+ - `NODE_ENV` (env, required for security-sensitive branches) — `development` / `test` toggle devkit hot reload + REPL and gate `/_test/reset`.
69
+ - `TEST_RESET_TOKEN` (env, required for `/_test/reset` to be reachable at all) — must match the `x-test-reset-token` request header. No fallback "no auth" mode.
70
+ - `projectConfig.http.healthEndpoint` (config) — path served by `handleHealthRoute`. Default `/_health`.
71
+ - `projectConfig.http.liveEndpoint` (config) — path served by `handleLivezRoute`. Default `/livez`.
72
+ - `projectConfig.http.readyEndpoint` (config) — path served by `handleReadyzRoute`. Default `/readyz`.
73
+ - `projectConfig.http.testResetEndpoint` (config) — path served by `handleTestResetRoute`. Default `/_test/reset`.
74
+ - `projectConfig.logging.socketStartup` / `projectConfig.logging.devLogs` (config) — gate the boot log line emitted by `listen()`.
75
+ - Positional argv `<bundles> [port]` — preset list (merged in `createProdRuntimeMapsProvider`) and listen port.
76
+
77
+ ## Peer dependencies
78
+
79
+ - **Required (runtime deps)**: `@luckystack/api`, `@luckystack/core`.
80
+ - **Peer (canonical ranges)**: `@prisma/client@^6.19.0` (transitive via core), `socket.io@^4.8.0`.
81
+ - **Optional (all auto-detected by `bootstrapLuckyStack`; the server degrades gracefully when absent — `auth.disabled` / `sync.disabled`, presence skipped)**: `@luckystack/login`, `@luckystack/presence`, `@luckystack/sync`, `@luckystack/error-tracking`, `@luckystack/email`, `@luckystack/cron`, `@luckystack/docs-ui`, `@luckystack/devkit` (dev-only, dynamically imported by the `enableDevTools` branch).
82
+ - **0.2.0 install-anything-anytime**: `bootstrapLuckyStack` runs an auto-detect phase BEFORE the consumer overlay that imports each installed optional package's `@luckystack/<pkg>/register` side-effect subpath (`OPTIONAL_PACKAGES` in `capabilities.ts` = `login, email, error-tracking, presence, cron, docs-ui`). So `npm i @luckystack/<pkg>` + env + restart self-wires the BACKEND with zero code edits. Detection uses `import.meta.resolve` (the `@luckystack/*` exports maps are import-only — a CJS `require.resolve` reports them absent). Client-side mounts (presence JSX, login pages) need `npx luckystack add <feature>` (`@luckystack/cli`) because Vite can't statically import an uninstalled package and file-routing only scans `src/`.
83
+
84
+ ## Related
85
+
86
+ - Architecture deep-dives: `/docs/ARCHITECTURE_API.md`, `/docs/ARCHITECTURE_SOCKET.md`, `/docs/ARCHITECTURE_PACKAGING.md`, `/docs/HOSTING.md`.
87
+ - README (consumer quickstart): `./README.md`.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Mathijs van Melick
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mathijs van Melick
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.