@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/dist/index.d.ts CHANGED
@@ -1,352 +1,21 @@
1
- import { IncomingMessage, ServerResponse, Server } from 'node:http';
2
- import { Server as Server$1 } from 'socket.io';
3
- import { RuntimeMapsProvider } from '@luckystack/core';
4
- export { ErrorFormatter, ErrorFormatterContext, getErrorFormatter, registerErrorFormatter } from '@luckystack/core';
5
-
6
- /**
7
- * Module augmentation: extends `@luckystack/core`'s `HookPayloads` interface
8
- * with socket lifecycle hooks owned by this package. Side-effect imported
9
- * by `index.ts` so consumers get the augmentation automatically.
10
- *
11
- * Hook semantics:
12
- * - `on*` hooks are notifications. Handlers may side-effect; return values
13
- * are ignored.
14
- * - `pre*` hooks may return a `HookStopSignal` to abort the main flow. The
15
- * stop signal carries an `errorCode` that's emitted back to the client.
16
- * - `post*` hooks fire after the side-effect succeeds.
17
- */
18
- interface OnSocketConnectPayload {
19
- socketId: string;
20
- token: string | null;
21
- ip: string;
22
- }
23
- interface OnSocketDisconnectPayload {
24
- socketId: string;
25
- token: string | null;
26
- reason: string;
27
- }
28
- interface PreRoomJoinPayload {
29
- token: string;
30
- room: string;
31
- }
32
- interface PostRoomJoinPayload {
33
- token: string;
34
- room: string;
35
- allRooms: string[];
36
- }
37
- interface PreRoomLeavePayload {
38
- token: string;
39
- room: string;
40
- }
41
- interface PostRoomLeavePayload {
42
- token: string;
43
- room: string;
44
- allRooms: string[];
45
- }
46
- interface OnLocationUpdatePayload {
47
- token: string;
48
- oldLocation: {
49
- pathName: string;
50
- searchParams?: Record<string, string>;
51
- } | undefined;
52
- newLocation: {
53
- pathName: string;
54
- searchParams?: Record<string, string>;
55
- };
56
- }
57
- declare module '@luckystack/core' {
58
- interface HookPayloads {
59
- onSocketConnect: OnSocketConnectPayload;
60
- onSocketDisconnect: OnSocketDisconnectPayload;
61
- preRoomJoin: PreRoomJoinPayload;
62
- postRoomJoin: PostRoomJoinPayload;
63
- preRoomLeave: PreRoomLeavePayload;
64
- postRoomLeave: PostRoomLeavePayload;
65
- onLocationUpdate: OnLocationUpdatePayload;
66
- }
67
- }
68
-
69
- interface RouteContext {
70
- routePath: string;
71
- method: string;
72
- queryString: string | undefined;
73
- token: string | null;
74
- }
75
- type StaticFileHandler = (req: IncomingMessage, res: ServerResponse) => unknown;
76
- type FaviconHandler = (res: ServerResponse) => unknown;
77
- type CustomRouteHandler = (req: IncomingMessage, res: ServerResponse, ctx: RouteContext) => Promise<boolean> | boolean;
78
- type CustomRoutePhase = 'pre-params' | 'post-params';
79
- interface CreateLuckyStackServerOptions {
80
- /**
81
- * Port the HTTP server listens on. Resolution order:
82
- * 1. This option (`options.port`)
83
- * 2. Second positional argv (`npm run server -- <bundles> <port>`), parsed
84
- * by `@luckystack/server/parseArgv`
85
- * 3. `options.defaultPort` (the project's single-instance default, e.g. the
86
- * `backend` port from `config.ports.ts`)
87
- * 4. `80` for generic consumers that supplied no port source
88
- */
89
- port?: number | string;
90
- /**
91
- * Project's single-instance default backend port. Sits BELOW positional argv
92
- * so a multi-instance boot (`npm run server -- <preset> <port>`) still wins.
93
- * The scaffold passes the `backend` value from `config.ports.ts` here, so the
94
- * listen port has one static source of truth.
95
- */
96
- defaultPort?: number;
97
- /** Bind address. Defaults to process.env.SERVER_IP or '127.0.0.1'. */
98
- ip?: string;
99
- /** Project-side static file handler (Vite output, etc.). Used as the catch-all. */
100
- serveFile?: StaticFileHandler;
101
- /** Project-side favicon handler. */
102
- serveFavicon?: FaviconHandler;
103
- /**
104
- * Optional pre-fallback hook for project-specific HTTP routes. Return `true`
105
- * if the route was handled (response ended). Return `false` (or omit return)
106
- * to fall through to the framework's static file serving.
107
- */
108
- customRoutes?: CustomRouteHandler;
109
- /**
110
- * Enable dev-mode tooling (devkit hot reload, REPL, console init).
111
- * Defaults to `resolveRuntimeMode() !== 'production'` (NODE_ENV-based).
112
- */
113
- enableDevTools?: boolean;
114
- /**
115
- * Maximum HTTP buffer size for socket.io. Defaults to 5 MB. Adjust if you
116
- * stream large payloads through sockets.
117
- */
118
- maxHttpBufferSize?: number;
119
- /** Fail boot if no DeployConfig has been registered. Default: false. */
120
- requireDeployConfig?: boolean;
121
- /** Fail boot if no ServicesConfig has been registered. Default: false. */
122
- requireServicesConfig?: boolean;
123
- /** Fail boot if no OAuth providers have been registered. Default: false. */
124
- requireOAuthProviders?: boolean;
125
- /**
126
- * Dynamic-import callback for production runtime maps. When provided, the
127
- * framework registers its built-in `RuntimeMapsProvider` and the consumer
128
- * no longer needs a hand-rolled `server/prod/runtimeMaps.ts`. Pass a
129
- * function that calls `import()` with a path relative to the consumer's
130
- * server module — the framework cannot resolve that path on the
131
- * consumer's behalf because dynamic-import resolution is module-scoped.
132
- *
133
- * @example
134
- * loadGeneratedMaps: (preset) => import(`./prod/generatedApis.${preset}`)
135
- */
136
- loadGeneratedMaps?: (preset: string) => Promise<unknown>;
137
- /**
138
- * Override the preset(s) to load (skips argv lookup). Pass a single preset
139
- * name or an array for multi-preset boots. Default: the first positional
140
- * argv arg parsed by `@luckystack/server/parseArgv`, falling back to
141
- * `'default'`.
142
- */
143
- runtimeMapsPreset?: string | string[];
144
- }
145
- interface StopLuckyStackServerOptions {
146
- /**
147
- * Why the server is stopping — threaded into the `preServerStop` hook payload.
148
- * Defaults to `'manual'` for a programmatic `stop()`; the signal handlers pass
149
- * the received signal name.
150
- */
151
- reason?: 'SIGTERM' | 'SIGINT' | 'SIGHUP' | 'manual';
152
- /**
153
- * Soft budget (ms) for the whole shutdown sequence — the `preServerStop` hook
154
- * fan-out, the bounded `flushErrorTrackers()` race, and closing the
155
- * http/io/redis-adapter handles. Each step is wrapped so a single hanging step
156
- * cannot stall the others past this deadline. Defaults to `10000`.
157
- */
158
- timeoutMs?: number;
159
- }
160
- interface AfterListenOptions {
161
- /**
162
- * Re-throw instead of swallowing. Use only when the process is genuinely
163
- * useless without this task. Default `false` — a listening server survives a
164
- * failing post-listen task.
165
- */
166
- fatal?: boolean;
167
- /** Label used in the failure log. Defaults to `post-listen task`. */
168
- label?: string;
169
- }
170
- interface RunningLuckyStackServer {
171
- httpServer: Server;
172
- ioServer: Server$1;
173
- /**
174
- * Start listening. Resolves when the HTTP server is ready. Logs the bound
175
- * URL on success.
176
- */
177
- listen: (callback?: () => void) => Promise<Server>;
178
- /**
179
- * Run work that belongs AFTER the server is accepting traffic — durable queue
180
- * workers, warm-up passes, reconciliation jobs.
181
- *
182
- * A rejection here is logged loudly and swallowed by default, because the
183
- * server is already up: killing a healthy process over one broken dependency
184
- * turns an outage into a crash-loop (supervisor restarts, dependency is still
185
- * down, repeat). Pass `{ fatal: true }` when the process genuinely has no
186
- * purpose without the task — the rejection then propagates to the caller.
187
- *
188
- * Never resolves to the task's value; it is fire-and-report, not a pipeline
189
- * stage. Read what you need inside the callback.
190
- */
191
- afterListen: (task: () => void | Promise<void>, options?: AfterListenOptions) => Promise<void>;
192
- /**
193
- * Graceful shutdown (MIS-016). Stops accepting new connections, dispatches the
194
- * core `preServerStop` hook, flushes error-trackers (bounded by a timeout
195
- * race), and closes the Socket.io server, the HTTP server, and the
196
- * Redis-adapter pub/sub clients. Every step is isolated + time-bounded so one
197
- * failing/hanging step cannot hang the whole shutdown. Idempotent — a second
198
- * call returns the in-flight shutdown promise. Resolves once shutdown
199
- * completes (or the timeout forces it to finish).
200
- */
201
- stop: (options?: StopLuckyStackServerOptions) => Promise<void>;
202
- /** Alias of {@link stop} matching the `net.Server`-style `close({ timeoutMs })` API. */
203
- close: (options?: StopLuckyStackServerOptions) => Promise<void>;
204
- }
205
-
206
- declare const createLuckyStackServer: (options?: CreateLuckyStackServerOptions) => Promise<RunningLuckyStackServer>;
207
-
208
- interface BootstrapLuckyStackOptions extends CreateLuckyStackServerOptions {
209
- /**
210
- * Folder name (relative to project root) that contains the overlay files.
211
- * Default: `luckystack`. Each subfolder mirrors a framework package and
212
- * holds the project's overrides for that package.
213
- */
214
- overlayRoot?: string;
215
- /**
216
- * Skip auto-loading the overlay folder. Useful for tests that build the
217
- * registries by hand.
218
- */
219
- skipOverlayLoad?: boolean;
220
- }
221
- declare const OVERLAY_ORDER: string[];
222
- type OverlayLoader = () => Promise<void>;
223
- declare const registerOverlayLoader: (loader: OverlayLoader) => void;
224
- interface OverlayEntries {
225
- /** Files to import, in load order: `index.*` first, then the rest alphabetically. */
226
- fileNames: string[];
227
- /**
228
- * Subdirectories the flat walk skipped, minus conventional test folders.
229
- * The caller warns about these — silently dropping them meant overlay code
230
- * one level down simply never ran, with nothing to point at.
231
- */
232
- ignoredDirectoryNames: string[];
233
- }
234
- /**
235
- * Decide what a single overlay package folder contributes. Exported because
236
- * both the runtime walk here and `scripts/bundleServer.mjs` must agree exactly:
237
- * a file the bundler includes but the loader skips (or the reverse) is a
238
- * dev/prod divergence, which is the defect class ADR 0046 exists for.
239
- */
240
- declare const collectOverlayEntries: (packageDir: string) => OverlayEntries;
241
- declare const bootstrapLuckyStack: (options?: BootstrapLuckyStackOptions) => Promise<RunningLuckyStackServer>;
242
-
243
- interface BootstrapRequirements {
244
- /**
245
- * If true, require a deploy config to have been registered. Defaults to
246
- * false because single-instance deployments don't need one.
247
- */
248
- requireDeployConfig?: boolean;
249
- /**
250
- * If true, require a services config to have been registered. Defaults to
251
- * false; only needed when running the router.
252
- */
253
- requireServicesConfig?: boolean;
254
- /**
255
- * If true, require an OAuth provider list to have been registered.
256
- * Defaults to false (an app may use credentials only).
257
- */
258
- requireOAuthProviders?: boolean;
259
- }
260
- declare const verifyBootstrap: (requirements?: BootstrapRequirements) => Promise<void>;
261
-
262
- type RuntimeMapRecord = Record<string, unknown>;
263
- /**
264
- * The shape that the generated maps module must export. `scripts/generateServerRequests.ts`
265
- * emits exactly this shape: `{ apis, syncs, functions }` where each value is a
266
- * `Record<string, Handler>`. Consumers can import this type to validate their
267
- * generated file in CI or type-check the `loadGenerated` callback return.
268
- */
269
- interface GeneratedRuntimeMapsModule {
270
- apis: RuntimeMapRecord;
271
- syncs: RuntimeMapRecord;
272
- functions: RuntimeMapRecord;
273
- }
274
- interface ProdRuntimeMapsLoaderOptions {
275
- /**
276
- * Dynamic-import callback for the generated maps module of a given preset.
277
- * Called once per preset per process lifetime; the result is cached.
278
- *
279
- * The resolved module must have shape `{ apis, syncs, functions }` (the
280
- * shape `scripts/generateServerRequests.ts` emits — see
281
- * {@link GeneratedRuntimeMapsModule}). A mis-shaped module logs a warning
282
- * and results in every route for that preset returning `notFound`.
283
- *
284
- * Pass a function that calls `import()` with a path relative to YOUR
285
- * server-side module — the framework cannot resolve a relative path on
286
- * your behalf because dynamic-import resolution is module-scoped.
287
- *
288
- * @example
289
- * loadGenerated: (preset) => import(`./prod/generatedApis.${preset}`)
290
- */
291
- loadGenerated: (preset: string) => Promise<unknown>;
292
- /**
293
- * Override the preset(s) to load. Skips the argv lookup. Accepts a single
294
- * preset name or an array. Useful in tests or when the preset list comes
295
- * from a non-argv source.
296
- */
297
- preset?: string | string[];
298
- }
299
- declare const createProdRuntimeMapsProvider: (options: ProdRuntimeMapsLoaderOptions) => RuntimeMapsProvider;
300
- /**
301
- * Convenience wrapper around `createProdRuntimeMapsProvider` +
302
- * `registerRuntimeMapsProvider`. Most consumers want this — pass the
303
- * loader callback once and the runtime is wired.
304
- */
305
- declare const registerProdRuntimeMapsProvider: (options: ProdRuntimeMapsLoaderOptions) => RuntimeMapsProvider;
306
-
307
- interface ParsedServerArgv {
308
- bundles: string[];
309
- port: number | null;
310
- }
311
- declare const parseServerArgv: (argv: string[]) => ParsedServerArgv;
312
- declare const applyServerArgv: () => void;
313
- declare const getParsedBundles: () => string[];
314
- declare const getParsedPort: () => number | null;
315
-
316
- interface RegisterCustomRouteOptions {
317
- /** Pipeline phase the handler runs in. Defaults to `'post-params'`. */
318
- phase?: CustomRoutePhase;
319
- }
320
- declare const registerCustomRoute: (handler: CustomRouteHandler, options?: RegisterCustomRouteOptions) => void;
321
- declare const getCustomRoutes: () => readonly CustomRouteHandler[];
322
- declare const getPreParamsCustomRoutes: () => readonly CustomRouteHandler[];
323
- declare const clearCustomRoutes: () => void;
324
-
325
- interface OriginExemptMatcher {
326
- /**
327
- * A route is exempt when its path equals this prefix OR continues past it on a
328
- * path-SEGMENT boundary (i.e. `<prefix>/...`). Matching is boundary-aware so a
329
- * prefix can't bleed into a sibling route — `/webhooks` exempts `/webhooks` and
330
- * `/webhooks/stripe` but NOT `/webhooksadmin`.
331
- */
332
- pathPrefix: string;
333
- }
334
- declare const registerOriginExemptPath: (matcher: OriginExemptMatcher) => void;
335
- declare const getOriginExemptPaths: () => readonly OriginExemptMatcher[];
336
- declare const clearOriginExemptPaths: () => void;
337
- declare const isOriginExemptPath: (routePath: string) => boolean;
338
-
339
- type SecurityHeadersBuilder = (req: IncomingMessage) => Record<string, string> | null | undefined;
340
- /**
341
- * Register a custom security-headers builder. Called for every HTTP
342
- * request; return a plain object that gets merged on top of the framework
343
- * defaults. Use for Content-Security-Policy, HSTS, Permissions-Policy.
344
- *
345
- * Last-write-wins: subsequent calls replace the previous builder. Pass
346
- * `null` to unregister.
347
- */
348
- declare const registerSecurityHeaders: (builder: SecurityHeadersBuilder | null) => void;
349
- /** Read the active builder (or null). */
350
- declare const getSecurityHeadersBuilder: () => SecurityHeadersBuilder | null;
351
-
352
- export { type AfterListenOptions, type BootstrapLuckyStackOptions, type BootstrapRequirements, type CreateLuckyStackServerOptions, type CustomRouteHandler, type CustomRoutePhase, type FaviconHandler, type GeneratedRuntimeMapsModule, OVERLAY_ORDER, type OnLocationUpdatePayload, type OnSocketConnectPayload, type OnSocketDisconnectPayload, type OriginExemptMatcher, type OverlayEntries, type ParsedServerArgv, type PostRoomJoinPayload, type PostRoomLeavePayload, type PreRoomJoinPayload, type PreRoomLeavePayload, type ProdRuntimeMapsLoaderOptions, type RegisterCustomRouteOptions, type RouteContext, type RunningLuckyStackServer, type SecurityHeadersBuilder, type StaticFileHandler, type StopLuckyStackServerOptions, applyServerArgv, bootstrapLuckyStack, clearCustomRoutes, clearOriginExemptPaths, collectOverlayEntries, createLuckyStackServer, createProdRuntimeMapsProvider, getCustomRoutes, getOriginExemptPaths, getParsedBundles, getParsedPort, getPreParamsCustomRoutes, getSecurityHeadersBuilder, isOriginExemptPath, parseServerArgv, registerCustomRoute, registerOriginExemptPath, registerOverlayLoader, registerProdRuntimeMapsProvider, registerSecurityHeaders, verifyBootstrap };
1
+ import './hookPayloads.js';
2
+ export { createLuckyStackServer } from './createServer.js';
3
+ export type { OnSocketConnectPayload, OnSocketDisconnectPayload, PreRoomJoinPayload, PostRoomJoinPayload, PreRoomLeavePayload, PostRoomLeavePayload, OnLocationUpdatePayload, } from './hookPayloads.js';
4
+ export { bootstrapLuckyStack, registerOverlayLoader, OVERLAY_ORDER, collectOverlayEntries } from './bootstrap.js';
5
+ export type { OverlayEntries } from './bootstrap.js';
6
+ export type { BootstrapLuckyStackOptions } from './bootstrap.js';
7
+ export { verifyBootstrap } from './verifyBootstrap.js';
8
+ export type { BootstrapRequirements } from './verifyBootstrap.js';
9
+ export { createProdRuntimeMapsProvider, registerProdRuntimeMapsProvider, } from './runtimeMapsLoader.js';
10
+ export type { ProdRuntimeMapsLoaderOptions, GeneratedRuntimeMapsModule } from './runtimeMapsLoader.js';
11
+ export { applyServerArgv, parseServerArgv, getParsedBundles, getParsedPort, } from './argv.js';
12
+ export type { ParsedServerArgv } from './argv.js';
13
+ export { registerCustomRoute, getCustomRoutes, getPreParamsCustomRoutes, clearCustomRoutes, } from './customRoutesRegistry.js';
14
+ export type { RegisterCustomRouteOptions } from './customRoutesRegistry.js';
15
+ export { registerOriginExemptPath, getOriginExemptPaths, clearOriginExemptPaths, isOriginExemptPath, } from './originExemptRegistry.js';
16
+ export type { OriginExemptMatcher } from './originExemptRegistry.js';
17
+ export { registerSecurityHeaders, getSecurityHeadersBuilder, } from './securityHeadersRegistry.js';
18
+ export type { SecurityHeadersBuilder } from './securityHeadersRegistry.js';
19
+ export { registerErrorFormatter, getErrorFormatter, } from './errorFormatterRegistry.js';
20
+ export type { ErrorFormatter, ErrorFormatterContext } from './errorFormatterRegistry.js';
21
+ export type { AfterListenOptions, CreateLuckyStackServerOptions, RunningLuckyStackServer, StopLuckyStackServerOptions, RouteContext, StaticFileHandler, FaviconHandler, CustomRouteHandler, CustomRoutePhase, } from './types.js';