@luckystack/server 0.7.5 → 0.8.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/CHANGELOG.md +14 -0
- package/CLAUDE.md +1 -1
- package/dist/index.js +33 -9
- package/dist/index.js.map +1 -1
- package/docs/create-server.md +2 -2
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-07-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Routed GET API calls can carry a lossless typed payload through the reserved `__luckystack_data` query value while public key-per-query callers remain compatible.
|
|
15
|
+
|
|
16
|
+
## [0.7.6] - 2026-07-23
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- A successfully listening server now refreshes its boot-UUID TTL every third
|
|
21
|
+
of the configured lifetime and stops the heartbeat during shutdown. Healthy
|
|
22
|
+
long-running servers therefore no longer become not-ready after one hour.
|
|
23
|
+
|
|
10
24
|
## [0.7.4] - 2026-07-22
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/CLAUDE.md
CHANGED
|
@@ -25,7 +25,7 @@ One-call server bootstrap for a LuckyStack project. Wires together a raw Node.js
|
|
|
25
25
|
|
|
26
26
|
| Function / Export | One-liner | Deep doc |
|
|
27
27
|
|---|---|---|
|
|
28
|
-
| `createLuckyStackServer(options)` | Lower-level factory that returns `{ httpServer, ioServer, listen, stop, close }`. Wires HTTP + Socket.io + framework routes;
|
|
28
|
+
| `createLuckyStackServer(options)` | Lower-level factory that returns `{ httpServer, ioServer, listen, stop, close }`. Wires HTTP + Socket.io + framework routes; after a successful listen starts the boot-UUID TTL heartbeat, and stops it during shutdown. Installs a persistent `httpServer.on('error')` listener and (in prod) SIGTERM/SIGINT → graceful `stop()`. | -> docs/create-server.md |
|
|
29
29
|
| `RunningLuckyStackServer.stop(options?)` / `.close(options?)` | Graceful shutdown (MIS-016). Stops accepting new connections, dispatches the core `preServerStop` hook, flushes error-trackers (bounded by a per-step timeout race), then closes io + http + the Redis-adapter pub/sub clients. Each step isolated + time-bounded so one hanging step can't stall shutdown. Idempotent. `options`: `{ reason?, timeoutMs? }` (default `timeoutMs` 10000). | -> docs/create-server.md |
|
|
30
30
|
| `bootstrapLuckyStack(options)` | High-level entry: auto-imports `luckystack/<pkg>/*.ts` overlay in topological order, then delegates to `createLuckyStackServer`. An overlay file that fails to import (classic: it imports a package removed via `luckystack remove`) aborts boot with an ACTIONABLE error naming the file, instead of a raw ERR_MODULE_NOT_FOUND. | -> docs/create-server.md |
|
|
31
31
|
| `OVERLAY_ORDER` (exported const) | The canonical overlay walk order (`core, deploy, login, email, sentry, presence, cron, docs-ui, server`). Exported so the consumer's `scripts/bundleServer.mjs` imports it at build time — a hardcoded bundler copy once silently dropped the cron slot from prod; `overlayOrderParity.test.ts` pins the bundlers' fallback lists to this. | -> docs/create-server.md |
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/createServer.ts
|
|
11
11
|
import http from "http";
|
|
12
|
-
import { registerBindAddress, registerBoundAddress, writeBootUuid, getLogger as getLogger13, getProjectConfig as getProjectConfig15, tryCatch as tryCatch10, tryCatchSync as
|
|
12
|
+
import { registerBindAddress, registerBoundAddress, writeBootUuid, startBootUuidHeartbeat, getLogger as getLogger13, getProjectConfig as getProjectConfig15, tryCatch as tryCatch10, tryCatchSync as tryCatchSync3, resolveEnvKey as resolveEnvKey5, dispatchHook as dispatchHook8 } from "@luckystack/core";
|
|
13
13
|
|
|
14
14
|
// src/httpHandler.ts
|
|
15
15
|
import { randomUUID } from "crypto";
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
normalizeOrigin,
|
|
25
25
|
readSession as readSession3,
|
|
26
26
|
tryCatch as tryCatch7,
|
|
27
|
-
tryCatchSync
|
|
27
|
+
tryCatchSync as tryCatchSync2
|
|
28
28
|
} from "@luckystack/core";
|
|
29
29
|
|
|
30
30
|
// src/logSanitize.ts
|
|
@@ -921,7 +921,8 @@ import {
|
|
|
921
921
|
dispatchHook as dispatchHook3,
|
|
922
922
|
extractTokenFromRequest,
|
|
923
923
|
getLogger as getLogger4,
|
|
924
|
-
tryCatch as tryCatch4
|
|
924
|
+
tryCatch as tryCatch4,
|
|
925
|
+
tryCatchSync
|
|
925
926
|
} from "@luckystack/core";
|
|
926
927
|
import { handleHttpApiRequest } from "@luckystack/api";
|
|
927
928
|
|
|
@@ -1038,8 +1039,25 @@ var handleApiRoute = async ({
|
|
|
1038
1039
|
res.end(JSON.stringify(response));
|
|
1039
1040
|
return true;
|
|
1040
1041
|
}
|
|
1041
|
-
|
|
1042
|
+
let apiData = typeof params === "object" ? { ...params } : {};
|
|
1042
1043
|
delete apiData.stream;
|
|
1044
|
+
const routedData = apiData.__luckystack_data;
|
|
1045
|
+
if (typeof routedData === "string") {
|
|
1046
|
+
const [parseError, parsed] = tryCatchSync(() => JSON.parse(routedData));
|
|
1047
|
+
if (parseError || parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1048
|
+
const response = {
|
|
1049
|
+
status: "error",
|
|
1050
|
+
httpStatus: 400,
|
|
1051
|
+
message: "api.invalidRequestFormat",
|
|
1052
|
+
errorCode: "api.invalidRequestFormat"
|
|
1053
|
+
};
|
|
1054
|
+
res.setHeader("Content-Type", "application/json");
|
|
1055
|
+
res.writeHead(400);
|
|
1056
|
+
res.end(JSON.stringify(response));
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
apiData = { ...parsed };
|
|
1060
|
+
}
|
|
1043
1061
|
const requesterIp = resolveRequesterIp(req);
|
|
1044
1062
|
const result = await handleHttpApiRequest({
|
|
1045
1063
|
name: apiName,
|
|
@@ -1390,7 +1408,7 @@ var setSecurityHeaders = (req, res, origin) => {
|
|
|
1390
1408
|
res.setHeader("X-Content-Type-Options", securityHeaders.contentTypeOptions);
|
|
1391
1409
|
const builder = getSecurityHeadersBuilder();
|
|
1392
1410
|
if (builder) {
|
|
1393
|
-
const [error] =
|
|
1411
|
+
const [error] = tryCatchSync2(() => {
|
|
1394
1412
|
const custom = builder(req);
|
|
1395
1413
|
if (custom) {
|
|
1396
1414
|
for (const [name, value] of Object.entries(custom)) {
|
|
@@ -1509,7 +1527,7 @@ var handleHttpRequestInner = async (req, res, options) => {
|
|
|
1509
1527
|
);
|
|
1510
1528
|
const url = req.url ?? "/";
|
|
1511
1529
|
const [routePathRaw, queryStringRaw] = url.split("?");
|
|
1512
|
-
const [decodeError, decodedPath] =
|
|
1530
|
+
const [decodeError, decodedPath] = tryCatchSync2(() => decodeURIComponent(routePathRaw ?? "/"));
|
|
1513
1531
|
if (decodeError || decodedPath === null) {
|
|
1514
1532
|
res.statusCode = 400;
|
|
1515
1533
|
res.setHeader("Content-Type", "text/plain");
|
|
@@ -2398,8 +2416,8 @@ var listenLuckyStackServer = (httpServer, ip, port, callback) => new Promise((re
|
|
|
2398
2416
|
process.once("exit", clearDevServerInfo);
|
|
2399
2417
|
}
|
|
2400
2418
|
if (attemptPort !== startPort && !production) {
|
|
2401
|
-
const configuredCallbackBase =
|
|
2402
|
-
const callbackPort =
|
|
2419
|
+
const configuredCallbackBase = tryCatchSync3(() => getProjectConfig15().oauthCallbackBase)[1] ?? "";
|
|
2420
|
+
const callbackPort = tryCatchSync3(() => {
|
|
2403
2421
|
const callbackUrl = new URL(configuredCallbackBase);
|
|
2404
2422
|
return callbackUrl.port || (callbackUrl.protocol === "https:" ? "443" : "80");
|
|
2405
2423
|
})[1] ?? "";
|
|
@@ -2460,9 +2478,15 @@ var createLuckyStackServer = async (options = {}) => {
|
|
|
2460
2478
|
const { io: ioServer, adapterClients } = loadSocket(httpServer, {
|
|
2461
2479
|
maxHttpBufferSize: options.maxHttpBufferSize
|
|
2462
2480
|
});
|
|
2463
|
-
|
|
2481
|
+
let bootUuidHeartbeat = null;
|
|
2482
|
+
const listen = async (callback) => {
|
|
2483
|
+
const listeningServer = await listenLuckyStackServer(httpServer, ip, port, callback);
|
|
2484
|
+
bootUuidHeartbeat ??= startBootUuidHeartbeat();
|
|
2485
|
+
return listeningServer;
|
|
2486
|
+
};
|
|
2464
2487
|
let shutdownPromise = null;
|
|
2465
2488
|
const stop = (stopOptions = {}) => {
|
|
2489
|
+
bootUuidHeartbeat?.stop();
|
|
2466
2490
|
shutdownPromise ??= runGracefulShutdown({ httpServer, ioServer, adapterClients }, stopOptions);
|
|
2467
2491
|
return shutdownPromise;
|
|
2468
2492
|
};
|