@luckystack/server 0.8.2 → 0.8.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 +20 -0
- package/CLAUDE.md +6 -6
- package/README.md +20 -15
- package/dist/{chunk-QLVQ75QL.js → chunk-HJXOI6LY.js} +9 -12
- package/dist/chunk-HJXOI6LY.js.map +1 -0
- package/dist/index.d.ts +4 -6
- package/dist/index.js +5 -6
- 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 +16 -26
- package/docs/create-server.md +6 -6
- package/docs/http-routes.md +7 -5
- package/docs/request-pipeline.md +1 -1
- package/docs/runtime-maps.md +1 -1
- package/docs/security-defaults.md +20 -14
- package/package.json +14 -10
- package/dist/chunk-QLVQ75QL.js.map +0 -1
package/dist/parseArgv.js
CHANGED
package/dist/parseArgv.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/parseArgv.ts"],"sourcesContent":["//? Side-effect-only entrypoint. Import this as the FIRST line of your\n//? `server.ts` so the positional CLI args (`<bundles> <port>`) are
|
|
1
|
+
{"version":3,"sources":["../src/parseArgv.ts"],"sourcesContent":["//? Side-effect-only entrypoint. Import this as the FIRST line of your\n//? `server.ts` so the positional CLI args (`<bundles> <port>`) are registered\n//? before any other module evaluates the consumer config (notably its top-level\n//? OAuth callback base).\n//?\n//? Usage:\n//? import '@luckystack/server/parseArgv';\n\nimport { applyServerArgv } from './argv';\n\napplyServerArgv();\n"],"mappings":";;;;;AAUA,gBAAgB;","names":[]}
|
package/docs/argv-parsing.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Argv Parsing (`parseServerArgv` + `applyServerArgv`)
|
|
2
2
|
|
|
3
|
-
> Deep specs. Bron: `packages/server/src/argv.ts`, `packages/server/src/parseArgv.ts`. Bijgewerkt: 2026-
|
|
3
|
+
> Deep specs. Bron: `packages/server/src/argv.ts`, `packages/server/src/parseArgv.ts`. Bijgewerkt: 2026-08-16.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -13,23 +13,18 @@ npm run server -- <bundle[,bundle...]> [port]
|
|
|
13
13
|
- Arg 0 — preset list. Comma-separated; duplicates collapsed; runtime maps from each preset are shallow-merged at boot.
|
|
14
14
|
- Arg 1 — listen port. Numeric. Optional.
|
|
15
15
|
|
|
16
|
-
Argv
|
|
16
|
+
Argv provides one shape consumed by `createProdRuntimeMapsProvider` (preset), `createLuckyStackServer` (port), and the browser-safe `@luckystack/core/config` port-override registry.
|
|
17
17
|
|
|
18
18
|
The module exposes:
|
|
19
19
|
|
|
20
20
|
- A pure parser: `parseServerArgv(argv)`.
|
|
21
|
-
- A side-effect runner that reads `process.argv.slice(2)` once and
|
|
21
|
+
- A side-effect runner that reads `process.argv.slice(2)` once and registers the result: `applyServerArgv()`.
|
|
22
22
|
- Read accessors: `getParsedBundles()`, `getParsedPort()`.
|
|
23
23
|
- A side-effect-only entrypoint `@luckystack/server/parseArgv` that simply imports `applyServerArgv` and runs it.
|
|
24
24
|
|
|
25
|
-
The side-effect entry MUST be the FIRST import in the consumer's `server.ts` because the parsed port
|
|
25
|
+
The side-effect entry MUST be the FIRST import in the consumer's `server.ts` because it registers the parsed port in `@luckystack/core/config`, and the consumer's config reads that override at top-level evaluation time. When no CLI port was supplied, the registry stays empty and `config.ts` uses the consumer-owned `config.ports.ts` backend value.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
- `@luckystack/core` `bindAddress.ts` fallback
|
|
29
|
-
- The consumer's `config.ts` `backendUrl` constant
|
|
30
|
-
- `@luckystack/login` `oauthProviders.ts` callback URL builder
|
|
31
|
-
|
|
32
|
-
Importing anything that pulls one of those four before `parseArgv` runs will lock in the wrong port.
|
|
27
|
+
Importing the consumer config before `parseArgv` runs can lock the static default into its OAuth callback base. The server listen path still reads the same registry through `getParsedPort()`, so both surfaces share one override without an environment writeback.
|
|
33
28
|
|
|
34
29
|
## API Reference
|
|
35
30
|
|
|
@@ -105,13 +100,13 @@ export const applyServerArgv = (): void;
|
|
|
105
100
|
- Idempotent. Subsequent calls return immediately via the module-level `hasRun` latch.
|
|
106
101
|
- First call:
|
|
107
102
|
1. `parseServerArgv(process.argv.slice(2))` (throws on malformed input).
|
|
108
|
-
2. Caches `bundles`
|
|
109
|
-
3.
|
|
103
|
+
2. Caches `bundles` in module state.
|
|
104
|
+
3. Calls `registerPortOverride(port)`. A numeric port becomes visible through the browser-safe core/config entry; `null` clears the override.
|
|
110
105
|
|
|
111
106
|
**Errors / Edge cases:**
|
|
112
107
|
|
|
113
|
-
- Throwing during this call aborts boot before
|
|
114
|
-
- Calling `applyServerArgv` after
|
|
108
|
+
- Throwing during this call aborts boot before consumer config evaluates.
|
|
109
|
+
- Calling `applyServerArgv` after consumer config has evaluated is too late — that module has already captured its callback base.
|
|
115
110
|
|
|
116
111
|
**Example:**
|
|
117
112
|
|
|
@@ -164,7 +159,7 @@ console.log(getParsedBundles()); // e.g. ['billing', 'vehicles']
|
|
|
164
159
|
export const getParsedPort = (): number | null;
|
|
165
160
|
```
|
|
166
161
|
|
|
167
|
-
**Returns:** the
|
|
162
|
+
**Returns:** the port currently held in the core/config override registry. `null` when no positional port was registered.
|
|
168
163
|
|
|
169
164
|
**Behavior:**
|
|
170
165
|
|
|
@@ -172,8 +167,8 @@ export const getParsedPort = (): number | null;
|
|
|
172
167
|
- Consumed by `createLuckyStackServer` as one of the port-resolution fallbacks:
|
|
173
168
|
1. `options.port`
|
|
174
169
|
2. `getParsedPort()`
|
|
175
|
-
3. `
|
|
176
|
-
4. `80`
|
|
170
|
+
3. `options.defaultPort` (the scaffold's `config.ports.ts` backend)
|
|
171
|
+
4. `80` (generic final fallback)
|
|
177
172
|
|
|
178
173
|
**Example:**
|
|
179
174
|
|
|
@@ -204,28 +199,23 @@ applyServerArgv();
|
|
|
204
199
|
import '@luckystack/server/parseArgv';
|
|
205
200
|
```
|
|
206
201
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
- Importing `'./config'` (which evaluates `backendUrl` at top level) before `parseArgv`.
|
|
210
|
-
- Importing `@luckystack/core` modules that pull the env Zod schema before `parseArgv`.
|
|
211
|
-
|
|
212
|
-
Both will freeze the wrong port into module-level constants.
|
|
202
|
+
Consumer `config.ts` MUST be imported below this line because it reads `getPortOverride()` at module evaluation time. Importing core itself first is safe: the registry is mutable and browser-safe. With no CLI port, scaffold config intentionally falls back to its pure-data `config.ports.ts` value.
|
|
213
203
|
|
|
214
204
|
## Resolution order summary
|
|
215
205
|
|
|
216
206
|
| Consumer | Source of port |
|
|
217
207
|
| --- | --- |
|
|
218
|
-
| `createLuckyStackServer` | `options.port` -> `getParsedPort()` -> `
|
|
208
|
+
| `createLuckyStackServer` | `options.port` -> `getParsedPort()` -> `options.defaultPort` -> `80` |
|
|
219
209
|
| `createLuckyStackServer` IP | `options.ip` -> `SERVER_IP` -> `127.0.0.1` |
|
|
220
210
|
| `createProdRuntimeMapsProvider` | `options.preset` (string -> single-entry array; non-empty array -> dedup) -> `getParsedBundles()` -> `['default']` |
|
|
221
211
|
|
|
222
212
|
## CLI examples
|
|
223
213
|
|
|
224
214
|
```bash
|
|
225
|
-
# Default preset,
|
|
215
|
+
# Default preset, config.ports.ts backend
|
|
226
216
|
npm run server
|
|
227
217
|
|
|
228
|
-
# Single bundle,
|
|
218
|
+
# Single bundle, config.ports.ts backend
|
|
229
219
|
npm run server -- billing
|
|
230
220
|
|
|
231
221
|
# Two bundles merged, port 4001
|
package/docs/create-server.md
CHANGED
|
@@ -13,10 +13,10 @@ Both honor an explicit pre-flight check via `verifyBootstrap(requirements?)`, wh
|
|
|
13
13
|
|
|
14
14
|
Boot order (effective for both entries):
|
|
15
15
|
|
|
16
|
-
1. `bootstrapLuckyStack` only: load overlay files (`core` -> `deploy` -> `login` -> `sentry` -> `presence` -> `docs-ui` -> `server`, each folder topologically followed by alphabetical `*.ts`).
|
|
16
|
+
1. `bootstrapLuckyStack` only: load overlay files (`core` -> `deploy` -> `login` -> `email` -> `sentry` -> `presence` -> `cron` -> `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-
|
|
19
|
+
4. Resolve `port` (`options.port` -> argv-registry override -> `options.defaultPort` -> `80`) and `ip` (`options.ip` -> `SERVER_IP` -> `127.0.0.1`); validate the port in `0..65535` and register the intended address plus configured-default metadata. After listen succeeds, register `httpServer.address().port` as the actually-bound address.
|
|
20
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 })`.
|
|
@@ -38,8 +38,8 @@ export const createLuckyStackServer = async (
|
|
|
38
38
|
|
|
39
39
|
| Field | Type | Default | Purpose |
|
|
40
40
|
| --- | --- | --- | --- |
|
|
41
|
-
| `port` | `number \| string` | `getParsedPort()` -> `defaultPort` -> `
|
|
42
|
-
| `defaultPort` | `number` | `
|
|
41
|
+
| `port` | `number \| string` | `getParsedPort()` -> `defaultPort` -> `80` | Explicit HTTP listen port. Numeric strings are accepted; partial/non-integer/out-of-range values fail before `listen`. |
|
|
42
|
+
| `defaultPort` | `number` | `80` | Config-level fallback below explicit `port` + argv; the scaffold passes `config.ports.backend`. It is also recorded so a programmatic `port` can update a callback that still names this default without overriding an unrelated local router ingress. |
|
|
43
43
|
| `ip` | `string` | `process.env.SERVER_IP ?? '127.0.0.1'` | Bind address. Registered so `checkOrigin` and other framework code see the resolved value. |
|
|
44
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`. |
|
|
45
45
|
| `serveFavicon` | `FaviconHandler` | none | Handler for `/favicon.ico`. Without it the route returns `404`. |
|
|
@@ -129,7 +129,7 @@ export const bootstrapLuckyStack = async (
|
|
|
129
129
|
|
|
130
130
|
**Behavior:**
|
|
131
131
|
|
|
132
|
-
- If `skipOverlayLoad !== true` and `<ROOT_DIR>/<overlayRoot>` exists, walk the canonical package order (`core`, `deploy`, `login`, `sentry`, `presence`, `docs-ui`, `server`).
|
|
132
|
+
- If `skipOverlayLoad !== true` and `<ROOT_DIR>/<overlayRoot>` exists, walk the canonical package order (`core`, `deploy`, `login`, `email`, `sentry`, `presence`, `cron`, `docs-ui`, `server`).
|
|
133
133
|
- Inside each package folder: import `index.ts`/`index.js` first if present, then every remaining `*.ts` / `*.js` file in alphabetical order. Each file is responsible for its own side-effect registration.
|
|
134
134
|
- Then delegate to `createLuckyStackServer(options)`.
|
|
135
135
|
|
|
@@ -276,7 +276,7 @@ export type CustomRouteHandler = (
|
|
|
276
276
|
|
|
277
277
|
| Source | Key | Effect |
|
|
278
278
|
| --- | --- | --- |
|
|
279
|
-
|
|
|
279
|
+
| config/argv | `config.ports.ts` backend / positional `[port]` | Static consumer default and per-boot override; no environment port bridge. |
|
|
280
280
|
| env | `SERVER_IP` | IP fallback. |
|
|
281
281
|
| env | `NODE_ENV` | Switches `enableDevTools` default, dynamic-import branch for devkit, and `verifyBootstrap` warn-vs-throw for runtime-maps / localized normalizer. |
|
|
282
282
|
| env | `SECURE` | When `'true'`, session cookies are emitted with the `Secure;` flag (see `request-pipeline.md`). |
|
package/docs/http-routes.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# HTTP Routes
|
|
2
2
|
|
|
3
|
-
> Deep specs. Bron: `packages/server/src/httpHandler.ts`, `packages/server/src/httpRoutes/*.ts`, `packages/server/src/customRoutesRegistry.ts`.
|
|
3
|
+
> Deep specs. Bron: `packages/server/src/httpHandler.ts`, `packages/server/src/httpRoutes/*.ts`, `packages/server/src/customRoutesRegistry.ts`.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ A custom-route registry sits in front of the static fallback. Both the legacy `c
|
|
|
33
33
|
|
|
34
34
|
| Route | Method(s) | Handler | Response (success) | Response (failure) |
|
|
35
35
|
| --- | --- | --- | --- | --- |
|
|
36
|
-
| `/auth/csrf` | GET | `handleCsrfRoute` | `200 { status: 'success', csrfToken }
|
|
36
|
+
| `/auth/csrf` | GET | `handleCsrfRoute` | `200 { status: 'success', csrfToken }`; without login it also sets the double-submit cookie | `401 auth.unauthenticated` only when login is installed and no valid session exists |
|
|
37
37
|
| `/favicon.ico` | GET | `handleFaviconRoute` | Whatever `options.serveFavicon(res)` writes | `404` empty when no handler supplied |
|
|
38
38
|
| `/livez` | GET | `handleLivezRoute` | `200 { status: 'live' }` | Always 200 when reachable |
|
|
39
39
|
| `/readyz` | GET | `handleReadyzRoute` | `200 { status: 'ready', checks: {...} }` | `503 { status: 'not-ready', checks: {...} }` |
|
|
@@ -52,7 +52,9 @@ A custom-route registry sits in front of the static fallback. Both the legacy `c
|
|
|
52
52
|
| `/assets/*`, `*.{png,jpg,jpeg,gif,svg,html,css,js}` | GET | `handleStaticAndSpaFallback` | Whatever `options.serveFile` writes (URL temporarily rewritten) | `404` when no `serveFile` is wired |
|
|
53
53
|
| Any other extension-less path | GET | `handleStaticAndSpaFallback` | SPA fallback (rewrite URL to `/`, call `serveFile`) | `404` when no `serveFile` |
|
|
54
54
|
|
|
55
|
-
>
|
|
55
|
+
> `GET /auth/csrf` issues the token; state-changing requests send it using `getCsrfConfig().headerName` (default `x-csrf-token`). `registerCsrfConfig(...)` also controls the login-absent cookie name/options and token length.
|
|
56
|
+
>
|
|
57
|
+
> `/_test/reset` clears rate limits and only the framework's default Redis session/active-user namespaces. It does not clear application database rows or invoke a custom `SessionAdapter`; custom-storage tests own that cleanup.
|
|
56
58
|
|
|
57
59
|
## API Reference — handlers
|
|
58
60
|
|
|
@@ -172,7 +174,7 @@ A handler returns `true` (or simply ends `res`) to short-circuit dispatch; retur
|
|
|
172
174
|
- Calls `handleHttpApiRequest({ name, data, token, requesterIp, xLanguageHeader, acceptLanguageHeader, method, stream? })` from `@luckystack/api`. The `stream` callback emits `event: stream` SSE frames during long-running calls.
|
|
173
175
|
- Emits the result (`event: final` for SSE, or `JSON.stringify(result)` + `res.writeHead(result.httpStatus)` for non-SSE).
|
|
174
176
|
|
|
175
|
-
**Errors:** any thrown error is logged + captured
|
|
177
|
+
**Errors:** any thrown error is logged + captured by the registered error tracker(s), `apiError` hook dispatched, and the envelope `500 api.invalidRequestFormat` is returned (over SSE or JSON).
|
|
176
178
|
|
|
177
179
|
### `handleSyncRoute(ctx)`
|
|
178
180
|
|
|
@@ -184,7 +186,7 @@ A handler returns `true` (or simply ends `res`) to short-circuit dispatch; retur
|
|
|
184
186
|
- Calls `handleHttpSyncRequest({ name: 'sync/<rest>', cb, data, receiver, ignoreSelf, token, requesterIp, xLanguageHeader, acceptLanguageHeader, stream? })` from `@luckystack/sync`.
|
|
185
187
|
- SSE handling mirrors `handleApiRoute`.
|
|
186
188
|
|
|
187
|
-
**Errors:** mirrors `handleApiRoute` — logs,
|
|
189
|
+
**Errors:** mirrors `handleApiRoute` — logs, registered error tracker(s), `syncError` hook, returns `500 sync.invalidRequestFormat`.
|
|
188
190
|
|
|
189
191
|
### `handleCustomRoutes(ctx)`
|
|
190
192
|
|
package/docs/request-pipeline.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Request Pipeline
|
|
2
2
|
|
|
3
|
-
> Deep specs. Bron: `packages/server/src/httpHandler.ts`, `packages/server/src/loadSocket.ts`, `packages/server/src/sse.ts`.
|
|
3
|
+
> Deep specs. Bron: `packages/server/src/httpHandler.ts`, `packages/server/src/loadSocket.ts`, `packages/server/src/sse.ts`.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
package/docs/runtime-maps.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Security Defaults
|
|
2
2
|
|
|
3
|
-
> Deep specs.
|
|
3
|
+
> Deep specs. Sources: `packages/server/src/httpHandler.ts`, `packages/server/src/httpRoutes/csrfRoute.ts`, `packages/server/src/httpRoutes/csrfMiddleware.ts`, `packages/server/src/httpRoutes/testResetRoute.ts`, `packages/server/src/securityHeadersRegistry.ts`, and `packages/server/src/errorFormatterRegistry.ts`.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
1. **Origin policy.** No `Host` fallback — non-browser callers cannot silently bypass CORS on state-changing methods.
|
|
10
10
|
2. **Security headers.** Framework defaults are emitted for every response. A consumer-registered builder may override or extend.
|
|
11
|
-
3. **CSRF middleware.** Cookie-mode state-changing
|
|
11
|
+
3. **CSRF middleware.** Cookie-mode state-changing framework and custom routes require the configured CSRF header unless they are an auth bootstrap/callback route or explicitly origin-exempt. With login installed it validates a session-bound token; without login it uses a stateless double-submit cookie/header pair. Mismatches return 403 and dispatch `csrfMismatch`.
|
|
12
12
|
4. **`/_test/reset` fail-closed.** Requires `NODE_ENV` to be exactly `development` or `test` AND a non-empty `TEST_RESET_TOKEN` env var. An unset token does NOT mean "no auth"; it means the route is permanently 403.
|
|
13
13
|
|
|
14
14
|
Plus two extension seams:
|
|
@@ -82,20 +82,24 @@ Lives in `enforceCsrfOnStateChangingRequest` (`csrfMiddleware.ts`). Runs after o
|
|
|
82
82
|
|
|
83
83
|
**Activation predicate (all required):**
|
|
84
84
|
|
|
85
|
-
- `projectConfig.session.basedToken === false` (cookie mode).
|
|
86
|
-
- HTTP method is
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
- A session token was extracted from the request.
|
|
85
|
+
- `projectConfig.session.basedToken === false` (cookie mode; the public scaffold flag is `config.sessionBasedToken`).
|
|
86
|
+
- HTTP method is state-changing: not `GET`, `HEAD`, or `OPTIONS`.
|
|
87
|
+
- The path is a framework API/sync/auth-API route, or any custom path outside `/auth/*` and `/assets/*`.
|
|
88
|
+
- The path is not an auth callback, credentials/email-code/2FA login-bootstrap route, or a prefix registered through `registerOriginExemptPath(...)`.
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
The validation branch then depends on whether `@luckystack/login` is installed:
|
|
92
91
|
|
|
93
|
-
1. `
|
|
94
|
-
2.
|
|
95
|
-
3.
|
|
96
|
-
4.
|
|
92
|
+
1. Read `getCsrfConfig().headerName` (default `x-csrf-token`; first value when the Node header is an array).
|
|
93
|
+
2. **Login present:** when a valid session token/session exists, compare the header with `session.csrfToken`. Requests without a valid session continue to the downstream auth policy instead of being misclassified as CSRF failures.
|
|
94
|
+
3. **Login absent:** read the configured CSRF cookie (default `csrf-token`) and compare it with the header using the timing-safe helper. No session read occurs.
|
|
95
|
+
4. On mismatch, dispatch `csrfMismatch` with `{ route, method, requestId, userId, providedToken: Boolean(provided) }`—presence only, never the token—then return `403 auth.csrfMismatch`.
|
|
97
96
|
|
|
98
|
-
Token issue
|
|
97
|
+
**Token issue:** `GET /auth/csrf` is the single issuing endpoint.
|
|
98
|
+
|
|
99
|
+
- With login present it requires a valid session and returns that session's CSRF token.
|
|
100
|
+
- Without login it returns a new random token and sets the same value in the configurable double-submit cookie; `httpOnly: true` is supported because the client reads the matching token from the JSON response. The cookie's `Secure` attribute follows explicit CSRF config or `SECURE=true`.
|
|
101
|
+
|
|
102
|
+
The client helpers fetch/cache the token per invocation origin and attach it to state-changing requests. Token mode (`sessionBasedToken=true`, internally `session.basedToken=true`) deliberately skips CSRF because JavaScript explicitly supplies the bearer/handshake token; the browser does not ambiently attach it like a cookie.
|
|
99
103
|
|
|
100
104
|
## `/_test/reset` fail-closed contract
|
|
101
105
|
|
|
@@ -125,7 +129,9 @@ x-test-reset-token: <value of TEST_RESET_TOKEN>
|
|
|
125
129
|
| 3 | `redis SCAN` + `DEL` keys matching `<projectName>-activeUsers:*`. Reported as `'activeUsers'` when at least one key is deleted. |
|
|
126
130
|
| 4 (opt-in) | When `?include=hooks` is supplied, `clearAllHooks()` empties every framework hook registration. Reported as `'hooks'`. |
|
|
127
131
|
|
|
128
|
-
`getProjectName()` (from `@luckystack/core`) is the single source of truth for the key prefix
|
|
132
|
+
`getProjectName()` (from `@luckystack/core`) is the single source of truth for the default Redis key prefix—same helper used by the Redis session adapter and rate limiter.
|
|
133
|
+
|
|
134
|
+
> **Custom `SessionAdapter` caveat:** `/_test/reset` clears only the framework's default Redis session and active-user namespaces. It does not call or discover a custom adapter, so sessions stored in DynamoDB, Postgres, an in-memory test adapter, or another backend survive this endpoint. Custom-adapter test suites must clear their own store in setup/teardown.
|
|
129
135
|
|
|
130
136
|
**Response:** `200 application/json { status: 'success', cleared: string[] }`. The `cleared` array reflects which subsystems reported deletions.
|
|
131
137
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luckystack/server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -57,24 +57,28 @@
|
|
|
57
57
|
"test": "vitest run"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@luckystack/api": "^0.8.
|
|
61
|
-
"@luckystack/core": "^0.8.
|
|
60
|
+
"@luckystack/api": "^0.8.4",
|
|
61
|
+
"@luckystack/core": "^0.8.4"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"@prisma/client": "^6.19.0",
|
|
65
65
|
"socket.io": "^4.8.0",
|
|
66
|
-
"@luckystack/
|
|
67
|
-
"@luckystack/
|
|
68
|
-
"@luckystack/
|
|
69
|
-
"@luckystack/
|
|
70
|
-
"@luckystack/
|
|
71
|
-
"@luckystack/
|
|
72
|
-
"@luckystack/
|
|
66
|
+
"@luckystack/cron": "^0.8.4",
|
|
67
|
+
"@luckystack/devkit": "^0.8.4",
|
|
68
|
+
"@luckystack/docs-ui": "^0.8.4",
|
|
69
|
+
"@luckystack/email": "^0.8.4",
|
|
70
|
+
"@luckystack/error-tracking": "^0.8.4",
|
|
71
|
+
"@luckystack/login": "^0.8.4",
|
|
72
|
+
"@luckystack/presence": "^0.8.4",
|
|
73
|
+
"@luckystack/sync": "^0.8.4"
|
|
73
74
|
},
|
|
74
75
|
"peerDependenciesMeta": {
|
|
75
76
|
"@prisma/client": {
|
|
76
77
|
"optional": true
|
|
77
78
|
},
|
|
79
|
+
"@luckystack/cron": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
78
82
|
"@luckystack/devkit": {
|
|
79
83
|
"optional": true
|
|
80
84
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portResolution.ts","../src/argv.ts"],"sourcesContent":["export interface ResolveServerPortInput {\n optionsPort?: number | string;\n parsedPort?: number | null;\n defaultPort?: number | string;\n envPort?: string;\n}\n\nexport const normalizeServerPort = (\n value: number | string,\n source = 'server port',\n): number => {\n let normalized: number;\n if (typeof value === 'number') normalized = value;\n else normalized = /^\\d+$/.test(value) ? Number(value) : Number.NaN;\n\n if (!Number.isInteger(normalized) || normalized < 0 || normalized > 65_535) {\n throw new RangeError(\n `[luckystack:port] ${source} must be an integer from 0 through 65535, got: \"${String(value)}\".`,\n );\n }\n\n return normalized;\n};\n\n//? One explicit, testable precedence chain. The scaffold passes ports.backend as\n//? `defaultPort`; SERVER_PORT remains a final compatibility fallback.\nexport const resolveServerPort = ({\n optionsPort,\n parsedPort,\n defaultPort,\n envPort,\n}: ResolveServerPortInput): number => {\n if (optionsPort !== undefined) return normalizeServerPort(optionsPort, 'options.port');\n if (parsedPort !== undefined && parsedPort !== null) return normalizeServerPort(parsedPort, 'argv port');\n if (defaultPort !== undefined) return normalizeServerPort(defaultPort, 'options.defaultPort');\n if (envPort !== undefined) return normalizeServerPort(envPort, 'SERVER_PORT');\n return 80;\n};\n","//? 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\nimport { normalizeServerPort } from './portResolution';\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 = normalizeServerPort(portArg, 'port argument');\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":";AAOO,IAAM,sBAAsB,CACjC,OACA,SAAS,kBACE;AACX,MAAI;AACJ,MAAI,OAAO,UAAU,SAAU,cAAa;AAAA,MACvC,cAAa,QAAQ,KAAK,KAAK,IAAI,OAAO,KAAK,IAAI,OAAO;AAE/D,MAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,KAAK,aAAa,OAAQ;AAC1E,UAAM,IAAI;AAAA,MACR,qBAAqB,MAAM,mDAAmD,OAAO,KAAK,CAAC;AAAA,IAC7F;AAAA,EACF;AAEA,SAAO;AACT;AAIO,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA,YAAAA;AAAA,EACA;AAAA,EACA;AACF,MAAsC;AACpC,MAAI,gBAAgB,OAAW,QAAO,oBAAoB,aAAa,cAAc;AACrF,MAAIA,gBAAe,UAAaA,gBAAe,KAAM,QAAO,oBAAoBA,aAAY,WAAW;AACvG,MAAI,gBAAgB,OAAW,QAAO,oBAAoB,aAAa,qBAAqB;AAC5F,MAAI,YAAY,OAAW,QAAO,oBAAoB,SAAS,aAAa;AAC5E,SAAO;AACT;;;ACjBA,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,oBAAoB,SAAS,eAAe;AAAA,EACrD;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":["parsedPort"]}
|