@luckystack/server 0.10.2 → 0.12.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.
Files changed (54) hide show
  1. package/AGENTS.md +7 -0
  2. package/CHANGELOG.md +13 -1
  3. package/README.md +2 -200
  4. package/dist/afterListen.d.ts +2 -0
  5. package/dist/argv.d.ts +8 -0
  6. package/dist/bootstrap.d.ts +36 -0
  7. package/dist/capabilities.d.ts +10 -0
  8. package/dist/createServer.d.ts +4 -0
  9. package/dist/customRoutesRegistry.d.ts +9 -0
  10. package/dist/devServerInfo.d.ts +7 -0
  11. package/dist/devToolsStatus.d.ts +3 -0
  12. package/dist/errorFormatterRegistry.d.ts +2 -0
  13. package/dist/hookPayloads.d.ts +62 -0
  14. package/dist/httpHandler.d.ts +3 -0
  15. package/dist/httpRoutes/apiRoute.d.ts +2 -0
  16. package/dist/httpRoutes/authApiRoute.d.ts +2 -0
  17. package/dist/httpRoutes/authCallbackRoute.d.ts +2 -0
  18. package/dist/httpRoutes/authLogoutRoute.d.ts +2 -0
  19. package/dist/httpRoutes/authProvidersRoute.d.ts +2 -0
  20. package/dist/httpRoutes/authSecondFactorRoutes.d.ts +3 -0
  21. package/dist/httpRoutes/csrfMiddleware.d.ts +8 -0
  22. package/dist/httpRoutes/csrfRoute.d.ts +2 -0
  23. package/dist/httpRoutes/customRoutes.d.ts +3 -0
  24. package/dist/httpRoutes/faviconRoute.d.ts +2 -0
  25. package/dist/httpRoutes/healthRoutes.d.ts +4 -0
  26. package/dist/httpRoutes/resolveRequesterIp.d.ts +27 -0
  27. package/dist/httpRoutes/sessionCookie.d.ts +1 -0
  28. package/dist/httpRoutes/staticRoutes.d.ts +2 -0
  29. package/dist/httpRoutes/syncRoute.d.ts +2 -0
  30. package/dist/httpRoutes/testResetRoute.d.ts +2 -0
  31. package/dist/httpRoutes/timingSafeEqual.d.ts +1 -0
  32. package/dist/httpRoutes/types.d.ts +15 -0
  33. package/dist/httpRoutes/uploadsRoute.d.ts +2 -0
  34. package/dist/index.d.ts +21 -352
  35. package/dist/index.js.map +1 -1
  36. package/dist/loadSocket.d.ts +21 -0
  37. package/dist/logSanitize.d.ts +1 -0
  38. package/dist/originExemptRegistry.d.ts +13 -0
  39. package/dist/parseArgv.d.ts +1 -2
  40. package/dist/portResolution.d.ts +7 -0
  41. package/dist/runtimeMapsLoader.d.ts +46 -0
  42. package/dist/securityHeadersRegistry.d.ts +13 -0
  43. package/dist/sse.d.ts +11 -0
  44. package/dist/stopServer.d.ts +28 -0
  45. package/dist/types.d.ts +138 -0
  46. package/dist/verifyBootstrap.d.ts +18 -0
  47. package/docs/argv-parsing.md +3 -244
  48. package/docs/create-server.md +3 -327
  49. package/docs/http-routes.md +3 -319
  50. package/docs/request-pipeline.md +3 -196
  51. package/docs/runtime-maps.md +3 -207
  52. package/docs/security-defaults.md +7 -238
  53. package/package.json +13 -13
  54. package/CLAUDE.md +0 -90
