tina4-nodejs 3.13.92 → 3.13.95
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/CLAUDE.md +170 -28
- package/README.md +2 -2
- package/package.json +13 -9
- package/packages/cli/dist/bin.js +33126 -30055
- package/packages/cli/src/commands/metrics.ts +17 -11
- package/packages/cli/src/commands/serve.ts +10 -9
- package/packages/core/dist/index.js +33062 -29908
- package/packages/core/src/ai.ts +7 -1
- package/packages/core/src/auth.ts +191 -39
- package/packages/core/src/background.ts +19 -19
- package/packages/core/src/cache.ts +492 -49
- package/packages/core/src/devAdmin.ts +79 -32
- package/packages/core/src/devMailbox.ts +20 -44
- package/packages/core/src/dispatchPipeline.ts +285 -0
- package/packages/core/src/dotenv.ts +185 -40
- package/packages/core/src/index.ts +7 -6
- package/packages/core/src/logger.ts +257 -36
- package/packages/core/src/mcp.ts +1 -1
- package/packages/core/src/messenger.ts +81 -13
- package/packages/core/src/metrics.ts +199 -961
- package/packages/core/src/middleware.ts +390 -123
- package/packages/core/src/queue.ts +188 -32
- package/packages/core/src/queueBackends/kafkaBackend.ts +109 -13
- package/packages/core/src/queueBackends/liteBackend.ts +13 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +101 -9
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +22 -4
- package/packages/core/src/rateLimiter.ts +10 -5
- package/packages/core/src/request.ts +6 -9
- package/packages/core/src/response.ts +46 -1
- package/packages/core/src/router.ts +29 -4
- package/packages/core/src/server.ts +751 -414
- package/packages/core/src/session.ts +244 -27
- package/packages/core/src/sessionHandlers/childError.ts +72 -0
- package/packages/core/src/sessionHandlers/databaseHandler.ts +338 -48
- package/packages/core/src/sessionHandlers/memcachedHandler.ts +181 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +293 -202
- package/packages/core/src/sessionHandlers/mongoHandler.ts +88 -8
- package/packages/core/src/sessionHandlers/respClient.ts +16 -143
- package/packages/core/src/sessionHandlers/sqlClient.ts +290 -0
- package/packages/core/src/sessionHandlers/syncBridge.ts +190 -0
- package/packages/core/src/sessionHandlers/syncSocket.ts +236 -0
- package/packages/core/src/testClient.ts +18 -5
- package/packages/core/src/trustedProxy.ts +249 -0
- package/packages/core/src/types.ts +29 -5
- package/packages/core/src/websocket.ts +66 -0
- package/packages/frond/dist/index.js +74 -31
- package/packages/frond/src/engine.ts +99 -33
- package/packages/orm/dist/index.js +26554 -23400
- package/packages/orm/src/adapters/firebird.ts +183 -56
- package/packages/orm/src/adapters/mongodb.ts +25 -4
- package/packages/orm/src/adapters/mssql.ts +114 -29
- package/packages/orm/src/adapters/mysql.ts +103 -40
- package/packages/orm/src/adapters/odbc.ts +44 -21
- package/packages/orm/src/adapters/postgres.ts +118 -26
- package/packages/orm/src/adapters/sqlDialect.ts +120 -0
- package/packages/orm/src/adapters/sqlite.ts +64 -25
- package/packages/orm/src/baseModel.ts +135 -40
- package/packages/orm/src/cachedDatabase.ts +43 -19
- package/packages/orm/src/connectTimeout.ts +265 -0
- package/packages/orm/src/database.ts +338 -198
- package/packages/orm/src/databaseResult.ts +65 -13
- package/packages/orm/src/databaseUrl.ts +484 -0
- package/packages/orm/src/docstore.ts +386 -145
- package/packages/orm/src/index.ts +13 -3
- package/packages/orm/src/migration.ts +18 -3
- package/packages/orm/src/queryBuilder.ts +38 -4
- package/packages/orm/src/sqlTranslator.ts +310 -4
- package/packages/orm/src/types.ts +15 -4
- package/types/cli/src/bin.d.ts +92 -0
- package/types/cli/src/commands/build.d.ts +2 -0
- package/types/cli/src/commands/generate.d.ts +47 -0
- package/types/cli/src/commands/init.d.ts +1 -0
- package/types/cli/src/commands/metrics.d.ts +6 -0
- package/types/cli/src/commands/migrate.d.ts +1 -0
- package/types/cli/src/commands/migrateCreate.d.ts +1 -0
- package/types/cli/src/commands/migrateRollback.d.ts +1 -0
- package/types/cli/src/commands/migrateStatus.d.ts +1 -0
- package/types/cli/src/commands/queue.d.ts +20 -0
- package/types/cli/src/commands/routes.d.ts +1 -0
- package/types/cli/src/commands/seed.d.ts +1 -0
- package/types/cli/src/commands/serve.d.ts +6 -0
- package/types/cli/src/commands/test.d.ts +1 -0
- package/types/core/src/ai.d.ts +64 -0
- package/types/core/src/api.d.ts +262 -0
- package/types/core/src/auth.d.ts +177 -0
- package/types/core/src/authGate.d.ts +20 -0
- package/types/core/src/background.d.ts +34 -0
- package/types/core/src/cache.d.ts +163 -0
- package/types/core/src/constants.d.ts +38 -0
- package/types/core/src/container.d.ts +44 -0
- package/types/core/src/context/chunker.d.ts +31 -0
- package/types/core/src/context/index.d.ts +93 -0
- package/types/core/src/devAdmin.d.ts +179 -0
- package/types/core/src/devMailbox.d.ts +54 -0
- package/types/core/src/dispatchPipeline.d.ts +117 -0
- package/types/core/src/docs.d.ts +141 -0
- package/types/core/src/docsAutoDiscovery.d.ts +6 -0
- package/types/core/src/dotenv.d.ts +87 -0
- package/types/core/src/env.d.ts +28 -0
- package/types/core/src/errorOverlay.d.ts +36 -0
- package/types/core/src/events.d.ts +75 -0
- package/types/core/src/fakeData.d.ts +55 -0
- package/types/core/src/feedback.d.ts +90 -0
- package/types/core/src/graphql.d.ts +207 -0
- package/types/core/src/health.d.ts +22 -0
- package/types/core/src/htmlElement.d.ts +75 -0
- package/types/core/src/i18n.d.ts +37 -0
- package/types/core/src/index.d.ts +92 -0
- package/types/core/src/job.d.ts +39 -0
- package/types/core/src/logger.d.ts +200 -0
- package/types/core/src/mcp.d.ts +248 -0
- package/types/core/src/messenger.d.ts +191 -0
- package/types/core/src/metrics.d.ts +41 -0
- package/types/core/src/middleware.d.ts +330 -0
- package/types/core/src/mqtt.d.ts +257 -0
- package/types/core/src/mqttMessage.d.ts +67 -0
- package/types/core/src/plan.d.ts +96 -0
- package/types/core/src/projectIndex.d.ts +56 -0
- package/types/core/src/queue.d.ts +268 -0
- package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
- package/types/core/src/queueBackends/liteBackend.d.ts +128 -0
- package/types/core/src/queueBackends/mongoBackend.d.ts +119 -0
- package/types/core/src/queueBackends/rabbitmqBackend.d.ts +55 -0
- package/types/core/src/rateLimiter.d.ts +49 -0
- package/types/core/src/request.d.ts +25 -0
- package/types/core/src/response.d.ts +28 -0
- package/types/core/src/routeDiscovery.d.ts +12 -0
- package/types/core/src/router.d.ts +366 -0
- package/types/core/src/scss.d.ts +19 -0
- package/types/core/src/server.d.ts +146 -0
- package/types/core/src/service.d.ts +115 -0
- package/types/core/src/session.d.ts +341 -0
- package/types/core/src/sessionHandlers/childError.d.ts +34 -0
- package/types/core/src/sessionHandlers/databaseHandler.d.ts +97 -0
- package/types/core/src/sessionHandlers/memcachedHandler.d.ts +60 -0
- package/types/core/src/sessionHandlers/mongoClient.d.ts +35 -0
- package/types/core/src/sessionHandlers/mongoHandler.d.ts +109 -0
- package/types/core/src/sessionHandlers/respClient.d.ts +22 -0
- package/types/core/src/sessionHandlers/sqlClient.d.ts +39 -0
- package/types/core/src/sessionHandlers/syncBridge.d.ts +91 -0
- package/types/core/src/sessionHandlers/syncSocket.d.ts +49 -0
- package/types/core/src/sessionHandlers/valkeyHandler.d.ts +65 -0
- package/types/core/src/static.d.ts +2 -0
- package/types/core/src/test.d.ts +94 -0
- package/types/core/src/testClient.d.ts +36 -0
- package/types/core/src/testing.d.ts +58 -0
- package/types/core/src/trustedProxy.d.ts +44 -0
- package/types/core/src/types.d.ts +242 -0
- package/types/core/src/validator.d.ts +52 -0
- package/types/core/src/websocket.d.ts +402 -0
- package/types/core/src/websocketBackplane.d.ts +166 -0
- package/types/core/src/websocketConnection.d.ts +54 -0
- package/types/core/src/wsdl.d.ts +101 -0
- package/types/frond/src/engine.d.ts +263 -0
- package/types/frond/src/index.d.ts +2 -0
- package/types/orm/src/adapters/firebird.d.ts +183 -0
- package/types/orm/src/adapters/mongodb.d.ts +81 -0
- package/types/orm/src/adapters/mssql.d.ts +77 -0
- package/types/orm/src/adapters/mysql.d.ts +67 -0
- package/types/orm/src/adapters/odbc.d.ts +94 -0
- package/types/orm/src/adapters/postgres.d.ts +86 -0
- package/types/orm/src/adapters/sqlDialect.d.ts +71 -0
- package/types/orm/src/adapters/sqlite.d.ts +68 -0
- package/types/orm/src/autoCrud.d.ts +73 -0
- package/types/orm/src/baseModel.d.ts +427 -0
- package/types/orm/src/cachedDatabase.d.ts +190 -0
- package/types/orm/src/connectTimeout.d.ts +100 -0
- package/types/orm/src/database.d.ts +655 -0
- package/types/orm/src/databaseResult.d.ts +109 -0
- package/types/orm/src/databaseUrl.d.ts +125 -0
- package/types/orm/src/docstore.d.ts +241 -0
- package/types/orm/src/fakeData.d.ts +22 -0
- package/types/orm/src/index.d.ts +43 -0
- package/types/orm/src/migration.d.ts +275 -0
- package/types/orm/src/model.d.ts +7 -0
- package/types/orm/src/query.d.ts +14 -0
- package/types/orm/src/queryBuilder.d.ts +193 -0
- package/types/orm/src/realtime/index.d.ts +7 -0
- package/types/orm/src/realtime/models/attachment.d.ts +43 -0
- package/types/orm/src/realtime/models/channel.d.ts +32 -0
- package/types/orm/src/realtime/models/channelMember.d.ts +32 -0
- package/types/orm/src/realtime/models/message.d.ts +36 -0
- package/types/orm/src/realtime/models/workspace.d.ts +26 -0
- package/types/orm/src/realtime/realtime.d.ts +24 -0
- package/types/orm/src/realtime/storage.d.ts +61 -0
- package/types/orm/src/seeder.d.ts +118 -0
- package/types/orm/src/sqlTranslator.d.ts +258 -0
- package/types/orm/src/types.d.ts +148 -0
- package/types/orm/src/validation.d.ts +6 -0
- package/types/swagger/src/generator.d.ts +46 -0
- package/types/swagger/src/index.d.ts +2 -0
- package/types/swagger/src/ui.d.ts +11 -0
- package/packages/core/src/sessionHandlers/redisHandler.ts +0 -206
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { Tina4Config } from "./types.js";
|
|
3
|
+
import { Router } from "./router.js";
|
|
4
|
+
/** How long a graceful shutdown waits for in-flight requests, in seconds. */
|
|
5
|
+
export declare const DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 30;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the shutdown budget from `TINA4_SHUTDOWN_TIMEOUT` (seconds).
|
|
8
|
+
*
|
|
9
|
+
* 30s matches Kubernetes' default `terminationGracePeriodSeconds` and
|
|
10
|
+
* Gunicorn's `graceful_timeout`, so the drain finishes just BEFORE the
|
|
11
|
+
* orchestrator's SIGKILL rather than being truncated by it. Same env var and
|
|
12
|
+
* same default as tina4-ruby's `Tina4::Shutdown`.
|
|
13
|
+
*
|
|
14
|
+
* A non-numeric or negative value falls back to the default rather than
|
|
15
|
+
* silently disabling the drain - a typo must not turn shutdown into a
|
|
16
|
+
* zero-second force-kill.
|
|
17
|
+
*/
|
|
18
|
+
export declare function shutdownTimeoutSeconds(): number;
|
|
19
|
+
/**
|
|
20
|
+
* Build the startup banner's optional surface lines (issue #99).
|
|
21
|
+
*
|
|
22
|
+
* Only advertise a surface that is actually REACHABLE. In production, or with
|
|
23
|
+
* TINA4_DEBUG off, /swagger and /__dev return 404 -- printing them anyway both
|
|
24
|
+
* misleads an operator into believing a dev surface is exposed and sends a
|
|
25
|
+
* developer to a dead link.
|
|
26
|
+
*
|
|
27
|
+
* Kept as a pure function of (port, two booleans) so the contract is unit
|
|
28
|
+
* testable without booting a server and grepping stdout. Parity: Python
|
|
29
|
+
* banner_surface_lines, PHP App::bannerSurfaceLines, Ruby
|
|
30
|
+
* Tina4.banner_surface_lines.
|
|
31
|
+
*
|
|
32
|
+
* @returns [swaggerLine, dashboardLine] -- each empty, or a newline plus the
|
|
33
|
+
* banner row, ready to interpolate.
|
|
34
|
+
*/
|
|
35
|
+
export declare function bannerSurfaceLines(port: number, opts: {
|
|
36
|
+
swaggerEnabled: boolean;
|
|
37
|
+
devAdminEnabled: boolean;
|
|
38
|
+
}): [string, string];
|
|
39
|
+
/**
|
|
40
|
+
* Apply pending DB migrations on startup — NON-BREAKING.
|
|
41
|
+
*
|
|
42
|
+
* When a `migrations/` folder exists (with at least one `.sql` file, excluding
|
|
43
|
+
* `.down.sql`) and `TINA4_AUTO_MIGRATE` is not disabled (default "true";
|
|
44
|
+
* false/0/no/off disable), pending migrations are applied during boot so the
|
|
45
|
+
* schema is current with no manual `tina4 migrate` step. A failure here is
|
|
46
|
+
* logged LOUD via `Log.error` and the service STILL starts — a bad migration
|
|
47
|
+
* must never take the backend down. (The explicit `tina4 migrate` CLI stays
|
|
48
|
+
* fail-fast so CI still gets a non-zero exit. Only this startup hook swallows.)
|
|
49
|
+
*
|
|
50
|
+
* Disable with `TINA4_AUTO_MIGRATE=false` — e.g. multi-instance production that
|
|
51
|
+
* migrates as a separate deploy step (concurrent first-apply can race).
|
|
52
|
+
*
|
|
53
|
+
* @param migrationDir - migrations directory (default "migrations", relative to base)
|
|
54
|
+
* @param base - project root used to resolve the migrations directory
|
|
55
|
+
*/
|
|
56
|
+
export declare function autoMigrateOnStartup(migrationDir?: string, base?: string): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Refuse to boot if pre-3.12 un-prefixed env vars are still set.
|
|
59
|
+
*
|
|
60
|
+
* Tina4 v3.12 hard-renamed every framework-specific env var to use the
|
|
61
|
+
* `TINA4_` prefix. Booting silently with a legacy `DATABASE_URL` or
|
|
62
|
+
* `SECRET` would let auth, DB, or mail fall back to insecure defaults
|
|
63
|
+
* while the user thought their config was being read. Better to die
|
|
64
|
+
* loudly with a list of names to fix.
|
|
65
|
+
*
|
|
66
|
+
* Bypass with `TINA4_ALLOW_LEGACY_ENV=true` in CI / migration scripts
|
|
67
|
+
* that genuinely need both names set during a transition window.
|
|
68
|
+
*/
|
|
69
|
+
export declare function _checkLegacyEnvVars(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve port and host with priority: explicit config > ENV var > default.
|
|
72
|
+
* Exported for testability.
|
|
73
|
+
*
|
|
74
|
+
* Host resolution prefers `TINA4_HOST` (the framework-prefixed name —
|
|
75
|
+
* matches Python parity) and falls back to the unprefixed `HOST` env var
|
|
76
|
+
* for backwards compatibility.
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolvePortAndHost(config?: {
|
|
79
|
+
port?: number;
|
|
80
|
+
host?: string;
|
|
81
|
+
}): {
|
|
82
|
+
port: number;
|
|
83
|
+
host: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Whether the boot banner should be suppressed. Set TINA4_SUPPRESS=true to
|
|
87
|
+
* silence the ASCII-art banner and route table on startup — useful in CI,
|
|
88
|
+
* test runners, and embedded contexts where stdout is consumed by another
|
|
89
|
+
* process.
|
|
90
|
+
*/
|
|
91
|
+
export declare function isBannerSuppressed(): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Honour TINA4_TEMPLATE_ROUTING=off|false|0|no|disabled as an explicit kill
|
|
94
|
+
* switch. Default: enabled. Drop a file in src/templates/pages/ and it serves
|
|
95
|
+
* at the matching URL — the zero-config Tina4 convention. Operators who want
|
|
96
|
+
* explicit-only routing can set TINA4_TEMPLATE_ROUTING=off and every URL
|
|
97
|
+
* must be registered via get() / post() (or be a static file).
|
|
98
|
+
*/
|
|
99
|
+
export declare function templateAutoRoutingEnabled(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Return the canonical HTTP reason phrase for `status`. Falls back to a
|
|
102
|
+
* sensible label when an exotic status is used. Never returns an empty string.
|
|
103
|
+
*/
|
|
104
|
+
export declare function httpReason(status: number): string;
|
|
105
|
+
/**
|
|
106
|
+
* Reset the production template cache. Tests use this between scenarios so
|
|
107
|
+
* a fresh scan picks up fixture files in a tmp project.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resetTemplateCache(): void;
|
|
110
|
+
/**
|
|
111
|
+
* Resolve a URL path to a template file in src/templates/pages/.
|
|
112
|
+
*
|
|
113
|
+
* Only files inside `src/templates/pages/` auto-route from a URL. Anything
|
|
114
|
+
* in `src/templates/` outside `pages/` (partials, layouts, base.twig,
|
|
115
|
+
* errors, components) is never served standalone.
|
|
116
|
+
*
|
|
117
|
+
* Dev mode: checks filesystem every time for live changes.
|
|
118
|
+
* Production: uses a cached lookup built once at startup.
|
|
119
|
+
*
|
|
120
|
+
* The whole feature can be turned off with `TINA4_TEMPLATE_ROUTING=off`.
|
|
121
|
+
*/
|
|
122
|
+
export declare function resolveTemplate(pathname: string, templatesDir: string): string | null;
|
|
123
|
+
/**
|
|
124
|
+
* Start the Tina4 HTTP server.
|
|
125
|
+
* Thin wrapper around startServer() for cross-framework parity with PHP and Ruby.
|
|
126
|
+
*/
|
|
127
|
+
export declare function start(config?: Tina4Config): Promise<{
|
|
128
|
+
close: () => void;
|
|
129
|
+
router: Router;
|
|
130
|
+
port: number;
|
|
131
|
+
}>;
|
|
132
|
+
/**
|
|
133
|
+
* Stop the running Tina4 server gracefully.
|
|
134
|
+
*/
|
|
135
|
+
export declare function stop(): void;
|
|
136
|
+
/**
|
|
137
|
+
* Dispatch a raw Node.js request through the Tina4 router and write the response.
|
|
138
|
+
* Requires startServer() to have been called first.
|
|
139
|
+
* Useful for testing and embedding.
|
|
140
|
+
*/
|
|
141
|
+
export declare function handle(rawReq: IncomingMessage, rawRes: ServerResponse): Promise<void>;
|
|
142
|
+
export declare function startServer(config?: Tina4Config): Promise<{
|
|
143
|
+
close: () => void;
|
|
144
|
+
router: Router;
|
|
145
|
+
port: number;
|
|
146
|
+
}>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export interface ServiceOptions {
|
|
2
|
+
timing?: string;
|
|
3
|
+
daemon?: boolean;
|
|
4
|
+
interval?: number;
|
|
5
|
+
maxRetries?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ServiceContext {
|
|
8
|
+
running: boolean;
|
|
9
|
+
lastRun: Date | null;
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
12
|
+
export type ServiceHandler = (context: ServiceContext) => Promise<void> | void;
|
|
13
|
+
export interface ServiceInfo {
|
|
14
|
+
name: string;
|
|
15
|
+
options: ServiceOptions;
|
|
16
|
+
running: boolean;
|
|
17
|
+
lastRun: Date | null;
|
|
18
|
+
retries: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parse a single cron field and check if the given value matches.
|
|
22
|
+
* Supports: * (every), N/n (step), N,N,N (list), N-N (range), plain number.
|
|
23
|
+
*/
|
|
24
|
+
export declare function matchCronField(field: string, value: number): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Check whether a Date matches a 5-field cron expression.
|
|
27
|
+
* Fields: minute hour dayOfMonth month dayOfWeek
|
|
28
|
+
*/
|
|
29
|
+
export declare function matchesCron(expression: string, date: Date): boolean;
|
|
30
|
+
export declare abstract class Tina4Service {
|
|
31
|
+
private _running;
|
|
32
|
+
/** Main work loop — subclasses MUST override. */
|
|
33
|
+
abstract run(): Promise<void> | void;
|
|
34
|
+
/**
|
|
35
|
+
* Signal this service to stop. The next `shouldStop()` check returns true.
|
|
36
|
+
* Override for custom shutdown behaviour but always call `super.stop()`.
|
|
37
|
+
*/
|
|
38
|
+
stop(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Returns true once `stop()` has been called. Use inside `run()` loops
|
|
41
|
+
* as the exit condition:
|
|
42
|
+
*
|
|
43
|
+
* async run() {
|
|
44
|
+
* while (!this.shouldStop()) { ... }
|
|
45
|
+
* }
|
|
46
|
+
*/
|
|
47
|
+
shouldStop(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Return a callable that ServiceRunner can register. Used by
|
|
50
|
+
* ServiceRunner.registerService under the hood.
|
|
51
|
+
*/
|
|
52
|
+
asHandler(): ServiceHandler;
|
|
53
|
+
}
|
|
54
|
+
export declare class ServiceRunner {
|
|
55
|
+
/**
|
|
56
|
+
* Register a service with a handler and options.
|
|
57
|
+
*/
|
|
58
|
+
static register(name: string, handler: ServiceHandler, options?: ServiceOptions): void;
|
|
59
|
+
/**
|
|
60
|
+
* Register a class-based service (subclass of {@link Tina4Service}) by name.
|
|
61
|
+
*
|
|
62
|
+
* Wraps the service's `run()` method as the runner's handler. Defaults
|
|
63
|
+
* to `daemon: true` because Tina4Service subclasses manage their own
|
|
64
|
+
* loop inside `run()`. Override via `options`.
|
|
65
|
+
*
|
|
66
|
+
* class EmailWorker extends Tina4Service { async run() { ... } }
|
|
67
|
+
* ServiceRunner.registerService("emails", new EmailWorker());
|
|
68
|
+
* await ServiceRunner.start();
|
|
69
|
+
*
|
|
70
|
+
* Cross-framework parity with PHP `ServiceRunner::registerService` and
|
|
71
|
+
* Ruby `Tina4::ServiceRunner.register_service`.
|
|
72
|
+
*/
|
|
73
|
+
static registerService(name: string, service: Tina4Service, options?: ServiceOptions): void;
|
|
74
|
+
/**
|
|
75
|
+
* Discover services from a directory. Each file should export
|
|
76
|
+
* { name, handler, timing?, interval?, daemon?, maxRetries? }.
|
|
77
|
+
*/
|
|
78
|
+
static discover(serviceDir?: string): Promise<ServiceInfo[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Start all registered services, or a specific one by name.
|
|
81
|
+
*/
|
|
82
|
+
static start(name?: string): void;
|
|
83
|
+
/**
|
|
84
|
+
* Stop all running services, or a specific one by name.
|
|
85
|
+
*/
|
|
86
|
+
static stop(name?: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* List all registered services with their current state.
|
|
89
|
+
*/
|
|
90
|
+
static list(): ServiceInfo[];
|
|
91
|
+
/**
|
|
92
|
+
* Check if a specific service is running.
|
|
93
|
+
*/
|
|
94
|
+
static isRunning(name: string): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Remove a service from the registry (stops it first if running).
|
|
97
|
+
*/
|
|
98
|
+
static remove(name: string): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Clear all registered services (stops them all first).
|
|
101
|
+
*/
|
|
102
|
+
static clear(): void;
|
|
103
|
+
/**
|
|
104
|
+
* Check if a 5-field cron pattern matches the given (or current) date/time.
|
|
105
|
+
*/
|
|
106
|
+
static matchCron(pattern: string, now?: Date): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Watch service files for changes and hot-reload in dev mode.
|
|
109
|
+
*/
|
|
110
|
+
static watch(serviceDir?: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Stop watching service files.
|
|
113
|
+
*/
|
|
114
|
+
static unwatch(): void;
|
|
115
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
export interface SessionConfig {
|
|
2
|
+
/** Session backend type: "file", "redis", "valkey", "mongo", "memcached", "database" (or "db") */
|
|
3
|
+
backend?: string;
|
|
4
|
+
/** File storage path (default: "data/sessions") */
|
|
5
|
+
path?: string;
|
|
6
|
+
/** Time-to-live in seconds (default: 3600) */
|
|
7
|
+
ttl?: number;
|
|
8
|
+
/** Redis host (default: "127.0.0.1") */
|
|
9
|
+
redisHost?: string;
|
|
10
|
+
/** Redis port (default: 6379) */
|
|
11
|
+
redisPort?: number;
|
|
12
|
+
/** Redis password (optional) */
|
|
13
|
+
redisPassword?: string;
|
|
14
|
+
/** Redis key prefix (default: "tina4:session:") */
|
|
15
|
+
redisPrefix?: string;
|
|
16
|
+
/** Redis database index (default: 0) */
|
|
17
|
+
redisDb?: number;
|
|
18
|
+
}
|
|
19
|
+
interface SessionData {
|
|
20
|
+
_created: number;
|
|
21
|
+
_accessed: number;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Is `sessionId` a well-formed opaque session identifier?
|
|
26
|
+
*
|
|
27
|
+
* Callers pass UNTRUSTED input here (the session cookie is attacker-chosen), so
|
|
28
|
+
* anything that is not a string of the opaque alphabet is rejected.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isValidSessionId(sessionId: unknown): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Every accepted backend name, aliases included. Byte-identical membership in
|
|
33
|
+
* all four frameworks. Written once here so the switch below and the error
|
|
34
|
+
* message cannot disagree.
|
|
35
|
+
*/
|
|
36
|
+
export declare const VALID_SESSION_BACKENDS: readonly ["file", "filesystem", "redis", "valkey", "mongodb", "mongo", "memcached", "memcache", "database", "db"];
|
|
37
|
+
/** Canonical name of each backend, for the error message (aliases omitted). */
|
|
38
|
+
export declare const CANONICAL_SESSION_BACKENDS: readonly ["file", "redis", "valkey", "mongodb", "memcached", "database"];
|
|
39
|
+
/**
|
|
40
|
+
* Base interface for session storage backends.
|
|
41
|
+
* Implementations must provide read, write, and destroy.
|
|
42
|
+
*/
|
|
43
|
+
export interface SessionHandler {
|
|
44
|
+
read(sessionId: string): SessionData | null;
|
|
45
|
+
write(sessionId: string, data: SessionData, ttl?: number): void;
|
|
46
|
+
destroy(sessionId: string): void;
|
|
47
|
+
/** Garbage-collect expired sessions. Optional — Redis/Valkey/Mongo handle TTL natively. */
|
|
48
|
+
gc?(maxLifetime: number): void;
|
|
49
|
+
}
|
|
50
|
+
export declare class FileSessionHandler implements SessionHandler {
|
|
51
|
+
private storagePath;
|
|
52
|
+
constructor(storagePath?: string);
|
|
53
|
+
private ensureDir;
|
|
54
|
+
/**
|
|
55
|
+
* Derive the file backing a session id. TWO independent guards, both required.
|
|
56
|
+
*
|
|
57
|
+
* This is the one place a session id becomes a filesystem path, and it used to
|
|
58
|
+
* interpolate the id RAW: `join(storagePath, "../../OUTSIDE/appconfig.json")`
|
|
59
|
+
* left the session directory entirely, so a cookie could read an existing
|
|
60
|
+
* .json from anywhere on disk into `session.all()` and then OVERWRITE it on
|
|
61
|
+
* save. Reproduced on Node 24.9.0 / macOS.
|
|
62
|
+
*
|
|
63
|
+
* 1. VALIDATE — a malformed id is refused outright. It throws rather than
|
|
64
|
+
* returning null so a hostile id can never be mistaken for an ordinary
|
|
65
|
+
* cache miss (the Session layer catches it, logs it, and degrades).
|
|
66
|
+
* 2. HASH — the filename is a SHA-256 of the id, matching the Python master
|
|
67
|
+
* (`hashlib.sha256(session_id.encode()).hexdigest()`). A hex digest cannot
|
|
68
|
+
* contain a separator or a dot, so even an id that somehow passed the
|
|
69
|
+
* validator can only ever name a file inside `storagePath`.
|
|
70
|
+
*
|
|
71
|
+
* DEPLOY NOTE: hashing CHANGES the filename for every id, so existing on-disk
|
|
72
|
+
* sessions are orphaned and every logged-in user is logged out ONCE on the
|
|
73
|
+
* deploy that ships this. That is accepted: under strict session mode an old
|
|
74
|
+
* cookie is discarded on a read miss anyway, so the sessions were going to be
|
|
75
|
+
* dropped regardless.
|
|
76
|
+
*/
|
|
77
|
+
private filePath;
|
|
78
|
+
read(sessionId: string): SessionData | null;
|
|
79
|
+
write(sessionId: string, data: SessionData, ttl?: number): void;
|
|
80
|
+
destroy(sessionId: string): void;
|
|
81
|
+
gc(maxLifetime?: number): void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Redis session handler using raw TCP (RESP protocol).
|
|
85
|
+
*
|
|
86
|
+
* Uses synchronous socket communication — no external Redis client required.
|
|
87
|
+
* Stores session data as JSON strings with Redis TTL for automatic expiry.
|
|
88
|
+
*
|
|
89
|
+
* Configure via environment variables:
|
|
90
|
+
* TINA4_SESSION_REDIS_HOST (default: "127.0.0.1")
|
|
91
|
+
* TINA4_SESSION_REDIS_PORT (default: 6379)
|
|
92
|
+
* TINA4_SESSION_REDIS_PASSWORD (optional)
|
|
93
|
+
* TINA4_SESSION_REDIS_PREFIX (default: "tina4:session:")
|
|
94
|
+
* TINA4_SESSION_REDIS_DB (default: 0)
|
|
95
|
+
*
|
|
96
|
+
* Or pass via SessionConfig.
|
|
97
|
+
*/
|
|
98
|
+
export declare class RedisSessionHandler implements SessionHandler {
|
|
99
|
+
private host;
|
|
100
|
+
private port;
|
|
101
|
+
private password;
|
|
102
|
+
private prefix;
|
|
103
|
+
private db;
|
|
104
|
+
constructor(config?: SessionConfig);
|
|
105
|
+
/**
|
|
106
|
+
* Execute a Redis command synchronously against the live server.
|
|
107
|
+
*
|
|
108
|
+
* Delegates to the shared {@link respCommandSync} transport: a genuine key miss
|
|
109
|
+
* yields `""`, and a transport/connection FAILURE (server unreachable, rejected
|
|
110
|
+
* AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
|
|
111
|
+
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
112
|
+
*/
|
|
113
|
+
private execSync;
|
|
114
|
+
private key;
|
|
115
|
+
read(sessionId: string): SessionData | null;
|
|
116
|
+
write(sessionId: string, data: SessionData, ttl?: number): void;
|
|
117
|
+
destroy(sessionId: string): void;
|
|
118
|
+
}
|
|
119
|
+
export declare class Session {
|
|
120
|
+
private handler;
|
|
121
|
+
private ttl;
|
|
122
|
+
private sessionId;
|
|
123
|
+
private data;
|
|
124
|
+
/**
|
|
125
|
+
* Dirty flag — set when data changes, cleared only on a successful write.
|
|
126
|
+
* Retained on a failed write so a later save() retries once the backend
|
|
127
|
+
* recovers (mirrors the Python `_dirty` semantics).
|
|
128
|
+
*/
|
|
129
|
+
private dirty;
|
|
130
|
+
/**
|
|
131
|
+
* Backend-failure policy: log-loud + degrade (default), or re-raise when
|
|
132
|
+
* TINA4_SESSION_STRICT is truthy. A read failure logs + yields an empty
|
|
133
|
+
* session, a write failure logs + returns false (best-effort, dirty
|
|
134
|
+
* retained), destroy/gc failures log + swallow. Parity across all four
|
|
135
|
+
* frameworks. Strict mode is the escape hatch (same as events/seeding).
|
|
136
|
+
*/
|
|
137
|
+
private strict;
|
|
138
|
+
/**
|
|
139
|
+
* True when the LAST backend read RAISED rather than returning a miss.
|
|
140
|
+
* Lets `start()` tell "no such session" from "the store is unreachable".
|
|
141
|
+
*/
|
|
142
|
+
private lastReadFailed;
|
|
143
|
+
constructor(backend?: string, config?: SessionConfig);
|
|
144
|
+
/**
|
|
145
|
+
* Use a custom session handler (for advanced use cases).
|
|
146
|
+
*/
|
|
147
|
+
setHandler(handler: SessionHandler): void;
|
|
148
|
+
private logBackendError;
|
|
149
|
+
/**
|
|
150
|
+
* Read through the backend; on FAILURE log + degrade to empty (or re-throw
|
|
151
|
+
* under strict).
|
|
152
|
+
*
|
|
153
|
+
* Sets {@link lastReadFailed} so `start()` can tell "the store answered, and
|
|
154
|
+
* has no such session" from "the store did not answer at all". Strict mode
|
|
155
|
+
* must discard an id only on the first: treating an outage as an unknown id
|
|
156
|
+
* rotates the session id on EVERY request for the whole outage, logging the
|
|
157
|
+
* entire userbase out over one Redis blip and orphaning their stored
|
|
158
|
+
* sessions. The policy is log-loud + degrade, never rotate.
|
|
159
|
+
*/
|
|
160
|
+
private safeRead;
|
|
161
|
+
/** Write through the backend; on FAILURE log + return false (or re-throw under strict). */
|
|
162
|
+
private safeWrite;
|
|
163
|
+
/** Destroy through the backend; on FAILURE log + swallow (or re-throw under strict). */
|
|
164
|
+
private safeDestroy;
|
|
165
|
+
/**
|
|
166
|
+
* Start or resume a session.
|
|
167
|
+
*
|
|
168
|
+
* `sessionId` is UNTRUSTED — it arrives from the session cookie, which the
|
|
169
|
+
* client fully controls. An id that is not a well-formed opaque identifier is
|
|
170
|
+
* DISCARDED and a fresh one minted, never adopted: adopting it let a cookie
|
|
171
|
+
* steer a filesystem path (a `tina4_session=../../OUTSIDE/appconfig` cookie
|
|
172
|
+
* read an existing .json from outside the session directory into
|
|
173
|
+
* `session.all()`, then OVERWROTE it on save) and let an attacker pre-plant a
|
|
174
|
+
* session id that survived the victim's login (session fixation).
|
|
175
|
+
*
|
|
176
|
+
* The check runs BEFORE the read, so a hostile id never reaches a handler at
|
|
177
|
+
* all. A legitimate id from any of the four frameworks passes unchanged.
|
|
178
|
+
*
|
|
179
|
+
* STRICT SESSION MODE (deliberate, and Node is the family reference for it):
|
|
180
|
+
* an id that is WELL-FORMED but UNKNOWN to the backend is also discarded and
|
|
181
|
+
* a fresh one minted — the `if (loaded)` below only adopts an id the store
|
|
182
|
+
* actually knows. That is OWASP's strict mode and PHP's own
|
|
183
|
+
* `session.use_strict_mode=1` default, and it is what stops an attacker
|
|
184
|
+
* planting a session id that survives the victim's login. The validation
|
|
185
|
+
* above sits IN FRONT of it; neither replaces the other.
|
|
186
|
+
*
|
|
187
|
+
* @param sessionId - Existing session ID to resume (optional)
|
|
188
|
+
* @returns The session ID
|
|
189
|
+
*/
|
|
190
|
+
start(sessionId?: string): string;
|
|
191
|
+
/**
|
|
192
|
+
* Get a value from the session.
|
|
193
|
+
*/
|
|
194
|
+
get(key: string, defaultValue?: unknown): unknown;
|
|
195
|
+
/**
|
|
196
|
+
* Set a value in the session.
|
|
197
|
+
*/
|
|
198
|
+
set(key: string, value: unknown): void;
|
|
199
|
+
/**
|
|
200
|
+
* Delete a key from the session.
|
|
201
|
+
*/
|
|
202
|
+
delete(key: string): void;
|
|
203
|
+
/**
|
|
204
|
+
* Destroy the entire session.
|
|
205
|
+
*
|
|
206
|
+
* A backend failure is logged (never silent) but does not throw under the
|
|
207
|
+
* default policy — local state is cleared regardless so the request proceeds.
|
|
208
|
+
*/
|
|
209
|
+
destroy(): void;
|
|
210
|
+
/**
|
|
211
|
+
* Get all session data (excluding internal keys).
|
|
212
|
+
*/
|
|
213
|
+
all(): Record<string, unknown>;
|
|
214
|
+
/**
|
|
215
|
+
* Clear all session data (but keep the session alive).
|
|
216
|
+
*/
|
|
217
|
+
clear(): void;
|
|
218
|
+
/**
|
|
219
|
+
* Check if a key exists in the session.
|
|
220
|
+
*/
|
|
221
|
+
has(key: string): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Regenerate the session ID (keeps data, new ID).
|
|
224
|
+
*
|
|
225
|
+
* Call this right after a successful login or any privilege change to defeat
|
|
226
|
+
* session fixation — the pre-auth ID is destroyed and the data is carried
|
|
227
|
+
* onto a fresh, unguessable ID. A backend destroy/write failure is logged
|
|
228
|
+
* (never silent) but does not throw under the default policy.
|
|
229
|
+
*/
|
|
230
|
+
regenerate(): string;
|
|
231
|
+
/**
|
|
232
|
+
* Dual-mode flash: set with value, get+remove without.
|
|
233
|
+
*
|
|
234
|
+
* session.flash("message", "Saved!") // set
|
|
235
|
+
* session.flash("message") // get + auto-remove → "Saved!"
|
|
236
|
+
*/
|
|
237
|
+
flash(key: string, value?: unknown): unknown;
|
|
238
|
+
/**
|
|
239
|
+
* Get flash data by key (alias for flash(key) without value).
|
|
240
|
+
*/
|
|
241
|
+
getFlash(key: string, defaultValue?: unknown): unknown;
|
|
242
|
+
/**
|
|
243
|
+
* Get the current session ID.
|
|
244
|
+
*/
|
|
245
|
+
getSessionId(): string | null;
|
|
246
|
+
/**
|
|
247
|
+
* Return a Set-Cookie header value for this session.
|
|
248
|
+
*
|
|
249
|
+
* Honours these env vars (cross-framework parity):
|
|
250
|
+
* TINA4_SESSION_NAME — cookie name (default: "tina4_session")
|
|
251
|
+
* TINA4_SESSION_SAMESITE — SameSite attribute (default: "Lax")
|
|
252
|
+
* TINA4_SESSION_HTTPONLY — emit HttpOnly (default: true)
|
|
253
|
+
* TINA4_SESSION_SECURE — emit Secure (default: false)
|
|
254
|
+
*/
|
|
255
|
+
cookieHeader(cookieName?: string): string;
|
|
256
|
+
/**
|
|
257
|
+
* Run garbage collection on the session backend.
|
|
258
|
+
* Removes expired file/database sessions. Redis/Valkey/Mongo handle TTL natively.
|
|
259
|
+
*
|
|
260
|
+
* A backend failure is logged (never silent) but does not throw under the
|
|
261
|
+
* default policy (re-raises under TINA4_SESSION_STRICT=true).
|
|
262
|
+
*/
|
|
263
|
+
gc(): void;
|
|
264
|
+
/**
|
|
265
|
+
* Persist session data to the backend.
|
|
266
|
+
*
|
|
267
|
+
* Returns true on a successful persist, false if the backend was unreachable
|
|
268
|
+
* (logged). The dirty flag is cleared only on success so a later save()
|
|
269
|
+
* retries once the backend recovers. A nothing-to-persist call returns true.
|
|
270
|
+
*/
|
|
271
|
+
save(): boolean;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Is the client's scheme HTTPS? Proxy-aware.
|
|
275
|
+
*
|
|
276
|
+
* TLS is normally terminated at a proxy (nginx, HAProxy, ALB, Cloudflare, most
|
|
277
|
+
* container deploys) which then forwards plain HTTP to Node — so the native
|
|
278
|
+
* socket is NOT encrypted on exactly the deployments that ARE https, and it
|
|
279
|
+
* cannot be the only signal. `x-forwarded-proto` carries the scheme the client
|
|
280
|
+
* actually used; a chain of proxies appends each hop ("https, http") and the
|
|
281
|
+
* FIRST is the client-facing one, which is the scheme the browser used.
|
|
282
|
+
*
|
|
283
|
+
* Parity with PHP `Request::isSecureScheme` (tina4-php#175). Spoofable when the
|
|
284
|
+
* app is directly reachable, but the failure mode is self-limiting: a spoofed
|
|
285
|
+
* `https` only makes the cookie MORE restrictive, and `request.ts` already
|
|
286
|
+
* trusts the same header for URL construction — honouring it here is consistent.
|
|
287
|
+
*
|
|
288
|
+
* @param forwardedProto Raw `x-forwarded-proto` value (or a resolved scheme like
|
|
289
|
+
* "https"/"http"); "" / undefined means "absent".
|
|
290
|
+
* @param socketEncrypted True when Node terminated TLS itself (direct https, no
|
|
291
|
+
* proxy) — the native fallback when no forwarded header.
|
|
292
|
+
*/
|
|
293
|
+
export declare function isSecureScheme(forwardedProto?: string, socketEncrypted?: boolean): boolean;
|
|
294
|
+
/**
|
|
295
|
+
* Resolve the session cookie name — the single source of truth shared by the
|
|
296
|
+
* WRITE side (`buildSessionCookie` / `Session.cookieHeader`) and the READ side
|
|
297
|
+
* (the auto-session cookie parse in `server.ts`), so a cookie written under a
|
|
298
|
+
* renamed name is read back on the next request.
|
|
299
|
+
*
|
|
300
|
+
* TINA4_SESSION_NAME Cookie name (default: "tina4_session")
|
|
301
|
+
*
|
|
302
|
+
* Keeping this in one place means the default can never drift between the two
|
|
303
|
+
* sides: an operator who sets `TINA4_SESSION_NAME` renames the cookie on both
|
|
304
|
+
* the emit and the parse paths at once. Parity with Python
|
|
305
|
+
* `session.session_cookie_name()`.
|
|
306
|
+
*/
|
|
307
|
+
export declare function sessionCookieName(): string;
|
|
308
|
+
/**
|
|
309
|
+
* Is TINA4_SESSION_STRICT on? The single source of truth for the flag.
|
|
310
|
+
*
|
|
311
|
+
* Module level, not a Session field, because the REQUEST PATH has to be able to
|
|
312
|
+
* consult it when a Session could not be constructed at all - a handler whose
|
|
313
|
+
* constructor raises, or a refused TINA4_SESSION_BACKEND, both fail BEFORE
|
|
314
|
+
* there is an object to ask. That gap is why strict mode used to be inert on
|
|
315
|
+
* the request path. Parity with Python `session.session_strict_mode()`.
|
|
316
|
+
*
|
|
317
|
+
* TINA4_SESSION_STRICT re-throw instead of degrading (default: false)
|
|
318
|
+
*/
|
|
319
|
+
export declare function sessionStrictMode(): boolean;
|
|
320
|
+
/**
|
|
321
|
+
* Build the `Set-Cookie` header value for a Tina4 session. Centralised so
|
|
322
|
+
* the auto-cookie path in server.ts and `Session.cookieHeader()` agree on
|
|
323
|
+
* which env vars are honoured and what the defaults are.
|
|
324
|
+
*
|
|
325
|
+
* Env vars (Python parity):
|
|
326
|
+
* TINA4_SESSION_NAME — cookie name (default: "tina4_session")
|
|
327
|
+
* TINA4_SESSION_SAMESITE — SameSite attribute (default: "Lax")
|
|
328
|
+
* TINA4_SESSION_HTTPONLY — emit HttpOnly (default: true)
|
|
329
|
+
* TINA4_SESSION_SECURE — emit Secure (default: false; SameSite=None forces it on)
|
|
330
|
+
*
|
|
331
|
+
* `Secure` is emitted when ANY of: TINA4_SESSION_SECURE is truthy; SameSite is
|
|
332
|
+
* `None` (browsers reject a None cookie without Secure); OR the request scheme
|
|
333
|
+
* is https, detected proxy-aware from `forwardedProto` / `socketEncrypted`. The
|
|
334
|
+
* auto-cookie path in server.ts threads the request's scheme in so an HTTPS
|
|
335
|
+
* deploy behind a TLS-terminating proxy ships Secure without the operator
|
|
336
|
+
* having to know about TINA4_SESSION_SECURE (nodejs#34). Plain HTTP with no
|
|
337
|
+
* proxy header and no native TLS stays NOT Secure — an eager Secure would make
|
|
338
|
+
* http://localhost dev cookies undeliverable.
|
|
339
|
+
*/
|
|
340
|
+
export declare function buildSessionCookie(sessionId: string | null, ttl: number, cookieName?: string, forwardedProto?: string, socketEncrypted?: boolean): string;
|
|
341
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tina4 session handlers — turn a failed `execFileSync` child into a readable cause.
|
|
3
|
+
*
|
|
4
|
+
* The session-handler interface is synchronous but every backend client is async,
|
|
5
|
+
* so each command runs in a short-lived `node -e` child. When that child fails,
|
|
6
|
+
* `execFileSync` throws an error whose `.message` begins "Command failed:" and
|
|
7
|
+
* then embeds THE ENTIRE GENERATED SCRIPT — kilobytes of source with the real
|
|
8
|
+
* reason nowhere in it. Every handler used to throw exactly that, so an
|
|
9
|
+
* operator debugging a Redis outage got a wall of JavaScript instead of
|
|
10
|
+
* "connect ECONNREFUSED 127.0.0.1:6379".
|
|
11
|
+
*
|
|
12
|
+
* The children already write the real reason to stderr; `execFileSync` captures
|
|
13
|
+
* it on `err.stderr`. This module is the ONE place that prefers it, so the three
|
|
14
|
+
* call sites (respClient, mongoClient, redisHandler's npm path) cannot drift.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Extract the most useful one-line cause from a thrown `execFileSync` error.
|
|
18
|
+
*
|
|
19
|
+
* Order of preference:
|
|
20
|
+
* 1. the child's own stderr — what it actually reported;
|
|
21
|
+
* 2. a timeout, named as such (a SIGTERM kill leaves stderr empty, so without
|
|
22
|
+
* this the caller would see the useless generic message);
|
|
23
|
+
* 3. a non-zero exit code with no output at all;
|
|
24
|
+
* 4. the error's own message, first line only and length-capped, so the
|
|
25
|
+
* generated script can never be dumped into a log.
|
|
26
|
+
*/
|
|
27
|
+
export declare function childFailureReason(err: unknown): string;
|
|
28
|
+
/**
|
|
29
|
+
* Build the Error a session handler throws when its child command failed.
|
|
30
|
+
*
|
|
31
|
+
* `label` names the backend ("Redis", "Valkey", "MongoDB") so the message says
|
|
32
|
+
* which one broke; the wording is shared so all three read alike.
|
|
33
|
+
*/
|
|
34
|
+
export declare function childFailureError(label: string, err: unknown): Error;
|