@luckystack/server 0.7.2 → 0.7.4
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 +37 -0
- package/CLAUDE.md +3 -3
- package/dist/{chunk-X3OSC5W3.js → chunk-QLVQ75QL.js} +29 -2
- package/dist/chunk-QLVQ75QL.js.map +1 -0
- package/dist/index.js +69 -27
- package/dist/index.js.map +1 -1
- package/dist/parseArgv.js +1 -1
- package/docs/create-server.md +9 -8
- package/docs/http-routes.md +3 -2
- package/package.json +10 -10
- package/dist/chunk-X3OSC5W3.js.map +0 -1
package/dist/parseArgv.js
CHANGED
package/docs/create-server.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Create Server (`createLuckyStackServer` + `bootstrapLuckyStack`)
|
|
2
2
|
|
|
3
|
-
> Deep specs. Bron: `packages/server/src/createServer.ts`, `packages/server/src/bootstrap.ts`, `packages/server/src/verifyBootstrap.ts`, `packages/server/src/hookPayloads.ts`, `packages/server/src/types.ts`. Bijgewerkt: 2026-
|
|
3
|
+
> Deep specs. Bron: `packages/server/src/createServer.ts`, `packages/server/src/bootstrap.ts`, `packages/server/src/verifyBootstrap.ts`, `packages/server/src/hookPayloads.ts`, `packages/server/src/types.ts`. Bijgewerkt: 2026-07-20.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -16,8 +16,8 @@ Boot order (effective for both entries):
|
|
|
16
16
|
1. `bootstrapLuckyStack` only: load overlay files (`core` -> `deploy` -> `login` -> `sentry` -> `presence` -> `docs-ui` -> `server`, each folder topologically followed by alphabetical `*.ts`).
|
|
17
17
|
2. If `options.loadGeneratedMaps` was supplied, register the framework-shipped runtime-maps provider before `verifyBootstrap` so the boot check sees it.
|
|
18
18
|
3. `verifyBootstrap` runs (using the per-call `requireDeployConfig` / `requireServicesConfig` / `requireOAuthProviders` flags). Throws a single descriptive `Error` if anything is missing.
|
|
19
|
-
4. Resolve `port` (`options.port` -> argv-parsed -> `SERVER_PORT` -> `80`) and `ip` (`options.ip` -> `SERVER_IP` -> `127.0.0.1`);
|
|
20
|
-
5. In dev mode (`enableDevTools !== false` and `NODE_ENV !== 'production'`): `initConsolelog()` + dynamic-import `@luckystack/devkit` -> `initializeAll()` + `setupWatchers()`. Install `SIGINT` / `SIGTERM` handlers that force-exit.
|
|
19
|
+
4. Resolve `port` (`options.port` -> argv-parsed -> `options.defaultPort` -> `SERVER_PORT` -> `80`) and `ip` (`options.ip` -> `SERVER_IP` -> `127.0.0.1`); validate the port in `0..65535` and register the intended address. After listen succeeds, register `httpServer.address().port` as the actually-bound address.
|
|
20
|
+
5. In dev mode (`enableDevTools !== false` and `NODE_ENV !== 'production'`): `initConsolelog()` + dynamic-import `@luckystack/devkit` -> `initializeAll()` + `setupWatchers()`. Initialization status is shared with API/sync dispatchers and `/readyz`; a fatal devkit error keeps all three fail-closed with 503. Install `SIGINT` / `SIGTERM` handlers that force-exit.
|
|
21
21
|
6. `writeBootUuid()` writes a fresh boot UUID to Redis so `/_health` becomes truthful and the router can detect rolling restarts.
|
|
22
22
|
7. Construct `http.createServer(handleHttpRequest)` and `loadSocket(httpServer, { maxHttpBufferSize })`.
|
|
23
23
|
8. Return `{ httpServer, ioServer, listen }`. The HTTP server has NOT started listening yet; the caller invokes `listen()` to bind.
|
|
@@ -38,8 +38,9 @@ export const createLuckyStackServer = async (
|
|
|
38
38
|
|
|
39
39
|
| Field | Type | Default | Purpose |
|
|
40
40
|
| --- | --- | --- | --- |
|
|
41
|
-
| `port` | `number \| string` | `getParsedPort()` -> `SERVER_PORT` -> `80` | HTTP listen port.
|
|
42
|
-
| `
|
|
41
|
+
| `port` | `number \| string` | `getParsedPort()` -> `defaultPort` -> `SERVER_PORT` -> `80` | Explicit HTTP listen port. Numeric strings are accepted; partial/non-integer/out-of-range values fail before `listen`. |
|
|
42
|
+
| `defaultPort` | `number` | `SERVER_PORT` -> `80` | Config-level fallback below explicit `port` + argv; the scaffold passes `config.ports.backend`. |
|
|
43
|
+
| `ip` | `string` | `process.env.SERVER_IP ?? '127.0.0.1'` | Bind address. Registered so `checkOrigin` and other framework code see the resolved value. |
|
|
43
44
|
| `serveFile` | `StaticFileHandler` | none | Catch-all GET handler (Vite output, SPA `index.html`). Called for `/assets/*`, known static extensions, and the final SPA fallback. Without it the static fallback returns `404`. |
|
|
44
45
|
| `serveFavicon` | `FaviconHandler` | none | Handler for `/favicon.ico`. Without it the route returns `404`. |
|
|
45
46
|
| `customRoutes` | `CustomRouteHandler` | none | Inline custom-route hook. Composed after the registry handlers from `registerCustomRoute(...)`; first one to return `true` wins. |
|
|
@@ -61,13 +62,13 @@ interface RunningLuckyStackServer {
|
|
|
61
62
|
}
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
`listen()` resolves when the HTTP server is bound.
|
|
65
|
+
`listen()` resolves when the HTTP server is bound. The successful `httpServer.address().port` is registered, advertised to dev tooling, and logged, so `port: 0` reports the OS-assigned port. A busy port auto-increments by default in development (disable with `SERVER_PORT_AUTO_INCREMENT=0`) and fails by default in production. It never retries beyond `65535`. It is safe to call once; calling twice yields a Node `ERR_SERVER_ALREADY_LISTEN` from the underlying `httpServer.listen`.
|
|
65
66
|
|
|
66
67
|
**Behavior (execution order):**
|
|
67
68
|
|
|
68
69
|
- Auto-register prod runtime-maps provider if `loadGeneratedMaps` is set.
|
|
69
70
|
- Run `verifyBootstrap` (may throw).
|
|
70
|
-
- Resolve port
|
|
71
|
+
- Resolve + validate port precedence and ip; call `registerBindAddress({ ip, port })` for the intended address.
|
|
71
72
|
- If dev-tools enabled: load console initializer + devkit (`initializeAll`, `setupWatchers`); attach SIGINT / SIGTERM force-exit handlers.
|
|
72
73
|
- `await writeBootUuid()`.
|
|
73
74
|
- Create `http.Server` whose request handler is `handleHttpRequest(req, res, options)` (see `request-pipeline.md`).
|
|
@@ -275,7 +276,7 @@ export type CustomRouteHandler = (
|
|
|
275
276
|
|
|
276
277
|
| Source | Key | Effect |
|
|
277
278
|
| --- | --- | --- |
|
|
278
|
-
| env | `SERVER_PORT` |
|
|
279
|
+
| env | `SERVER_PORT` | Legacy port fallback below `options.port`, argv, and `options.defaultPort`. |
|
|
279
280
|
| env | `SERVER_IP` | IP fallback. |
|
|
280
281
|
| env | `NODE_ENV` | Switches `enableDevTools` default, dynamic-import branch for devkit, and `verifyBootstrap` warn-vs-throw for runtime-maps / localized normalizer. |
|
|
281
282
|
| env | `SECURE` | When `'true'`, session cookies are emitted with the `Secure;` flag (see `request-pipeline.md`). |
|
package/docs/http-routes.md
CHANGED
|
@@ -91,14 +91,15 @@ A handler returns `true` (or simply ends `res`) to short-circuit dispatch; retur
|
|
|
91
91
|
**Behavior:**
|
|
92
92
|
|
|
93
93
|
- Matches when `routePath === projectConfig.http.readyEndpoint` (default `/readyz`).
|
|
94
|
-
-
|
|
94
|
+
- Reads the shared `DevToolsStatus`, `readBootUuid()` (Redis), `redis.ping()`, and a database check (ADR 0020):
|
|
95
95
|
1. a consumer-registered `registerDbHealthCheck(...)` probe (from `@luckystack/core`) wins;
|
|
96
96
|
2. otherwise the built-in Prisma probe runs when Prisma is part of the install (`isPrismaClientRegistered()` or a resolvable `@prisma/client`) — provider-agnostic: tries `$queryRaw\`SELECT 1\`` first, then `$runCommandRaw({ ping: 1 })` (detecting the active provider via private fields drifts between Prisma majors, so it probes by capability);
|
|
97
97
|
3. otherwise the check reports `'skipped'` — a deliberately DB-less project (`orm: 'none'`) can still go ready.
|
|
98
|
-
- Returns `200 { status: 'ready', checks }` when boot UUID + Redis pass and the database check is not `false`; `503 { status: 'not-ready', ... }` otherwise. `checks.database` carries the tri-state (`true | false | 'skipped'`); `checks.prisma` is kept for backward compatibility (`true` when the database check passed or was skipped).
|
|
98
|
+
- Returns `200 { status: 'ready', checks }` when devkit did not fail, boot UUID + Redis pass, and the database check is not `false`; `503 { status: 'not-ready', ... }` otherwise. `checks.devTools` is `true` before/after a successful initialization and `false` after a recorded fatal failure. `checks.database` carries the tri-state (`true | false | 'skipped'`); `checks.prisma` is kept for backward compatibility (`true` when the database check passed or was skipped).
|
|
99
99
|
|
|
100
100
|
**Edge cases:**
|
|
101
101
|
|
|
102
|
+
- A recorded devkit error forces 503 even when Redis/database probes pass, so an incomplete runtime cannot advertise readiness just because infrastructure is green.
|
|
102
103
|
- `redis.ping()` errors are swallowed and surface as `redis: false`.
|
|
103
104
|
- A Prisma client with neither raw method available results in `prismaOk = false`.
|
|
104
105
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luckystack/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -57,19 +57,19 @@
|
|
|
57
57
|
"test": "vitest run"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@luckystack/api": "^0.7.
|
|
61
|
-
"@luckystack/core": "^0.7.
|
|
60
|
+
"@luckystack/api": "^0.7.4",
|
|
61
|
+
"@luckystack/core": "^0.7.4"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"@prisma/client": "^6.19.0",
|
|
65
65
|
"socket.io": "^4.8.0",
|
|
66
|
-
"@luckystack/devkit": "^0.7.
|
|
67
|
-
"@luckystack/docs-ui": "^0.7.
|
|
68
|
-
"@luckystack/email": "^0.7.
|
|
69
|
-
"@luckystack/error-tracking": "^0.7.
|
|
70
|
-
"@luckystack/login": "^0.7.
|
|
71
|
-
"@luckystack/presence": "^0.7.
|
|
72
|
-
"@luckystack/sync": "^0.7.
|
|
66
|
+
"@luckystack/devkit": "^0.7.4",
|
|
67
|
+
"@luckystack/docs-ui": "^0.7.4",
|
|
68
|
+
"@luckystack/email": "^0.7.4",
|
|
69
|
+
"@luckystack/error-tracking": "^0.7.4",
|
|
70
|
+
"@luckystack/login": "^0.7.4",
|
|
71
|
+
"@luckystack/presence": "^0.7.4",
|
|
72
|
+
"@luckystack/sync": "^0.7.4"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|
|
75
75
|
"@prisma/client": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/argv.ts"],"sourcesContent":["//? Positional argv parser for the LuckyStack server boot. Replaces\n//? `LUCKYSTACK_BUNDLE` + `SERVER_PORT` env-var reads with a single shape:\n//?\n//? npm run server -- billing,vehicles 4001\n//?\n//? Arg 0 = comma-separated preset list (runtime maps merged across them).\n//? Arg 1 = listen port (numeric).\n//?\n//? `applyServerArgv()` is called by the side-effect module\n//? `@luckystack/server/parseArgv`, which consumers import as the FIRST line\n//? of their `server.ts` so the parsed port lands in `process.env.SERVER_PORT`\n//? before `config.ts` (top-level `backendUrl` const) is evaluated.\n\nexport interface ParsedServerArgv {\n bundles: string[];\n port: number | null;\n}\n\nlet parsedBundles: string[] = [];\nlet parsedPort: number | null = null;\nlet hasRun = false;\n\nconst PORT_PATTERN = /^\\d+$/;\n\nexport const parseServerArgv = (argv: string[]): ParsedServerArgv => {\n if (argv.length > 2) {\n throw new Error(\n `[luckystack:argv] unexpected positional argument(s): \"${argv.slice(2).join(' ')}\". ` +\n `Usage: npm run server -- <bundle[,bundle...]> [port]`,\n );\n }\n\n const bundles = argv[0] && argv[0].length > 0\n ? [...new Set(argv[0].split(',').map((s) => s.trim()).filter(Boolean))]\n : [];\n\n let port: number | null = null;\n const portArg = argv[1];\n if (portArg !== undefined) {\n if (!PORT_PATTERN.test(portArg)) {\n throw new Error(\n `[luckystack:argv] port argument must be numeric, got: \"${portArg}\". ` +\n `Usage: npm run server -- <bundle[,bundle...]> [port]`,\n );\n }\n port = Number.parseInt(portArg, 10);\n }\n\n return { bundles, port };\n};\n\nexport const applyServerArgv = (): void => {\n if (hasRun) return;\n hasRun = true;\n\n const result = parseServerArgv(process.argv.slice(2));\n parsedBundles = result.bundles;\n parsedPort = result.port;\n\n //? Writeback so the downstream env-readers (`core/env.ts` Zod schema,\n //? `core/bindAddress.ts` fallback, consumer `config.ts` backendUrl,\n //? `oauthProviders.ts` callback URL) see the resolved port without us\n //? having to refactor those four call sites. This is a deliberate\n //? implementation detail — argv is the public source of truth.\n if (parsedPort !== null) {\n process.env.SERVER_PORT = String(parsedPort);\n }\n};\n\nexport const getParsedBundles = (): string[] => parsedBundles;\nexport const getParsedPort = (): number | null => parsedPort;\n"],"mappings":";AAkBA,IAAI,gBAA0B,CAAC;AAC/B,IAAI,aAA4B;AAChC,IAAI,SAAS;AAEb,IAAM,eAAe;AAEd,IAAM,kBAAkB,CAAC,SAAqC;AACnE,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM,IAAI;AAAA,MACR,yDAAyD,KAAK,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,IAElF;AAAA,EACF;AAEA,QAAM,UAAU,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,SAAS,IACxC,CAAC,GAAG,IAAI,IAAI,KAAK,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC,IACpE,CAAC;AAEL,MAAI,OAAsB;AAC1B,QAAM,UAAU,KAAK,CAAC;AACtB,MAAI,YAAY,QAAW;AACzB,QAAI,CAAC,aAAa,KAAK,OAAO,GAAG;AAC/B,YAAM,IAAI;AAAA,QACR,0DAA0D,OAAO;AAAA,MAEnE;AAAA,IACF;AACA,WAAO,OAAO,SAAS,SAAS,EAAE;AAAA,EACpC;AAEA,SAAO,EAAE,SAAS,KAAK;AACzB;AAEO,IAAM,kBAAkB,MAAY;AACzC,MAAI,OAAQ;AACZ,WAAS;AAET,QAAM,SAAS,gBAAgB,QAAQ,KAAK,MAAM,CAAC,CAAC;AACpD,kBAAgB,OAAO;AACvB,eAAa,OAAO;AAOpB,MAAI,eAAe,MAAM;AACvB,YAAQ,IAAI,cAAc,OAAO,UAAU;AAAA,EAC7C;AACF;AAEO,IAAM,mBAAmB,MAAgB;AACzC,IAAM,gBAAgB,MAAqB;","names":[]}
|