package/AGENTS.md ADDED
@@ -0,0 +1,7 @@
1
+ # @luckystack/server — design context
2
+
3
+ Server owns boot order and the single HTTP/Socket.io security perimeter. Use the high-level bootstrap for normal deployments, and the lower-level entry only when taking responsibility for the missing registrations and checks. Source: `src/bootstrap.ts`, `src/createServer.ts`, `src/verifyBootstrap.ts`.
4
+
5
+ Never treat CORS as authentication. The HTTP origin gate has no Host-header fallback; requests without a browser-attributable origin are rejected for state changes unless deliberately exempted. Exemptions for signed webhooks remove the origin/CSRF check, not the requirement to verify the caller in the route. Keep cookie-mode CSRF protection on state-changing custom routes and login bootstrap exceptions narrowly defined. Socket origin/auth checks and HTTP route checks are separate boundaries. Source: `src/httpHandler.ts`, `src/httpRoutes/csrfMiddleware.ts`, `src/originExemptRegistry.ts`, `src/loadSocket.ts`.
6
+
7
+ The test-reset endpoint must fail closed outside explicit test/development mode and without a secret. Its scoped targets come from core's shared list so server and test-runner cannot drift; an unknown target is an error, and the unscoped reset keeps its meaning (ADR 0064). Do not allow development map loading or tools to leak into production. See `docs/security-defaults.md`, `docs/runtime-maps.md`, `../login/AGENTS.md`.
package/CHANGELOG.md CHANGED
@@ -5,12 +5,24 @@ All notable changes to `@luckystack/server` are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.12.0] - 2026-10-15
9
+
10
+ ### Changed
11
+
12
+ - Requires Node.js 22.13 or newer (was 20). Node 20 stopped receiving security updates in April 2026, and the scaffold's lint toolchain (ESLint 10) needs 22.13.
13
+
14
+ ## [0.11.0] - 2026-10-14
15
+
16
+ ### Changed
17
+
18
+ - Type declarations are emitted per source file by TypeScript 7 (`tsc -b`) instead of one bundled `.d.ts` per entry, so the framework build no longer runs package by package. The exported types are unchanged; relative imports inside the declarations carry `.js`, so `moduleResolution: "node16"`/`"nodenext"` projects resolve them as before.
19
+ - Package design guidance now ships as `AGENTS.md`, not `CLAUDE.md`. Handwritten docs focus on rationale and safety; exact API contracts remain in installed declarations/source.
20
+
8
21
  ## [0.10.2] - 2026-10-09
9
22
 
10
23
  ### Added
11
24
 
12
25
  - `/_test/reset` accepts `?only=<target>[,<target>]` to clear exactly those targets (the shared list in core `TEST_RESET_TARGETS`). An unknown or empty target is `400 test.invalidResetTarget` — a typo must not silently reset nothing, or everything. Without `only` the default reset is unchanged. This lets an authenticated test sweep drain limiter buckets without deleting its own session.
13
-
14
26
  ## [0.8.7] - 2026-08-18
15
27
 
16
28
  ### Added
package/README.md CHANGED
@@ -1,203 +1,5 @@
1
1
  # @luckystack/server
2
2
 
