@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 +102 -87
- package/CLAUDE.md +87 -87
- package/LICENSE +21 -21
- package/README.md +198 -198
- package/dist/{chunk-RIVFTOTI.js → chunk-X3OSC5W3.js} +1 -1
- package/dist/chunk-X3OSC5W3.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/parseArgv.js +1 -1
- package/dist/parseArgv.js.map +1 -1
- package/docs/argv-parsing.md +256 -256
- package/docs/create-server.md +295 -295
- package/docs/http-routes.md +318 -318
- package/docs/request-pipeline.md +198 -198
- package/docs/runtime-maps.md +206 -206
- package/docs/security-defaults.md +236 -236
- package/package.json +10 -10
- package/dist/chunk-RIVFTOTI.js.map +0 -1
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- `
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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.
|