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.
Files changed (193) hide show
  1. package/CLAUDE.md +170 -28
  2. package/README.md +2 -2
  3. package/package.json +13 -9
  4. package/packages/cli/dist/bin.js +33126 -30055
  5. package/packages/cli/src/commands/metrics.ts +17 -11
  6. package/packages/cli/src/commands/serve.ts +10 -9
  7. package/packages/core/dist/index.js +33062 -29908
  8. package/packages/core/src/ai.ts +7 -1
  9. package/packages/core/src/auth.ts +191 -39
  10. package/packages/core/src/background.ts +19 -19
  11. package/packages/core/src/cache.ts +492 -49
  12. package/packages/core/src/devAdmin.ts +79 -32
  13. package/packages/core/src/devMailbox.ts +20 -44
  14. package/packages/core/src/dispatchPipeline.ts +285 -0
  15. package/packages/core/src/dotenv.ts +185 -40
  16. package/packages/core/src/index.ts +7 -6
  17. package/packages/core/src/logger.ts +257 -36
  18. package/packages/core/src/mcp.ts +1 -1
  19. package/packages/core/src/messenger.ts +81 -13
  20. package/packages/core/src/metrics.ts +199 -961
  21. package/packages/core/src/middleware.ts +390 -123
  22. package/packages/core/src/queue.ts +188 -32
  23. package/packages/core/src/queueBackends/kafkaBackend.ts +109 -13
  24. package/packages/core/src/queueBackends/liteBackend.ts +13 -0
  25. package/packages/core/src/queueBackends/mongoBackend.ts +101 -9
  26. package/packages/core/src/queueBackends/rabbitmqBackend.ts +22 -4
  27. package/packages/core/src/rateLimiter.ts +10 -5
  28. package/packages/core/src/request.ts +6 -9
  29. package/packages/core/src/response.ts +46 -1
  30. package/packages/core/src/router.ts +29 -4
  31. package/packages/core/src/server.ts +751 -414
  32. package/packages/core/src/session.ts +244 -27
  33. package/packages/core/src/sessionHandlers/childError.ts +72 -0
  34. package/packages/core/src/sessionHandlers/databaseHandler.ts +338 -48
  35. package/packages/core/src/sessionHandlers/memcachedHandler.ts +181 -0
  36. package/packages/core/src/sessionHandlers/mongoClient.ts +293 -202
  37. package/packages/core/src/sessionHandlers/mongoHandler.ts +88 -8
  38. package/packages/core/src/sessionHandlers/respClient.ts +16 -143
  39. package/packages/core/src/sessionHandlers/sqlClient.ts +290 -0
  40. package/packages/core/src/sessionHandlers/syncBridge.ts +190 -0
  41. package/packages/core/src/sessionHandlers/syncSocket.ts +236 -0
  42. package/packages/core/src/testClient.ts +18 -5
  43. package/packages/core/src/trustedProxy.ts +249 -0
  44. package/packages/core/src/types.ts +29 -5
  45. package/packages/core/src/websocket.ts +66 -0
  46. package/packages/frond/dist/index.js +74 -31
  47. package/packages/frond/src/engine.ts +99 -33
  48. package/packages/orm/dist/index.js +26554 -23400
  49. package/packages/orm/src/adapters/firebird.ts +183 -56
  50. package/packages/orm/src/adapters/mongodb.ts +25 -4
  51. package/packages/orm/src/adapters/mssql.ts +114 -29
  52. package/packages/orm/src/adapters/mysql.ts +103 -40
  53. package/packages/orm/src/adapters/odbc.ts +44 -21
  54. package/packages/orm/src/adapters/postgres.ts +118 -26
  55. package/packages/orm/src/adapters/sqlDialect.ts +120 -0
  56. package/packages/orm/src/adapters/sqlite.ts +64 -25
  57. package/packages/orm/src/baseModel.ts +135 -40
  58. package/packages/orm/src/cachedDatabase.ts +43 -19
  59. package/packages/orm/src/connectTimeout.ts +265 -0
  60. package/packages/orm/src/database.ts +338 -198
  61. package/packages/orm/src/databaseResult.ts +65 -13
  62. package/packages/orm/src/databaseUrl.ts +484 -0
  63. package/packages/orm/src/docstore.ts +386 -145
  64. package/packages/orm/src/index.ts +13 -3
  65. package/packages/orm/src/migration.ts +18 -3
  66. package/packages/orm/src/queryBuilder.ts +38 -4
  67. package/packages/orm/src/sqlTranslator.ts +310 -4
  68. package/packages/orm/src/types.ts +15 -4
  69. package/types/cli/src/bin.d.ts +92 -0
  70. package/types/cli/src/commands/build.d.ts +2 -0
  71. package/types/cli/src/commands/generate.d.ts +47 -0
  72. package/types/cli/src/commands/init.d.ts +1 -0
  73. package/types/cli/src/commands/metrics.d.ts +6 -0
  74. package/types/cli/src/commands/migrate.d.ts +1 -0
  75. package/types/cli/src/commands/migrateCreate.d.ts +1 -0
  76. package/types/cli/src/commands/migrateRollback.d.ts +1 -0
  77. package/types/cli/src/commands/migrateStatus.d.ts +1 -0
  78. package/types/cli/src/commands/queue.d.ts +20 -0
  79. package/types/cli/src/commands/routes.d.ts +1 -0
  80. package/types/cli/src/commands/seed.d.ts +1 -0
  81. package/types/cli/src/commands/serve.d.ts +6 -0
  82. package/types/cli/src/commands/test.d.ts +1 -0
  83. package/types/core/src/ai.d.ts +64 -0
  84. package/types/core/src/api.d.ts +262 -0
  85. package/types/core/src/auth.d.ts +177 -0
  86. package/types/core/src/authGate.d.ts +20 -0
  87. package/types/core/src/background.d.ts +34 -0
  88. package/types/core/src/cache.d.ts +163 -0
  89. package/types/core/src/constants.d.ts +38 -0
  90. package/types/core/src/container.d.ts +44 -0
  91. package/types/core/src/context/chunker.d.ts +31 -0
  92. package/types/core/src/context/index.d.ts +93 -0
  93. package/types/core/src/devAdmin.d.ts +179 -0
  94. package/types/core/src/devMailbox.d.ts +54 -0
  95. package/types/core/src/dispatchPipeline.d.ts +117 -0
  96. package/types/core/src/docs.d.ts +141 -0
  97. package/types/core/src/docsAutoDiscovery.d.ts +6 -0
  98. package/types/core/src/dotenv.d.ts +87 -0
  99. package/types/core/src/env.d.ts +28 -0
  100. package/types/core/src/errorOverlay.d.ts +36 -0
  101. package/types/core/src/events.d.ts +75 -0
  102. package/types/core/src/fakeData.d.ts +55 -0
  103. package/types/core/src/feedback.d.ts +90 -0
  104. package/types/core/src/graphql.d.ts +207 -0
  105. package/types/core/src/health.d.ts +22 -0
  106. package/types/core/src/htmlElement.d.ts +75 -0
  107. package/types/core/src/i18n.d.ts +37 -0
  108. package/types/core/src/index.d.ts +92 -0
  109. package/types/core/src/job.d.ts +39 -0
  110. package/types/core/src/logger.d.ts +200 -0
  111. package/types/core/src/mcp.d.ts +248 -0
  112. package/types/core/src/messenger.d.ts +191 -0
  113. package/types/core/src/metrics.d.ts +41 -0
  114. package/types/core/src/middleware.d.ts +330 -0
  115. package/types/core/src/mqtt.d.ts +257 -0
  116. package/types/core/src/mqttMessage.d.ts +67 -0
  117. package/types/core/src/plan.d.ts +96 -0
  118. package/types/core/src/projectIndex.d.ts +56 -0
  119. package/types/core/src/queue.d.ts +268 -0
  120. package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
  121. package/types/core/src/queueBackends/liteBackend.d.ts +128 -0
  122. package/types/core/src/queueBackends/mongoBackend.d.ts +119 -0
  123. package/types/core/src/queueBackends/rabbitmqBackend.d.ts +55 -0
  124. package/types/core/src/rateLimiter.d.ts +49 -0
  125. package/types/core/src/request.d.ts +25 -0
  126. package/types/core/src/response.d.ts +28 -0
  127. package/types/core/src/routeDiscovery.d.ts +12 -0
  128. package/types/core/src/router.d.ts +366 -0
  129. package/types/core/src/scss.d.ts +19 -0
  130. package/types/core/src/server.d.ts +146 -0
  131. package/types/core/src/service.d.ts +115 -0
  132. package/types/core/src/session.d.ts +341 -0
  133. package/types/core/src/sessionHandlers/childError.d.ts +34 -0
  134. package/types/core/src/sessionHandlers/databaseHandler.d.ts +97 -0
  135. package/types/core/src/sessionHandlers/memcachedHandler.d.ts +60 -0
  136. package/types/core/src/sessionHandlers/mongoClient.d.ts +35 -0
  137. package/types/core/src/sessionHandlers/mongoHandler.d.ts +109 -0
  138. package/types/core/src/sessionHandlers/respClient.d.ts +22 -0
  139. package/types/core/src/sessionHandlers/sqlClient.d.ts +39 -0
  140. package/types/core/src/sessionHandlers/syncBridge.d.ts +91 -0
  141. package/types/core/src/sessionHandlers/syncSocket.d.ts +49 -0
  142. package/types/core/src/sessionHandlers/valkeyHandler.d.ts +65 -0
  143. package/types/core/src/static.d.ts +2 -0
  144. package/types/core/src/test.d.ts +94 -0
  145. package/types/core/src/testClient.d.ts +36 -0
  146. package/types/core/src/testing.d.ts +58 -0
  147. package/types/core/src/trustedProxy.d.ts +44 -0
  148. package/types/core/src/types.d.ts +242 -0
  149. package/types/core/src/validator.d.ts +52 -0
  150. package/types/core/src/websocket.d.ts +402 -0
  151. package/types/core/src/websocketBackplane.d.ts +166 -0
  152. package/types/core/src/websocketConnection.d.ts +54 -0
  153. package/types/core/src/wsdl.d.ts +101 -0
  154. package/types/frond/src/engine.d.ts +263 -0
  155. package/types/frond/src/index.d.ts +2 -0
  156. package/types/orm/src/adapters/firebird.d.ts +183 -0
  157. package/types/orm/src/adapters/mongodb.d.ts +81 -0
  158. package/types/orm/src/adapters/mssql.d.ts +77 -0
  159. package/types/orm/src/adapters/mysql.d.ts +67 -0
  160. package/types/orm/src/adapters/odbc.d.ts +94 -0
  161. package/types/orm/src/adapters/postgres.d.ts +86 -0
  162. package/types/orm/src/adapters/sqlDialect.d.ts +71 -0
  163. package/types/orm/src/adapters/sqlite.d.ts +68 -0
  164. package/types/orm/src/autoCrud.d.ts +73 -0
  165. package/types/orm/src/baseModel.d.ts +427 -0
  166. package/types/orm/src/cachedDatabase.d.ts +190 -0
  167. package/types/orm/src/connectTimeout.d.ts +100 -0
  168. package/types/orm/src/database.d.ts +655 -0
  169. package/types/orm/src/databaseResult.d.ts +109 -0
  170. package/types/orm/src/databaseUrl.d.ts +125 -0
  171. package/types/orm/src/docstore.d.ts +241 -0
  172. package/types/orm/src/fakeData.d.ts +22 -0
  173. package/types/orm/src/index.d.ts +43 -0
  174. package/types/orm/src/migration.d.ts +275 -0
  175. package/types/orm/src/model.d.ts +7 -0
  176. package/types/orm/src/query.d.ts +14 -0
  177. package/types/orm/src/queryBuilder.d.ts +193 -0
  178. package/types/orm/src/realtime/index.d.ts +7 -0
  179. package/types/orm/src/realtime/models/attachment.d.ts +43 -0
  180. package/types/orm/src/realtime/models/channel.d.ts +32 -0
  181. package/types/orm/src/realtime/models/channelMember.d.ts +32 -0
  182. package/types/orm/src/realtime/models/message.d.ts +36 -0
  183. package/types/orm/src/realtime/models/workspace.d.ts +26 -0
  184. package/types/orm/src/realtime/realtime.d.ts +24 -0
  185. package/types/orm/src/realtime/storage.d.ts +61 -0
  186. package/types/orm/src/seeder.d.ts +118 -0
  187. package/types/orm/src/sqlTranslator.d.ts +258 -0
  188. package/types/orm/src/types.d.ts +148 -0
  189. package/types/orm/src/validation.d.ts +6 -0
  190. package/types/swagger/src/generator.d.ts +46 -0
  191. package/types/swagger/src/index.d.ts +2 -0
  192. package/types/swagger/src/ui.d.ts +11 -0
  193. 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;