@luckystack/server 0.10.2 → 0.11.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/AGENTS.md +7 -0
- package/CHANGELOG.md +7 -1
- package/README.md +2 -200
- package/dist/afterListen.d.ts +2 -0
- package/dist/argv.d.ts +8 -0
- package/dist/bootstrap.d.ts +36 -0
- package/dist/capabilities.d.ts +10 -0
- package/dist/createServer.d.ts +4 -0
- package/dist/customRoutesRegistry.d.ts +9 -0
- package/dist/devServerInfo.d.ts +7 -0
- package/dist/devToolsStatus.d.ts +3 -0
- package/dist/errorFormatterRegistry.d.ts +2 -0
- package/dist/hookPayloads.d.ts +62 -0
- package/dist/httpHandler.d.ts +3 -0
- package/dist/httpRoutes/apiRoute.d.ts +2 -0
- package/dist/httpRoutes/authApiRoute.d.ts +2 -0
- package/dist/httpRoutes/authCallbackRoute.d.ts +2 -0
- package/dist/httpRoutes/authLogoutRoute.d.ts +2 -0
- package/dist/httpRoutes/authProvidersRoute.d.ts +2 -0
- package/dist/httpRoutes/authSecondFactorRoutes.d.ts +3 -0
- package/dist/httpRoutes/csrfMiddleware.d.ts +8 -0
- package/dist/httpRoutes/csrfRoute.d.ts +2 -0
- package/dist/httpRoutes/customRoutes.d.ts +3 -0
- package/dist/httpRoutes/faviconRoute.d.ts +2 -0
- package/dist/httpRoutes/healthRoutes.d.ts +4 -0
- package/dist/httpRoutes/resolveRequesterIp.d.ts +27 -0
- package/dist/httpRoutes/sessionCookie.d.ts +1 -0
- package/dist/httpRoutes/staticRoutes.d.ts +2 -0
- package/dist/httpRoutes/syncRoute.d.ts +2 -0
- package/dist/httpRoutes/testResetRoute.d.ts +2 -0
- package/dist/httpRoutes/timingSafeEqual.d.ts +1 -0
- package/dist/httpRoutes/types.d.ts +15 -0
- package/dist/httpRoutes/uploadsRoute.d.ts +2 -0
- package/dist/index.d.ts +21 -352
- package/dist/loadSocket.d.ts +21 -0
- package/dist/logSanitize.d.ts +1 -0
- package/dist/originExemptRegistry.d.ts +13 -0
- package/dist/parseArgv.d.ts +1 -2
- package/dist/portResolution.d.ts +7 -0
- package/dist/runtimeMapsLoader.d.ts +46 -0
- package/dist/securityHeadersRegistry.d.ts +13 -0
- package/dist/sse.d.ts +11 -0
- package/dist/stopServer.d.ts +28 -0
- package/dist/types.d.ts +138 -0
- package/dist/verifyBootstrap.d.ts +18 -0
- package/docs/argv-parsing.md +3 -244
- package/docs/create-server.md +3 -327
- package/docs/http-routes.md +3 -319
- package/docs/request-pipeline.md +3 -196
- package/docs/runtime-maps.md +3 -207
- package/docs/security-defaults.md +7 -238
- package/package.json +12 -12
- 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,18 @@ 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.11.0] - 2026-10-14
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
8
15
|
## [0.10.2] - 2026-10-09
|
|
9
16
|
|
|
10
17
|
### Added
|
|
11
18
|
|
|
12
19
|
- `/_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
20
|
## [0.8.7] - 2026-08-18
|
|
15
21
|
|
|
16
22
|
### Added
|
package/README.md
CHANGED
|
@@ -1,203 +1,5 @@
|
|
|
1
1
|
# @luckystack/server
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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`.
|
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,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,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,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 @@
|
|
|
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>;
|