tina4-nodejs 3.13.92 → 3.13.94
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 +16 -3
- package/README.md +1 -1
- package/package.json +12 -9
- package/packages/cli/dist/bin.js +1260 -969
- package/packages/core/dist/index.js +1260 -969
- package/packages/core/src/devMailbox.ts +20 -44
- package/packages/core/src/index.ts +2 -2
- package/packages/core/src/messenger.ts +72 -0
- package/packages/core/src/queueBackends/kafkaBackend.ts +108 -12
- package/packages/core/src/sessionHandlers/childError.ts +72 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +9 -3
- package/packages/core/src/sessionHandlers/redisHandler.ts +18 -5
- package/packages/core/src/sessionHandlers/respClient.ts +5 -1
- package/packages/frond/dist/index.js +74 -31
- package/packages/frond/src/engine.ts +99 -33
- package/packages/orm/dist/index.js +3055 -2764
- package/packages/orm/src/adapters/sqlite.ts +4 -1
- package/packages/orm/src/database.ts +108 -8
- 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 +154 -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 +160 -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/docs.d.ts +141 -0
- package/types/core/src/docsAutoDiscovery.d.ts +6 -0
- package/types/core/src/dotenv.d.ts +65 -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 +93 -0
- package/types/core/src/job.d.ts +39 -0
- package/types/core/src/logger.d.ts +123 -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 +77 -0
- package/types/core/src/middleware.d.ts +207 -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 +219 -0
- package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
- package/types/core/src/queueBackends/liteBackend.d.ts +119 -0
- package/types/core/src/queueBackends/mongoBackend.d.ts +97 -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 +355 -0
- package/types/core/src/scss.d.ts +19 -0
- package/types/core/src/server.d.ts +131 -0
- package/types/core/src/service.d.ts +115 -0
- package/types/core/src/session.d.ts +256 -0
- package/types/core/src/sessionHandlers/childError.d.ts +34 -0
- package/types/core/src/sessionHandlers/databaseHandler.d.ts +42 -0
- package/types/core/src/sessionHandlers/mongoClient.d.ts +24 -0
- package/types/core/src/sessionHandlers/mongoHandler.d.ts +61 -0
- package/types/core/src/sessionHandlers/redisHandler.d.ts +60 -0
- package/types/core/src/sessionHandlers/respClient.d.ts +22 -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/types.d.ts +219 -0
- package/types/core/src/validator.d.ts +52 -0
- package/types/core/src/websocket.d.ts +376 -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 +138 -0
- package/types/orm/src/adapters/mongodb.d.ts +81 -0
- package/types/orm/src/adapters/mssql.d.ts +70 -0
- package/types/orm/src/adapters/mysql.d.ts +66 -0
- package/types/orm/src/adapters/odbc.d.ts +97 -0
- package/types/orm/src/adapters/postgres.d.ts +85 -0
- package/types/orm/src/adapters/sqlite.d.ts +56 -0
- package/types/orm/src/autoCrud.d.ts +73 -0
- package/types/orm/src/baseModel.d.ts +391 -0
- package/types/orm/src/cachedDatabase.d.ts +177 -0
- package/types/orm/src/database.d.ts +609 -0
- package/types/orm/src/databaseResult.d.ts +85 -0
- package/types/orm/src/docstore.d.ts +182 -0
- package/types/orm/src/fakeData.d.ts +22 -0
- package/types/orm/src/index.d.ts +40 -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 +173 -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 +134 -0
- package/types/orm/src/types.d.ts +138 -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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
export interface SessionConfig {
|
|
2
|
+
/** Session backend type: "file", "redis", "valkey", "mongo", "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
|
+
* Base interface for session storage backends.
|
|
26
|
+
* Implementations must provide read, write, and destroy.
|
|
27
|
+
*/
|
|
28
|
+
export interface SessionHandler {
|
|
29
|
+
read(sessionId: string): SessionData | null;
|
|
30
|
+
write(sessionId: string, data: SessionData, ttl?: number): void;
|
|
31
|
+
destroy(sessionId: string): void;
|
|
32
|
+
/** Garbage-collect expired sessions. Optional — Redis/Valkey/Mongo handle TTL natively. */
|
|
33
|
+
gc?(maxLifetime: number): void;
|
|
34
|
+
}
|
|
35
|
+
export declare class FileSessionHandler implements SessionHandler {
|
|
36
|
+
private storagePath;
|
|
37
|
+
constructor(storagePath?: string);
|
|
38
|
+
private ensureDir;
|
|
39
|
+
private filePath;
|
|
40
|
+
read(sessionId: string): SessionData | null;
|
|
41
|
+
write(sessionId: string, data: SessionData, ttl?: number): void;
|
|
42
|
+
destroy(sessionId: string): void;
|
|
43
|
+
gc(maxLifetime?: number): void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Redis session handler using raw TCP (RESP protocol).
|
|
47
|
+
*
|
|
48
|
+
* Uses synchronous socket communication — no external Redis client required.
|
|
49
|
+
* Stores session data as JSON strings with Redis TTL for automatic expiry.
|
|
50
|
+
*
|
|
51
|
+
* Configure via environment variables:
|
|
52
|
+
* TINA4_SESSION_REDIS_HOST (default: "127.0.0.1")
|
|
53
|
+
* TINA4_SESSION_REDIS_PORT (default: 6379)
|
|
54
|
+
* TINA4_SESSION_REDIS_PASSWORD (optional)
|
|
55
|
+
* TINA4_SESSION_REDIS_PREFIX (default: "tina4:session:")
|
|
56
|
+
* TINA4_SESSION_REDIS_DB (default: 0)
|
|
57
|
+
*
|
|
58
|
+
* Or pass via SessionConfig.
|
|
59
|
+
*/
|
|
60
|
+
export declare class RedisSessionHandler implements SessionHandler {
|
|
61
|
+
private host;
|
|
62
|
+
private port;
|
|
63
|
+
private password;
|
|
64
|
+
private prefix;
|
|
65
|
+
private db;
|
|
66
|
+
constructor(config?: SessionConfig);
|
|
67
|
+
/**
|
|
68
|
+
* Execute a Redis command synchronously against the live server.
|
|
69
|
+
*
|
|
70
|
+
* Delegates to the shared {@link respCommandSync} transport: a genuine key miss
|
|
71
|
+
* yields `""`, and a transport/connection FAILURE (server unreachable, rejected
|
|
72
|
+
* AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
|
|
73
|
+
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
74
|
+
*/
|
|
75
|
+
private execSync;
|
|
76
|
+
private key;
|
|
77
|
+
read(sessionId: string): SessionData | null;
|
|
78
|
+
write(sessionId: string, data: SessionData, ttl?: number): void;
|
|
79
|
+
destroy(sessionId: string): void;
|
|
80
|
+
}
|
|
81
|
+
export declare class Session {
|
|
82
|
+
private handler;
|
|
83
|
+
private ttl;
|
|
84
|
+
private sessionId;
|
|
85
|
+
private data;
|
|
86
|
+
/**
|
|
87
|
+
* Dirty flag — set when data changes, cleared only on a successful write.
|
|
88
|
+
* Retained on a failed write so a later save() retries once the backend
|
|
89
|
+
* recovers (mirrors the Python `_dirty` semantics).
|
|
90
|
+
*/
|
|
91
|
+
private dirty;
|
|
92
|
+
/**
|
|
93
|
+
* Backend-failure policy: log-loud + degrade (default), or re-raise when
|
|
94
|
+
* TINA4_SESSION_STRICT is truthy. A read failure logs + yields an empty
|
|
95
|
+
* session, a write failure logs + returns false (best-effort, dirty
|
|
96
|
+
* retained), destroy/gc failures log + swallow. Parity across all four
|
|
97
|
+
* frameworks. Strict mode is the escape hatch (same as events/seeding).
|
|
98
|
+
*/
|
|
99
|
+
private strict;
|
|
100
|
+
constructor(backend?: string, config?: SessionConfig);
|
|
101
|
+
/**
|
|
102
|
+
* Use a custom session handler (for advanced use cases).
|
|
103
|
+
*/
|
|
104
|
+
setHandler(handler: SessionHandler): void;
|
|
105
|
+
private logBackendError;
|
|
106
|
+
/** Read through the backend; on FAILURE log + degrade to empty (or re-throw under strict). */
|
|
107
|
+
private safeRead;
|
|
108
|
+
/** Write through the backend; on FAILURE log + return false (or re-throw under strict). */
|
|
109
|
+
private safeWrite;
|
|
110
|
+
/** Destroy through the backend; on FAILURE log + swallow (or re-throw under strict). */
|
|
111
|
+
private safeDestroy;
|
|
112
|
+
/**
|
|
113
|
+
* Start or resume a session.
|
|
114
|
+
* @param sessionId - Existing session ID to resume (optional)
|
|
115
|
+
* @returns The session ID
|
|
116
|
+
*/
|
|
117
|
+
start(sessionId?: string): string;
|
|
118
|
+
/**
|
|
119
|
+
* Get a value from the session.
|
|
120
|
+
*/
|
|
121
|
+
get(key: string, defaultValue?: unknown): unknown;
|
|
122
|
+
/**
|
|
123
|
+
* Set a value in the session.
|
|
124
|
+
*/
|
|
125
|
+
set(key: string, value: unknown): void;
|
|
126
|
+
/**
|
|
127
|
+
* Delete a key from the session.
|
|
128
|
+
*/
|
|
129
|
+
delete(key: string): void;
|
|
130
|
+
/**
|
|
131
|
+
* Destroy the entire session.
|
|
132
|
+
*
|
|
133
|
+
* A backend failure is logged (never silent) but does not throw under the
|
|
134
|
+
* default policy — local state is cleared regardless so the request proceeds.
|
|
135
|
+
*/
|
|
136
|
+
destroy(): void;
|
|
137
|
+
/**
|
|
138
|
+
* Get all session data (excluding internal keys).
|
|
139
|
+
*/
|
|
140
|
+
all(): Record<string, unknown>;
|
|
141
|
+
/**
|
|
142
|
+
* Clear all session data (but keep the session alive).
|
|
143
|
+
*/
|
|
144
|
+
clear(): void;
|
|
145
|
+
/**
|
|
146
|
+
* Check if a key exists in the session.
|
|
147
|
+
*/
|
|
148
|
+
has(key: string): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Regenerate the session ID (keeps data, new ID).
|
|
151
|
+
*
|
|
152
|
+
* Call this right after a successful login or any privilege change to defeat
|
|
153
|
+
* session fixation — the pre-auth ID is destroyed and the data is carried
|
|
154
|
+
* onto a fresh, unguessable ID. A backend destroy/write failure is logged
|
|
155
|
+
* (never silent) but does not throw under the default policy.
|
|
156
|
+
*/
|
|
157
|
+
regenerate(): string;
|
|
158
|
+
/**
|
|
159
|
+
* Dual-mode flash: set with value, get+remove without.
|
|
160
|
+
*
|
|
161
|
+
* session.flash("message", "Saved!") // set
|
|
162
|
+
* session.flash("message") // get + auto-remove → "Saved!"
|
|
163
|
+
*/
|
|
164
|
+
flash(key: string, value?: unknown): unknown;
|
|
165
|
+
/**
|
|
166
|
+
* Get flash data by key (alias for flash(key) without value).
|
|
167
|
+
*/
|
|
168
|
+
getFlash(key: string, defaultValue?: unknown): unknown;
|
|
169
|
+
/**
|
|
170
|
+
* Get the current session ID.
|
|
171
|
+
*/
|
|
172
|
+
getSessionId(): string | null;
|
|
173
|
+
/**
|
|
174
|
+
* Return a Set-Cookie header value for this session.
|
|
175
|
+
*
|
|
176
|
+
* Honours these env vars (cross-framework parity):
|
|
177
|
+
* TINA4_SESSION_NAME — cookie name (default: "tina4_session")
|
|
178
|
+
* TINA4_SESSION_SAMESITE — SameSite attribute (default: "Lax")
|
|
179
|
+
* TINA4_SESSION_HTTPONLY — emit HttpOnly (default: true)
|
|
180
|
+
* TINA4_SESSION_SECURE — emit Secure (default: false)
|
|
181
|
+
*/
|
|
182
|
+
cookieHeader(cookieName?: string): string;
|
|
183
|
+
/**
|
|
184
|
+
* Run garbage collection on the session backend.
|
|
185
|
+
* Removes expired file/database sessions. Redis/Valkey/Mongo handle TTL natively.
|
|
186
|
+
*
|
|
187
|
+
* A backend failure is logged (never silent) but does not throw under the
|
|
188
|
+
* default policy (re-raises under TINA4_SESSION_STRICT=true).
|
|
189
|
+
*/
|
|
190
|
+
gc(): void;
|
|
191
|
+
/**
|
|
192
|
+
* Persist session data to the backend.
|
|
193
|
+
*
|
|
194
|
+
* Returns true on a successful persist, false if the backend was unreachable
|
|
195
|
+
* (logged). The dirty flag is cleared only on success so a later save()
|
|
196
|
+
* retries once the backend recovers. A nothing-to-persist call returns true.
|
|
197
|
+
*/
|
|
198
|
+
save(): boolean;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Is the client's scheme HTTPS? Proxy-aware.
|
|
202
|
+
*
|
|
203
|
+
* TLS is normally terminated at a proxy (nginx, HAProxy, ALB, Cloudflare, most
|
|
204
|
+
* container deploys) which then forwards plain HTTP to Node — so the native
|
|
205
|
+
* socket is NOT encrypted on exactly the deployments that ARE https, and it
|
|
206
|
+
* cannot be the only signal. `x-forwarded-proto` carries the scheme the client
|
|
207
|
+
* actually used; a chain of proxies appends each hop ("https, http") and the
|
|
208
|
+
* FIRST is the client-facing one, which is the scheme the browser used.
|
|
209
|
+
*
|
|
210
|
+
* Parity with PHP `Request::isSecureScheme` (tina4-php#175). Spoofable when the
|
|
211
|
+
* app is directly reachable, but the failure mode is self-limiting: a spoofed
|
|
212
|
+
* `https` only makes the cookie MORE restrictive, and `request.ts` already
|
|
213
|
+
* trusts the same header for URL construction — honouring it here is consistent.
|
|
214
|
+
*
|
|
215
|
+
* @param forwardedProto Raw `x-forwarded-proto` value (or a resolved scheme like
|
|
216
|
+
* "https"/"http"); "" / undefined means "absent".
|
|
217
|
+
* @param socketEncrypted True when Node terminated TLS itself (direct https, no
|
|
218
|
+
* proxy) — the native fallback when no forwarded header.
|
|
219
|
+
*/
|
|
220
|
+
export declare function isSecureScheme(forwardedProto?: string, socketEncrypted?: boolean): boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Resolve the session cookie name — the single source of truth shared by the
|
|
223
|
+
* WRITE side (`buildSessionCookie` / `Session.cookieHeader`) and the READ side
|
|
224
|
+
* (the auto-session cookie parse in `server.ts`), so a cookie written under a
|
|
225
|
+
* renamed name is read back on the next request.
|
|
226
|
+
*
|
|
227
|
+
* TINA4_SESSION_NAME Cookie name (default: "tina4_session")
|
|
228
|
+
*
|
|
229
|
+
* Keeping this in one place means the default can never drift between the two
|
|
230
|
+
* sides: an operator who sets `TINA4_SESSION_NAME` renames the cookie on both
|
|
231
|
+
* the emit and the parse paths at once. Parity with Python
|
|
232
|
+
* `session.session_cookie_name()`.
|
|
233
|
+
*/
|
|
234
|
+
export declare function sessionCookieName(): string;
|
|
235
|
+
/**
|
|
236
|
+
* Build the `Set-Cookie` header value for a Tina4 session. Centralised so
|
|
237
|
+
* the auto-cookie path in server.ts and `Session.cookieHeader()` agree on
|
|
238
|
+
* which env vars are honoured and what the defaults are.
|
|
239
|
+
*
|
|
240
|
+
* Env vars (Python parity):
|
|
241
|
+
* TINA4_SESSION_NAME — cookie name (default: "tina4_session")
|
|
242
|
+
* TINA4_SESSION_SAMESITE — SameSite attribute (default: "Lax")
|
|
243
|
+
* TINA4_SESSION_HTTPONLY — emit HttpOnly (default: true)
|
|
244
|
+
* TINA4_SESSION_SECURE — emit Secure (default: false; SameSite=None forces it on)
|
|
245
|
+
*
|
|
246
|
+
* `Secure` is emitted when ANY of: TINA4_SESSION_SECURE is truthy; SameSite is
|
|
247
|
+
* `None` (browsers reject a None cookie without Secure); OR the request scheme
|
|
248
|
+
* is https, detected proxy-aware from `forwardedProto` / `socketEncrypted`. The
|
|
249
|
+
* auto-cookie path in server.ts threads the request's scheme in so an HTTPS
|
|
250
|
+
* deploy behind a TLS-terminating proxy ships Secure without the operator
|
|
251
|
+
* having to know about TINA4_SESSION_SECURE (nodejs#34). Plain HTTP with no
|
|
252
|
+
* proxy header and no native TLS stays NOT Secure — an eager Secure would make
|
|
253
|
+
* http://localhost dev cookies undeliverable.
|
|
254
|
+
*/
|
|
255
|
+
export declare function buildSessionCookie(sessionId: string | null, ttl: number, cookieName?: string, forwardedProto?: string, socketEncrypted?: boolean): string;
|
|
256
|
+
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;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { SessionHandler } from "../session.js";
|
|
2
|
+
interface SessionData {
|
|
3
|
+
_created: number;
|
|
4
|
+
_accessed: number;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface DatabaseSessionConfig {
|
|
8
|
+
/** SQLite database file path (default: extracted from TINA4_DATABASE_URL or "data/tina4_sessions.db") */
|
|
9
|
+
dbPath?: string;
|
|
10
|
+
backend?: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
ttl?: number;
|
|
13
|
+
redisHost?: string;
|
|
14
|
+
redisPort?: number;
|
|
15
|
+
redisPassword?: string;
|
|
16
|
+
redisPrefix?: string;
|
|
17
|
+
redisDb?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Database session handler using node:sqlite (synchronous SQLite).
|
|
21
|
+
*
|
|
22
|
+
* Stores session data as JSON in a `tina4_session` table.
|
|
23
|
+
* Expiry is checked on read; expired rows are cleaned up lazily.
|
|
24
|
+
*/
|
|
25
|
+
export declare class DatabaseSessionHandler implements SessionHandler {
|
|
26
|
+
private db;
|
|
27
|
+
private initialized;
|
|
28
|
+
constructor(config?: DatabaseSessionConfig);
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the database file path from TINA4_DATABASE_URL or use the default.
|
|
31
|
+
*/
|
|
32
|
+
private resolveDbPath;
|
|
33
|
+
/**
|
|
34
|
+
* Ensure the session table exists (called once on first use).
|
|
35
|
+
*/
|
|
36
|
+
private ensureTable;
|
|
37
|
+
read(sessionId: string): SessionData | null;
|
|
38
|
+
write(sessionId: string, data: SessionData, ttl: number): void;
|
|
39
|
+
destroy(sessionId: string): void;
|
|
40
|
+
gc(_maxLifetime: number): void;
|
|
41
|
+
}
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface MongoTarget {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
database: string;
|
|
5
|
+
collection: string;
|
|
6
|
+
}
|
|
7
|
+
/** Command args: `filter` (always), plus `data`/`last_accessed` for an update. */
|
|
8
|
+
export interface MongoCommandArgs {
|
|
9
|
+
filter: Record<string, unknown>;
|
|
10
|
+
data?: unknown;
|
|
11
|
+
last_accessed?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Run a session Mongo command synchronously and return the raw child stdout.
|
|
15
|
+
*
|
|
16
|
+
* - command "find" -> the matched document as a JSON string, or "__EMPTY__".
|
|
17
|
+
* - command "update" -> "__OK__" (upsert of `{_id, data, last_accessed}`).
|
|
18
|
+
* - command "delete" -> "__OK__".
|
|
19
|
+
*
|
|
20
|
+
* THROWS `<label> command failed: ...` on a transport failure (server unreachable,
|
|
21
|
+
* timeout) OR a Mongo command error (`ok != 1`), so the Session boundary can
|
|
22
|
+
* log-loud + degrade (or re-throw under strict mode).
|
|
23
|
+
*/
|
|
24
|
+
export declare function mongoCommandSync(target: MongoTarget, command: "find" | "update" | "delete", args: MongoCommandArgs, label?: string): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tina4 MongoDB Session Handler — MongoDB wire protocol via raw TCP, zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Stores session data in MongoDB using the MongoDB wire protocol directly.
|
|
5
|
+
* No `mongodb` or `mongoose` npm package required.
|
|
6
|
+
*
|
|
7
|
+
* Configure via environment variables:
|
|
8
|
+
* TINA4_SESSION_MONGO_HOST (default: "127.0.0.1")
|
|
9
|
+
* TINA4_SESSION_MONGO_PORT (default: 27017)
|
|
10
|
+
* TINA4_SESSION_MONGO_URI (overrides host/port if set)
|
|
11
|
+
* TINA4_SESSION_MONGO_USERNAME (optional)
|
|
12
|
+
* TINA4_SESSION_MONGO_PASSWORD (optional)
|
|
13
|
+
* TINA4_SESSION_MONGO_DB (default: "tina4_sessions")
|
|
14
|
+
* TINA4_SESSION_MONGO_COLLECTION (default: "sessions")
|
|
15
|
+
*/
|
|
16
|
+
import type { SessionHandler } from "../session.js";
|
|
17
|
+
interface SessionData {
|
|
18
|
+
_created: number;
|
|
19
|
+
_accessed: number;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
export interface MongoSessionConfig {
|
|
23
|
+
host?: string;
|
|
24
|
+
port?: number;
|
|
25
|
+
uri?: string;
|
|
26
|
+
username?: string;
|
|
27
|
+
password?: string;
|
|
28
|
+
database?: string;
|
|
29
|
+
collection?: string;
|
|
30
|
+
backend?: string;
|
|
31
|
+
path?: string;
|
|
32
|
+
ttl?: number;
|
|
33
|
+
redisHost?: string;
|
|
34
|
+
redisPort?: number;
|
|
35
|
+
redisPassword?: string;
|
|
36
|
+
redisPrefix?: string;
|
|
37
|
+
redisDb?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* MongoDB session handler using raw TCP (MongoDB wire protocol).
|
|
41
|
+
*
|
|
42
|
+
* Uses synchronous socket communication via child process — no external
|
|
43
|
+
* MongoDB client library required. Stores session data as BSON documents
|
|
44
|
+
* with TTL index support.
|
|
45
|
+
*/
|
|
46
|
+
export declare class MongoSessionHandler implements SessionHandler {
|
|
47
|
+
private host;
|
|
48
|
+
private port;
|
|
49
|
+
private uri;
|
|
50
|
+
private username;
|
|
51
|
+
private password;
|
|
52
|
+
private database;
|
|
53
|
+
private collection;
|
|
54
|
+
constructor(config?: MongoSessionConfig);
|
|
55
|
+
/** Resolve the effective host/port (honours a configured mongodb:// URI). */
|
|
56
|
+
private target;
|
|
57
|
+
read(sessionId: string): SessionData | null;
|
|
58
|
+
write(sessionId: string, data: SessionData, _ttl: number): void;
|
|
59
|
+
destroy(sessionId: string): void;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { SessionHandler } from "../session.js";
|
|
2
|
+
interface SessionData {
|
|
3
|
+
_created: number;
|
|
4
|
+
_accessed: number;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface RedisNpmSessionConfig {
|
|
8
|
+
host?: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
url?: string;
|
|
11
|
+
password?: string;
|
|
12
|
+
prefix?: string;
|
|
13
|
+
db?: number;
|
|
14
|
+
backend?: string;
|
|
15
|
+
path?: string;
|
|
16
|
+
ttl?: number;
|
|
17
|
+
redisHost?: string;
|
|
18
|
+
redisPort?: number;
|
|
19
|
+
redisPassword?: string;
|
|
20
|
+
redisPrefix?: string;
|
|
21
|
+
redisDb?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Redis session handler using the `redis` npm package.
|
|
25
|
+
*
|
|
26
|
+
* Falls back to raw TCP (RESP protocol) if the `redis` package is not
|
|
27
|
+
* installed, matching the approach used by the Valkey handler.
|
|
28
|
+
*
|
|
29
|
+
* Stores session data as JSON strings with Redis TTL for automatic expiry.
|
|
30
|
+
*/
|
|
31
|
+
export declare class RedisNpmSessionHandler implements SessionHandler {
|
|
32
|
+
private host;
|
|
33
|
+
private port;
|
|
34
|
+
private url;
|
|
35
|
+
private password;
|
|
36
|
+
private prefix;
|
|
37
|
+
private db;
|
|
38
|
+
constructor(config?: RedisNpmSessionConfig);
|
|
39
|
+
/** Resolve a host/port for the raw-RESP path (parses TINA4_SESSION_REDIS_URL if set). */
|
|
40
|
+
private resolveHostPort;
|
|
41
|
+
/**
|
|
42
|
+
* Execute a Redis command synchronously.
|
|
43
|
+
*
|
|
44
|
+
* Prefers the official `redis` driver when it is installed (the class's reason
|
|
45
|
+
* to exist); otherwise speaks raw RESP via the shared {@link respCommandSync}
|
|
46
|
+
* transport — same correct path as the Valkey handler, no `redis` dependency.
|
|
47
|
+
*
|
|
48
|
+
* A genuine key miss yields `""`; a transport/connection FAILURE (server
|
|
49
|
+
* unreachable, rejected AUTH, timeout) THROWS so the Session boundary can
|
|
50
|
+
* distinguish "not found" (silent) from "backend failed" (log-loud + degrade).
|
|
51
|
+
*/
|
|
52
|
+
private execSync;
|
|
53
|
+
/** Drive the command through the `redis` npm client in a short-lived child. */
|
|
54
|
+
private execViaNpm;
|
|
55
|
+
private key;
|
|
56
|
+
read(sessionId: string): SessionData | null;
|
|
57
|
+
write(sessionId: string, data: SessionData, ttl: number): void;
|
|
58
|
+
destroy(sessionId: string): void;
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface RespTarget {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
/** AUTH password (empty/undefined = no AUTH sent). */
|
|
5
|
+
password?: string;
|
|
6
|
+
/** SELECT db index (0/undefined = no SELECT sent). */
|
|
7
|
+
db?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Run a single RESP command synchronously against host:port and return the reply.
|
|
11
|
+
*
|
|
12
|
+
* - A genuine nil / key-miss returns `""` (callers treat "" as "no session yet").
|
|
13
|
+
* - A transport FAILURE (server unreachable, timeout, connection closed before a
|
|
14
|
+
* reply) THROWS `<label> command failed: ...`.
|
|
15
|
+
* - A RESP error reply — including a rejected AUTH/SELECT handshake — THROWS
|
|
16
|
+
* `<label> error: ...`. A rejected handshake is a transport failure, not a
|
|
17
|
+
* result, so it is surfaced ahead of the command reply.
|
|
18
|
+
*
|
|
19
|
+
* Session values are JSON strings (they start with `{`), so they never collide
|
|
20
|
+
* with the `__NULL__` / `__ERR__` sentinels the child uses on stdout.
|
|
21
|
+
*/
|
|
22
|
+
export declare function respCommandSync(target: RespTarget, args: string[], label?: string): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tina4 Valkey Session Handler — Valkey (Redis-compatible) via raw TCP, zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Same as the Redis handler but uses VALKEY-prefixed configuration variables.
|
|
5
|
+
* Valkey is a Redis-compatible key-value store fork.
|
|
6
|
+
*
|
|
7
|
+
* Configure via environment variables:
|
|
8
|
+
* TINA4_SESSION_VALKEY_HOST (default: "127.0.0.1")
|
|
9
|
+
* TINA4_SESSION_VALKEY_PORT (default: 6379)
|
|
10
|
+
* TINA4_SESSION_VALKEY_PASSWORD (optional)
|
|
11
|
+
* TINA4_SESSION_VALKEY_PREFIX (default: "tina4:session:")
|
|
12
|
+
* TINA4_SESSION_VALKEY_DB (default: 0)
|
|
13
|
+
*/
|
|
14
|
+
import type { SessionHandler } from "../session.js";
|
|
15
|
+
interface SessionData {
|
|
16
|
+
_created: number;
|
|
17
|
+
_accessed: number;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
export interface ValkeySessionConfig {
|
|
21
|
+
host?: string;
|
|
22
|
+
port?: number;
|
|
23
|
+
password?: string;
|
|
24
|
+
prefix?: string;
|
|
25
|
+
db?: number;
|
|
26
|
+
backend?: string;
|
|
27
|
+
path?: string;
|
|
28
|
+
ttl?: number;
|
|
29
|
+
redisHost?: string;
|
|
30
|
+
redisPort?: number;
|
|
31
|
+
redisPassword?: string;
|
|
32
|
+
redisPrefix?: string;
|
|
33
|
+
redisDb?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Valkey session handler using raw TCP (RESP protocol).
|
|
37
|
+
*
|
|
38
|
+
* Uses synchronous socket communication — no external Valkey/Redis client required.
|
|
39
|
+
* Stores session data as JSON strings with Valkey TTL for automatic expiry.
|
|
40
|
+
*
|
|
41
|
+
* Valkey uses the same RESP protocol as Redis, so this handler is functionally
|
|
42
|
+
* identical to RedisSessionHandler but with VALKEY config variable names.
|
|
43
|
+
*/
|
|
44
|
+
export declare class ValkeySessionHandler implements SessionHandler {
|
|
45
|
+
private host;
|
|
46
|
+
private port;
|
|
47
|
+
private password;
|
|
48
|
+
private prefix;
|
|
49
|
+
private db;
|
|
50
|
+
constructor(config?: ValkeySessionConfig);
|
|
51
|
+
/**
|
|
52
|
+
* Execute a RESP command synchronously against the live Valkey server.
|
|
53
|
+
*
|
|
54
|
+
* Delegates to the shared {@link respCommandSync} transport: a genuine key miss
|
|
55
|
+
* yields `""`, and a transport/connection FAILURE (server unreachable, rejected
|
|
56
|
+
* AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
|
|
57
|
+
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
58
|
+
*/
|
|
59
|
+
private execSync;
|
|
60
|
+
private key;
|
|
61
|
+
read(sessionId: string): SessionData | null;
|
|
62
|
+
write(sessionId: string, data: SessionData, ttl: number): void;
|
|
63
|
+
destroy(sessionId: string): void;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tina4 — The Intelligent Native Application 4ramework
|
|
3
|
+
* Copyright 2007 - current Tina4
|
|
4
|
+
* License: MIT https://opensource.org/licenses/MIT
|
|
5
|
+
*
|
|
6
|
+
* Tina4 xUnit-style Test base class.
|
|
7
|
+
*
|
|
8
|
+
* Chapter 18 of the documentation has long shown:
|
|
9
|
+
*
|
|
10
|
+
* class UserApiTest extends Tina4Test {
|
|
11
|
+
* async testHealth() {
|
|
12
|
+
* const resp = await this.get("/health");
|
|
13
|
+
* this.assertEqual(resp.status, 200);
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Until 3.13.0 this class did not exist — examples crashed with
|
|
18
|
+
* "ReferenceError: Tina4Test is not defined". This is the Node.js
|
|
19
|
+
* parity of the Python `tina4_python.test.Test`, PHP `Tina4\Test`,
|
|
20
|
+
* and Ruby `Tina4::Test` classes shipped at the same time.
|
|
21
|
+
*
|
|
22
|
+
* The class has a built-in runner (`Tina4Test.runAll`) so the docs'
|
|
23
|
+
* `npx tina4nodejs test` flow can discover every subclass without
|
|
24
|
+
* an external test framework. HTTP helpers (get/post/put/patch/delete)
|
|
25
|
+
* delegate to a lazy TestClient. Positional assertions match the
|
|
26
|
+
* cross-framework (actual, expected, message) shape.
|
|
27
|
+
*/
|
|
28
|
+
import { TestClient, type TestResponse, type RequestOptions } from "./testClient.js";
|
|
29
|
+
/** Raised by Tina4Test assertion helpers when an assertion fails. */
|
|
30
|
+
export declare class AssertionError extends Error {
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
|
33
|
+
/** Result of running a Tina4Test subclass (or all subclasses). */
|
|
34
|
+
export interface TestRunResults {
|
|
35
|
+
passed: number;
|
|
36
|
+
failed: number;
|
|
37
|
+
errors: number;
|
|
38
|
+
details: Array<{
|
|
39
|
+
suite: string;
|
|
40
|
+
test: string;
|
|
41
|
+
status: "passed" | "failed" | "error";
|
|
42
|
+
message?: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Tina4 xUnit-style test base class — class-based suites with HTTP
|
|
47
|
+
* helpers and positional assertions, zero external deps.
|
|
48
|
+
*
|
|
49
|
+
* Subclass and define `test*` methods:
|
|
50
|
+
*
|
|
51
|
+
* class BasicTest extends Tina4Test {
|
|
52
|
+
* async testAddition() {
|
|
53
|
+
* this.assertEqual(2 + 2, 4, "addition works");
|
|
54
|
+
* }
|
|
55
|
+
* async testHttpHealth() {
|
|
56
|
+
* const resp = await this.get("/health");
|
|
57
|
+
* this.assertEqual(resp.status, 200);
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* const results = await Tina4Test.runAll();
|
|
62
|
+
* // → { passed, failed, errors, details }
|
|
63
|
+
*/
|
|
64
|
+
export declare class Tina4Test {
|
|
65
|
+
private _client;
|
|
66
|
+
/** snake_case lifecycle hook — runs before each test. Override in subclasses. */
|
|
67
|
+
setUp(): Promise<void>;
|
|
68
|
+
/** snake_case lifecycle hook — runs after each test. Override in subclasses. */
|
|
69
|
+
tearDown(): Promise<void>;
|
|
70
|
+
/** The lazily-created TestClient instance shared by this suite's tests. */
|
|
71
|
+
protected get client(): TestClient;
|
|
72
|
+
get(path: string, options?: RequestOptions): Promise<TestResponse>;
|
|
73
|
+
post(path: string, options?: RequestOptions): Promise<TestResponse>;
|
|
74
|
+
put(path: string, options?: RequestOptions): Promise<TestResponse>;
|
|
75
|
+
patch(path: string, options?: RequestOptions): Promise<TestResponse>;
|
|
76
|
+
delete(path: string, options?: RequestOptions): Promise<TestResponse>;
|
|
77
|
+
assertEqual(actual: unknown, expected: unknown, message?: string): void;
|
|
78
|
+
assertNotEqual(actual: unknown, expected: unknown, message?: string): void;
|
|
79
|
+
assertTrue(value: unknown, message?: string): void;
|
|
80
|
+
assertFalse(value: unknown, message?: string): void;
|
|
81
|
+
assertNull(value: unknown, message?: string): void;
|
|
82
|
+
assertNotNull(value: unknown, message?: string): void;
|
|
83
|
+
assertRaises(expectedClass: new (...args: never[]) => Error, fn: () => unknown | Promise<unknown>, message?: string): Promise<void>;
|
|
84
|
+
/** Register a subclass for discovery. Called automatically via `extends Tina4Test`. */
|
|
85
|
+
static register(klass: typeof Tina4Test): void;
|
|
86
|
+
/** Run every `test*` method on this class. Returns counts and per-test details. */
|
|
87
|
+
static run(this: typeof Tina4Test): Promise<TestRunResults>;
|
|
88
|
+
/** Run every Tina4Test subclass discovered via auto-registration. */
|
|
89
|
+
static runAll(options?: {
|
|
90
|
+
quiet?: boolean;
|
|
91
|
+
}): Promise<TestRunResults>;
|
|
92
|
+
/** Subclasses array — read-only view used by tests. */
|
|
93
|
+
static get subclasses(): ReadonlyArray<typeof Tina4Test>;
|
|
94
|
+
}
|