3
- > One-call server bootstrap for [LuckyStack](https://github.com/ItsLucky23/LuckyStack-v2). HTTP + Socket.io + framework routes (`/api/*`, `/sync/*`, `/_health`, `/_test/reset`, `/auth/*`, `/uploads/*`) wired together. Your `server.ts` shrinks to ~20 lines.
3
+ Bootstrap for LuckyStack HTTP and Socket.io. Install with `npm install @luckystack/server socket.io`; add feature packages such as `@luckystack/api` or `@luckystack/sync` only when used (optional peers in `package.json`). Prefer `bootstrapLuckyStack` for ordinary boot: register project configuration, bootstrap with your application's static-file handlers, run `verifyBootstrap` after registration, then listen. `createLuckyStackServer` requires ownership of registration and readiness checks. See `src/bootstrap.ts` and `src/verifyBootstrap.ts` for current requirements.
4
4
 
5
- ## Install
6
-
7
- ```bash
8
- npm install @luckystack/server socket.io
9
-
10
- # Add the features your project uses; the server auto-detects these optional peers.
11
- # For the standard API + realtime stack, for example:
12
- npm install @luckystack/api @luckystack/sync
13
- ```
14
-
15
- ## Quickstart — recommended (`bootstrapLuckyStack`)
16
-
17
- `bootstrapLuckyStack` is a thin wrapper around `createLuckyStackServer` that auto-imports your `luckystack/<pkg>/*.ts` overlay files in topological order before listening, so registries (DI, hooks, providers) are populated by the time the HTTP server boots. This is what `create-luckystack-app` scaffolds.
18
-
19
- ```ts
20
- import 'dotenv/config';
21
- import '../config'; // your registerProjectConfig(...)
22
- import '../deploy.config'; // your registerDeployConfig(...)
23
- import '../services.config'; // your registerServicesConfig(...)
24
-
25
- import { bootstrapLuckyStack } from '@luckystack/server';
26
- import { serveFile, serveFavicon } from './prod/serveFile';
27
-
28
- const server = await bootstrapLuckyStack({
29
- serveFile,
30
- serveFavicon,
31
- });
32
-
33
- await server.listen();
34
- ```
35
-
36
- The bootstrap call runs (in order):
37
-
38
- 1. Auto-imports `luckystack/core/*.ts`, `luckystack/deploy/*.ts`, `luckystack/login/*.ts`, `luckystack/email/*.ts`, `luckystack/sentry/*.ts`, `luckystack/presence/*.ts`, `luckystack/cron/*.ts`, `luckystack/docs-ui/*.ts`, `luckystack/server/*.ts` (topologically sorted, then alphabetically inside each folder).
39
- 2. Hands off to `createLuckyStackServer(options)`.
40
-
41
- Set `skipOverlayLoad: true` to handle every registration yourself, or `overlayRoot: 'custom-folder'` to load from somewhere other than `./luckystack`.
42
-
43
- ### Pre-flight check — `verifyBootstrap`
44
-
45
- Call `verifyBootstrap` **after** the overlay loads (or after your manual registrations) and **before** `server.listen()` if you want to fail fast on missing registrations instead of silent runtime crashes:
46
-
47
- ```ts
48
- import { bootstrapLuckyStack, verifyBootstrap } from '@luckystack/server';
49
-
50
- const server = await bootstrapLuckyStack({ serveFile, serveFavicon });
51
-
52
- await verifyBootstrap({
53
- requireDeployConfig: true, // single-instance deploys can omit this
54
- requireServicesConfig: true, // only true when the router will run
55
- requireOAuthProviders: true, // false if you only use credentials login
56
- });
57
-
58
- await server.listen();
59
- ```
60
-
61
- `verifyBootstrap` throws a single descriptive `Error` listing every missing registration. The full check covers:
62
-
63
- - `ProjectConfig` — always required.
64
- - `DeployConfig` — when `requireDeployConfig: true`.
65
- - `ServicesConfig` — when `requireServicesConfig: true`.
66
- - OAuth providers — when `requireOAuthProviders: true` and only the default `credentials` entry has been registered.
67
- - `RuntimeMapsProvider` — hard-fails in production; loud-warns in dev because devkit hot-reload normally registers one. Without it, every API/sync request silently resolves to `notFound`.
68
- - `LocalizedNormalizer` — hard-fails in production; warns in dev. Without it, error responses degrade to `errorCode`-as-message (no i18n).
69
-
70
- ## Lower-level entry — `createLuckyStackServer`
71
-
72
- Use this directly when you want to control the order of imports yourself or skip the overlay convention:
73
-
74
- ```ts
75
- import { initializeSentry } from '@luckystack/error-tracking';
76
- import { registerPresenceHooks } from '@luckystack/presence';
77
- import { createLuckyStackServer } from '@luckystack/server';
78
-
79
- initializeSentry();
80
- registerPresenceHooks();
81
-
82
- const server = await createLuckyStackServer({
83
- serveFile,
84
- serveFavicon,
85
- });
86
- await server.listen();
87
- ```
88
-
89
- Both entries handle devkit hot reload + console init in dev mode automatically — opt out with `enableDevTools: false` if you have your own.
90
-
91
- ## Options
92
-
93
- ```ts
94
- interface CreateLuckyStackServerOptions {
95
- port?: number | string; // options.port > argv > defaultPort > 80
96
- defaultPort?: number; // scaffold's config.ports.ts backend default
97
- ip?: string; // default: process.env.SERVER_IP ?? '127.0.0.1'
98
- serveFile?: StaticFileHandler; // catch-all for non-framework GETs (Vite output, SPA fallback)
99
- serveFavicon?: FaviconHandler; // /favicon.ico
100
- customRoutes?: CustomRouteHandler; // pre-fallback hook; return true to mark handled
101
- enableDevTools?: boolean; // default: NODE_ENV !== 'production'
102
- maxHttpBufferSize?: number; // default: 5 MB
103
- }
104
- ```
105
-
106
- `customRoutes` runs before the static file serving, so you can add project-specific HTTP endpoints (webhooks, OG image generation, etc.) without forking the package. Return `true` if you ended the response.
107
-
108
- You can also register custom routes globally via `registerCustomRoute(handler)` — the bootstrap call composes every registered handler into the running server. This is what `@luckystack/docs-ui` uses to mount `/_docs`.
109
-
110
- ```ts
111
- import { registerCustomRoute } from '@luckystack/server';
112
-
113
- registerCustomRoute(async (req, res) => {
114
- if (req.url !== '/healthz') return false;
115
- res.statusCode = 200;
116
- res.end('ok');
117
- return true;
118
- });
119
- ```
120
-
121
- ## What it wires
122
-
123
- - **HTTP server** with CORS + security headers, OPTIONS preflight, method validation, cookie sliding, CSRF middleware.
124
- - **Socket.io server** attached to the HTTP server, with optional Redis adapter when configured in deploy config.
125
- - **Framework routes:** `/_health`, `/livez`, `/readyz`, `/_test/reset` (dev/test only), `/api/*` and `/sync/*` (with SSE streaming), `/auth/api`, `/auth/callback/*`, `GET /auth/csrf`, `/uploads/*`, and `/assets/*`.
126
- - **Presence broadcasting** (when enabled in project config): connect / disconnect / reconnect, location updates, peer notifications.
127
- - **Boot UUID** written on startup so the load-balancer (`@luckystack/router`) can detect rolling restarts.
128
- - **Dev tools** in non-production: devkit hot reload, console initializer.
129
-
130
- ### HTTP route handler layout
131
-
132
- `handleHttpRequest` is now a thin (~190-line) orchestrator that sets up CORS / origin / security headers, runs the CSRF middleware, then dispatches a route-handler table. Each handler matches its own route path and returns `boolean` (handled or not). The handlers live in `packages/server/src/httpRoutes/`:
133
-
134
- | File | Routes |
135
- | --- | --- |
136
- | `csrfMiddleware.ts` + `csrfRoute.ts` | CSRF guard on cookie-mode writes + `GET /auth/csrf` token issue |
137
- | `healthRoutes.ts` | `/livez`, `/readyz`, `/_health` |
138
- | `testResetRoute.ts` | `/_test/reset` (dev/test only — see Security below) |
139
- | `faviconRoute.ts` | `/favicon.ico` |
140
- | `uploadsRoute.ts` | `/uploads/*` (avatar serving via `serveAvatar`) |
141
- | `authApiRoute.ts` | `/auth/api/*` |
142
- | `authCallbackRoute.ts` | `/auth/callback/*` |
143
- | `apiRoute.ts` | `/api/*` (delegates to `@luckystack/api`'s `handleHttpApiRequest`) |
144
- | `syncRoute.ts` | `/sync/*` (delegates to `@luckystack/sync`'s `handleHttpSyncRequest`) |
145
- | `customRoutes.ts` | Calls every handler registered via `registerCustomRoute(...)` and the inline `customRoutes` option |
146
- | `staticRoutes.ts` | Final fallback to the consumer's `serveFile(...)` (Vite SPA, etc.) |
147
-
148
- Top-level `handleHttpRequest` + `dispatchRoutes(handlers, ctx)` are the only orchestration; everything else is a flat list of single-purpose handlers. SSE handling, error fall-through, and dispatch order are preserved.
149
-
150
- ### Security defaults that may surprise you
151
-
152
- - **CORS fail-closed.** If neither `Origin` nor `Referer` is present, only read-only methods (GET, HEAD, OPTIONS) are allowed; state-changing methods are rejected with 403. Earlier builds fell back to `Host`, which silently bypassed CORS for non-browser callers (`curl`, server-to-server). When you `curl` a write endpoint, set `-H 'Origin: https://your-allowed-origin'`.
153
- - **`/_test/reset` is fail-closed.** It requires both `NODE_ENV` to be exactly `development` or `test` AND a non-empty `TEST_RESET_TOKEN` env var. Any other state returns 403 (no silent allow-list). Wire `TEST_RESET_TOKEN` in your dev/test `.env.local` and pass it as the `x-test-reset-token: ${TOKEN}` header on the reset call. `@luckystack/test-runner`'s `resetServerState` reads the same env var.
154
- - **CSRF middleware.** In cookie mode, state-changing framework routes and eligible custom routes require the configured CSRF header (default `x-csrf-token`). With login installed it must match the session-bound token; without login it must match the stateless double-submit cookie issued by `GET /auth/csrf`. The `apiRequest` helper in `@luckystack/core/client` fetches and attaches it automatically. Auth bootstrap/callback routes and explicitly origin-exempt paths are excluded. Rejections dispatch `csrfMismatch` before returning 403; the hook receives token presence only, never the value. Configure the header name, token length, and cookie name/options through `registerCsrfConfig(...)` — see `packages/core/docs/csrf-config.md` and `packages/server/docs/security-defaults.md`.
155
-
156
- ## Public API
157
-
158
- | Export | Purpose |
159
- | --- | --- |
160
- | `bootstrapLuckyStack(options)` | High-level entry: verifies config, auto-imports overlay, then calls `createLuckyStackServer`. |
161
- | `createLuckyStackServer(options)` | Lower-level factory that returns `{ httpServer, ioServer, listen, stop, close }` (`stop`/`close` = graceful MIS-016 shutdown). |
162
- | `verifyBootstrap(requirements?)` | Pre-flight check for project/deploy/services config and required env keys. |
163
- | `registerCustomRoute(handler)` / `getCustomRoutes()` / `clearCustomRoutes()` | Global custom-route registry composed by `bootstrapLuckyStack`. |
164
- | Hook payload types: `OnSocketConnectPayload`, `OnSocketDisconnectPayload`, `PreRoomJoinPayload`, `PostRoomJoinPayload`, `PreRoomLeavePayload`, `PostRoomLeavePayload`, `OnLocationUpdatePayload` | For socket-lifecycle hook handlers. |
165
- | Types: `CreateLuckyStackServerOptions`, `BootstrapLuckyStackOptions`, `BootstrapRequirements`, `RunningLuckyStackServer`, `RouteContext`, `StaticFileHandler`, `FaviconHandler`, `CustomRouteHandler` | Handler typing. |
166
-
167
- ## Dependencies
168
-
169
- - Runtime: `@luckystack/api`, `@luckystack/core`
170
- - Required peer: `socket.io@^4.8.0`
171
- - Optional peers: `@prisma/client@^6.19.0`, `@luckystack/login`, `@luckystack/presence`, `@luckystack/sync`, `@luckystack/error-tracking`, `@luckystack/email`, `@luckystack/cron`, `@luckystack/docs-ui`, and `@luckystack/devkit` (dev-only). Bootstrap auto-detects installed optional peers and degrades when they are absent.
172
-
173
- ## Selecting bundles + port at runtime
174
-
175
- `@luckystack/server/parseArgv` is a side-effect-only entrypoint. Import it as the **first line** of your `server.ts` so a positional CLI port reaches the browser-safe `@luckystack/core/config` registry before your project's `config.ts` evaluates.
176
-
177
- ```ts
178
- // server.ts
179
- import '@luckystack/server/parseArgv';
180
- // ...rest of your bootstrap
181
- ```
182
-
183
- Argv shape: `<bundle[,bundle...]> [port]`. The positional port overrides the
184
- scaffold's `defaultPort: ports.backend`. Generic boots with no explicit,
185
- positional, or default port retain the numeric fallback `80`.
186
-
187
- ```bash
188
- npm run server # default preset, config.ports.ts backend
189
- npm run server -- billing # one bundle, config.ports.ts backend
190
- npm run server -- billing,vehicles 4001 # merge bundles, listen on 4001
191
- ```
192
-
193
- When multiple presets are passed, the framework loads each preset's generated route map and shallow-merges `apis` / `syncs` / `functions`. Key collisions across presets throw at boot (services must own exactly one preset).
194
-
195
- ## Related architecture docs
196
-
197
- - [`docs/ARCHITECTURE_PACKAGING.md`](../../docs/ARCHITECTURE_PACKAGING.md) — package split, multi-service builds, preset bundles.
198
- - [`docs/ARCHITECTURE_SOCKET.md`](../../docs/ARCHITECTURE_SOCKET.md) — Socket.io setup, Redis adapter, room model.
199
- - [`docs/HOSTING.md`](../../docs/HOSTING.md) — multi-instance deployment, `@luckystack/router`, health probes.
200
-
201
- ## License
202
-
203
- MIT — see [LICENSE](../../LICENSE).
5
+ Server is the common security perimeter: preserve origin, CSRF, socket authorization and route-specific checks when adding custom routes. Webhook origin exemptions require independent caller authentication. Test-only reset must remain unreachable in production. See [design context](./AGENTS.md), [security rationale](./docs/security-defaults.md), and [runtime maps](./docs/runtime-maps.md). Runtime arguments and options are defined by `src/argv.ts` and `src/types.ts`.
@@ -0,0 +1,2 @@
1
+ import type { AfterListenOptions } from './types.js';
2
+ export declare const runAfterListenTask: (task: () => void | Promise<void>, { fatal, label }?: AfterListenOptions) => Promise<void>;
package/dist/argv.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export interface ParsedServerArgv {
2
+ bundles: string[];
3
+ port: number | null;
4
+ }
5
+ export declare const parseServerArgv: (argv: string[]) => ParsedServerArgv;
6
+ export declare const applyServerArgv: () => void;
7
+ export declare const getParsedBundles: () => string[];
8
+ export declare const getParsedPort: () => number | null;
@@ -0,0 +1,36 @@
1
+ import type { CreateLuckyStackServerOptions, RunningLuckyStackServer } from './types.js';
2
+ export interface BootstrapLuckyStackOptions extends CreateLuckyStackServerOptions {
3
+ /**
4
+ * Folder name (relative to project root) that contains the overlay files.
5
+ * Default: `luckystack`. Each subfolder mirrors a framework package and
6
+ * holds the project's overrides for that package.
7
+ */
8
+ overlayRoot?: string;
9
+ /**
10
+ * Skip auto-loading the overlay folder. Useful for tests that build the
11
+ * registries by hand.
12
+ */
13
+ skipOverlayLoad?: boolean;
14
+ }
15
+ export declare const OVERLAY_ORDER: string[];
16
+ type OverlayLoader = () => Promise<void>;
17
+ export declare const registerOverlayLoader: (loader: OverlayLoader) => void;
18
+ export interface OverlayEntries {
19
+ /** Files to import, in load order: `index.*` first, then the rest alphabetically. */
20
+ fileNames: string[];
21
+ /**
22
+ * Subdirectories the flat walk skipped, minus conventional test folders.
23
+ * The caller warns about these — silently dropping them meant overlay code
24
+ * one level down simply never ran, with nothing to point at.
25
+ */
26
+ ignoredDirectoryNames: string[];
27
+ }
28
+ /**
29
+ * Decide what a single overlay package folder contributes. Exported because
30
+ * both the runtime walk here and `scripts/bundleServer.mjs` must agree exactly:
31
+ * a file the bundler includes but the loader skips (or the reverse) is a
32
+ * dev/prod divergence, which is the defect class ADR 0046 exists for.
33
+ */
34
+ export declare const collectOverlayEntries: (packageDir: string) => OverlayEntries;
35
+ export declare const bootstrapLuckyStack: (options?: BootstrapLuckyStackOptions) => Promise<RunningLuckyStackServer>;
36
+ export {};
@@ -0,0 +1,10 @@
1
+ export declare const capabilities: {
2
+ readonly login: boolean;
3
+ readonly presence: boolean;
4
+ readonly sync: boolean;
5
+ };
6
+ export declare const OPTIONAL_PACKAGES: readonly ['login', 'email', 'error-tracking', 'presence', 'cron', 'docs-ui'];
7
+ export declare const canResolve: (specifier: string) => boolean;
8
+ export declare const getLogin: () => Promise<typeof import('@luckystack/login') | null>;
9
+ export declare const getPresence: () => Promise<typeof import('@luckystack/presence') | null>;
10
+ export declare const getSync: () => Promise<typeof import('@luckystack/sync') | null>;
@@ -0,0 +1,4 @@
1
+ import { type Server as HttpServer } from 'node:http';
2
+ import type { CreateLuckyStackServerOptions, RunningLuckyStackServer } from './types.js';
3
+ export declare const listenLuckyStackServer: (httpServer: HttpServer, ip: string, port: string | number, callback?: () => void) => Promise<HttpServer>;
4
+ export declare const createLuckyStackServer: (options?: CreateLuckyStackServerOptions) => Promise<RunningLuckyStackServer>;
@@ -0,0 +1,9 @@
1
+ import type { CustomRouteHandler, CustomRoutePhase } from './types.js';
2
+ export interface RegisterCustomRouteOptions {
3
+ /** Pipeline phase the handler runs in. Defaults to `'post-params'`. */
4
+ phase?: CustomRoutePhase;
5
+ }
6
+ export declare const registerCustomRoute: (handler: CustomRouteHandler, options?: RegisterCustomRouteOptions) => void;
7
+ export declare const getCustomRoutes: () => readonly CustomRouteHandler[];
8
+ export declare const getPreParamsCustomRoutes: () => readonly CustomRouteHandler[];
9
+ export declare const clearCustomRoutes: () => void;
@@ -0,0 +1,7 @@
1
+ export interface DevServerInfo {
2
+ ip: string;
3
+ port: number;
4
+ pid: number;
5
+ }
6
+ export declare const writeDevServerInfo: (ip: string, port: number) => void;
7
+ export declare const clearDevServerInfo: () => void;
@@ -0,0 +1,3 @@
1
+ export declare const markDevToolsInitFailed: (error: Error) => void;
2
+ export declare const getDevToolsInitError: () => Error | null;
3
+ export declare const clearDevToolsInitError: () => void;
@@ -0,0 +1,2 @@
1
+ export { registerErrorFormatter, getErrorFormatter, applyErrorFormatter, } from '@luckystack/core';
2
+ export type { ErrorFormatter, ErrorFormatterContext } from '@luckystack/core';
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Module augmentation: extends `@luckystack/core`'s `HookPayloads` interface
3
+ * with socket lifecycle hooks owned by this package. Side-effect imported
4
+ * by `index.ts` so consumers get the augmentation automatically.
5
+ *
6
+ * Hook semantics:
7
+ * - `on*` hooks are notifications. Handlers may side-effect; return values
8
+ * are ignored.
9
+ * - `pre*` hooks may return a `HookStopSignal` to abort the main flow. The
10
+ * stop signal carries an `errorCode` that's emitted back to the client.
11
+ * - `post*` hooks fire after the side-effect succeeds.
12
+ */
13
+ export interface OnSocketConnectPayload {
14
+ socketId: string;
15
+ token: string | null;
16
+ ip: string;
17
+ }
18
+ export interface OnSocketDisconnectPayload {
19
+ socketId: string;
20
+ token: string | null;
21
+ reason: string;
22
+ }
23
+ export interface PreRoomJoinPayload {
24
+ token: string;
25
+ room: string;
26
+ }
27
+ export interface PostRoomJoinPayload {
28
+ token: string;
29
+ room: string;
30
+ allRooms: string[];
31
+ }
32
+ export interface PreRoomLeavePayload {
33
+ token: string;
34
+ room: string;
35
+ }
36
+ export interface PostRoomLeavePayload {
37
+ token: string;
38
+ room: string;
39
+ allRooms: string[];
40
+ }
41
+ export interface OnLocationUpdatePayload {
42
+ token: string;
43
+ oldLocation: {
44
+ pathName: string;
45
+ searchParams?: Record<string, string>;
46
+ } | undefined;
47
+ newLocation: {
48
+ pathName: string;
49
+ searchParams?: Record<string, string>;
50
+ };
51
+ }
52
+ declare module '@luckystack/core' {
53
+ interface HookPayloads {
54
+ onSocketConnect: OnSocketConnectPayload;
55
+ onSocketDisconnect: OnSocketDisconnectPayload;
56
+ preRoomJoin: PreRoomJoinPayload;
57
+ postRoomJoin: PostRoomJoinPayload;
58
+ preRoomLeave: PreRoomLeavePayload;
59
+ postRoomLeave: PostRoomLeavePayload;
60
+ onLocationUpdate: OnLocationUpdatePayload;
61
+ }
62
+ }
@@ -0,0 +1,3 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { CreateLuckyStackServerOptions } from './types.js';
3
+ export declare const handleHttpRequest: (req: IncomingMessage, res: ServerResponse, options: CreateLuckyStackServerOptions) => Promise<void>;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleApiRoute: HttpRouteHandler;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleAuthApiRoute: HttpRouteHandler;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleAuthCallbackRoute: HttpRouteHandler;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleAuthLogoutRoute: HttpRouteHandler;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleAuthProvidersRoute: HttpRouteHandler;
@@ -0,0 +1,3 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleAuthEmailCodeRoute: HttpRouteHandler;
3
+ export declare const handleAuthTwoFactorRoute: HttpRouteHandler;
@@ -0,0 +1,8 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ export declare const enforceCsrfOnStateChangingRequest: ({ req, res, routePath, token, requestId, }: {
3
+ req: IncomingMessage;
4
+ res: ServerResponse;
5
+ routePath: string;
6
+ token: string | null;
7
+ requestId?: string;
8
+ }) => Promise<boolean>;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleCsrfRoute: HttpRouteHandler;
@@ -0,0 +1,3 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handlePreParamsCustomRoutes: HttpRouteHandler;
3
+ export declare const handleCustomRoutes: HttpRouteHandler;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleFaviconRoute: HttpRouteHandler;
@@ -0,0 +1,4 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleLivezRoute: HttpRouteHandler;
3
+ export declare const handleReadyzRoute: HttpRouteHandler;
4
+ export declare const handleHealthRoute: HttpRouteHandler;
@@ -0,0 +1,27 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ /**
3
+ * Resolve the real client IP for per-IP rate limiting on the HTTP api/sync
4
+ * transports.
5
+ *
6
+ * Extracted verbatim from the previously-duplicated inline block in
7
+ * `apiRoute.ts` and `syncRoute.ts` (both were byte-identical). Behaviour is
8
+ * preserved exactly:
9
+ *
10
+ * - Reads `http.trustProxy` from the active project config. Default
11
+ * `trustProxy: false` returns the raw `req.socket.remoteAddress` (with only
12
+ * IPv4-mapped IPv6 canonicalization via `resolveClientIp`); a trusted proxy
13
+ * honours `X-Forwarded-For` / `X-Real-IP`.
14
+ * - Preserves the historical `undefined` fallback when there is genuinely no
15
+ * address to resolve (no raw socket address AND — when proxied — no trusted
16
+ * forwarded header), so downstream `?? 'anonymous'` / `?? 'unknown'`
17
+ * bucketing stays byte-identical. This `undefined` short-circuit is the
18
+ * reason this helper exists rather than calling `resolveClientIp` directly:
19
+ * `resolveClientIp` would return the `'unknown'` sentinel here, which the
20
+ * api/sync transports historically did NOT key on.
21
+ *
22
+ * NOTE: the `/auth/api` route intentionally does NOT use this helper — it
23
+ * keys on `resolveClientIp(...)` unconditionally (returning the `'unknown'`
24
+ * sentinel when nothing resolves), which is a different, deliberate
25
+ * contract. Unifying the two would change behaviour, so it is left as-is.
26
+ */
27
+ export declare const resolveRequesterIp: (req: IncomingMessage) => string | undefined;
@@ -0,0 +1 @@
1
+ export declare const resolveCookieSecure: (sessionCookieSecure: boolean | undefined, secureEnv: string | undefined) => boolean;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleStaticAndSpaFallback: HttpRouteHandler;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleSyncRoute: HttpRouteHandler;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleTestResetRoute: HttpRouteHandler;
@@ -0,0 +1 @@
1
+ export declare const timingSafeStringEqual: (a: string, b: string) => boolean;
@@ -0,0 +1,15 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { CreateLuckyStackServerOptions } from '../types.js';
3
+ export interface HttpRouteContext {
4
+ req: IncomingMessage;
5
+ res: ServerResponse;
6
+ options: CreateLuckyStackServerOptions;
7
+ routePath: string;
8
+ queryString: string | undefined;
9
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
10
+ token: string | null;
11
+ requestId: string;
12
+ sessionCookieOptions: string;
13
+ params: object;
14
+ }
15
+ export type HttpRouteHandler = (ctx: HttpRouteContext) => Promise<boolean>;
@@ -0,0 +1,2 @@
1
+ import type { HttpRouteHandler } from './types.js';
2
+ export declare const handleUploadsRoute: HttpRouteHandler;