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,119 @@
1
+ import type { QueueJob } from "../queue.js";
2
+ export interface MongoConfig {
3
+ host?: string;
4
+ port?: number;
5
+ uri?: string;
6
+ username?: string;
7
+ password?: string;
8
+ database?: string;
9
+ collection?: string;
10
+ /**
11
+ * Reservation/visibility timeout (seconds). A dequeued message is held
12
+ * reserved with availableAt = now + timeout; reclaim returns it once that
13
+ * passes (consumer died mid-flight, before complete()/fail()). <= 0 disables
14
+ * the reclaim. Falls back to TINA4_QUEUE_VISIBILITY_TIMEOUT, else 300.
15
+ */
16
+ visibilityTimeout?: number;
17
+ /** Max attempts before the reclaim dead-letters a job instead of re-delivering. */
18
+ maxRetries?: number;
19
+ }
20
+ export interface QueueBackend {
21
+ push(queue: string, payload: unknown, delay?: number, priority?: number): string;
22
+ pop(queue: string): QueueJob | null;
23
+ size(queue: string): number;
24
+ clear(queue: string): void;
25
+ /** Release whatever connection the backend holds. Must be idempotent. */
26
+ close(): void;
27
+ }
28
+ /**
29
+ * MongoDB queue backend using the `mongodb` npm package.
30
+ *
31
+ * Uses synchronous-style communication by spawning a child process
32
+ * for each operation, similar to the RabbitMQ and Redis patterns.
33
+ * This keeps the interface synchronous as required by the Queue class.
34
+ */
35
+ export declare class MongoBackend implements QueueBackend {
36
+ private host;
37
+ private port;
38
+ private uri;
39
+ private username;
40
+ private password;
41
+ private database;
42
+ private collection;
43
+ private visibilityTimeout;
44
+ private maxRetries;
45
+ constructor(config?: MongoConfig);
46
+ /**
47
+ * Resolved connection config — exposed for testing/introspection.
48
+ */
49
+ getConfig(): {
50
+ uri: string;
51
+ database: string;
52
+ collection: string;
53
+ visibilityTimeout: number;
54
+ };
55
+ /**
56
+ * Resolved reservation/visibility timeout (seconds). <= 0 disables the
57
+ * reclaim. Exposed for testing/introspection.
58
+ */
59
+ getVisibilityTimeout(): number;
60
+ /**
61
+ * Build the Node script that performs one MongoDB queue operation in a child
62
+ * process. Exposed (not private) so tests can assert the visibility-timeout
63
+ * behaviour without a live MongoDB — the script's pop branch advances
64
+ * availableAt = now + visibilityTimeout and stamps reservedAt (the core fix),
65
+ * and the reclaim branch flips an expired { status: reserved } back to
66
+ * pending with attempts incremented (dead-lettering past maxRetries),
67
+ * disabled when visibilityTimeout <= 0.
68
+ */
69
+ buildScript(operation: string, queue: string, data?: string): string;
70
+ /**
71
+ * Execute a MongoDB operation synchronously via a child process.
72
+ */
73
+ private execSync;
74
+ popById(queue: string, id: string): QueueJob | null;
75
+ push(queue: string, payload: unknown, delay?: number, priority?: number): string;
76
+ pop(queue: string): QueueJob | null;
77
+ size(queue: string): number;
78
+ clear(queue: string): void;
79
+ /**
80
+ * Acknowledge a completed job — drop its reservation so the reclaim never
81
+ * re-delivers it. Without this a Mongo-popped job stayed reserved and was
82
+ * re-delivered after the visibility window (the redelivery bug).
83
+ */
84
+ complete(queue: string, id: string): void;
85
+ /**
86
+ * Record a failed attempt: requeue (reset availableAt, ++attempts) while
87
+ * retries remain, else dead-letter. Mirrors the file/lite backend.
88
+ */
89
+ fail(queue: string, id: string, error: string, maxRetries: number, retryBackoff?: number): void;
90
+ /** Explicit manual re-queue (always re-enqueues regardless of the retry limit). */
91
+ retry(queue: string, id: string, delaySeconds?: number): void;
92
+ /** Jobs that exceeded max retries (the `<queue>.dead_letter` collection topic). */
93
+ deadLetters(queue: string, maxRetries?: number): QueueJob[];
94
+ /** Jobs that failed but are still eligible for retry (status=failed, attempts < max). */
95
+ failed(queue: string, maxRetries?: number): QueueJob[];
96
+ /** Revive dead-lettered jobs under the (possibly raised) limit. Returns count revived. */
97
+ retryFailed(queue: string, maxRetries?: number): number;
98
+ /** Remove jobs by status (default: every doc for the topic). Returns count removed. */
99
+ purge(queue: string, status?: string): number;
100
+ /**
101
+ * Release the MongoDB connection. Idempotent — a second call is a no-op.
102
+ *
103
+ * HONEST CAVEAT, and it is the whole reason ADR-0022 exists: THIS backend
104
+ * holds no connection between calls to release. Every operation runs in its
105
+ * own child process (see execSync/buildScript), and that child's `finally`
106
+ * already does `await client.close()` before it exits — so the pool it opened
107
+ * is gone by the time the method returns. Unlike tina4-python, tina4-php and
108
+ * tina4-ruby, whose Mongo/broker backends hold a long-lived client that this
109
+ * method genuinely hands back, Node has nothing to give back.
110
+ *
111
+ * It is implemented anyway, and required by the QueueBackend interface,
112
+ * because the CONTRACT is what matters: `Queue.close()` must be callable on
113
+ * every backend in every framework, and the day the persistent-connection
114
+ * rewrite lands (ADR-0022's tracked fix) the client goes here with no change
115
+ * at any call site. A method that is a no-op today and correct forever beats
116
+ * a missing method the caller has to feature-detect.
117
+ */
118
+ close(): void;
119
+ }
@@ -0,0 +1,55 @@
1
+ import type { QueueJob } from "../queue.js";
2
+ export interface RabbitMQConfig {
3
+ host?: string;
4
+ port?: number;
5
+ username?: string;
6
+ password?: string;
7
+ vhost?: string;
8
+ /**
9
+ * Accepted for API parity with the file/MongoDB backends and IGNORED — the
10
+ * broker owns redelivery (unacked messages requeue on channel close), so the
11
+ * framework-level visibility timeout does not apply here.
12
+ */
13
+ visibilityTimeout?: number;
14
+ }
15
+ /**
16
+ * Parse an AMQP URL (amqp://[user:pass@]host[:port][/vhost]) into a partial
17
+ * RabbitMQConfig. Mirrors the Python/PHP/Ruby `parse_amqp_url` semantics:
18
+ * strips a leading amqp:// or amqps:// scheme, splits optional credentials,
19
+ * and reads the path segment as the URL-decoded vhost name. Only fields
20
+ * present in the URL are populated.
21
+ */
22
+ export declare function parseAmqpUrl(url: string): RabbitMQConfig;
23
+ export interface QueueBackend {
24
+ push(queue: string, payload: unknown, delay?: number, priority?: number): string;
25
+ pop(queue: string): QueueJob | null;
26
+ size(queue: string): number;
27
+ clear(queue: string): void;
28
+ }
29
+ /**
30
+ * RabbitMQ queue backend using raw AMQP 0-9-1 protocol.
31
+ *
32
+ * Uses synchronous-style communication by spawning a child process
33
+ * for each operation, similar to the Redis session handler pattern.
34
+ * This keeps the interface synchronous as required by the Queue class.
35
+ */
36
+ export declare class RabbitMQBackend implements QueueBackend {
37
+ private host;
38
+ private port;
39
+ private username;
40
+ private password;
41
+ private vhost;
42
+ constructor(config?: RabbitMQConfig);
43
+ /**
44
+ * Resolved connection config — exposed for testing/introspection.
45
+ */
46
+ getConfig(): Required<Omit<RabbitMQConfig, "visibilityTimeout">>;
47
+ /**
48
+ * Execute an AMQP operation synchronously via a child process.
49
+ */
50
+ private execSync;
51
+ push(queue: string, payload: unknown, _delay?: number): string;
52
+ pop(queue: string): QueueJob | null;
53
+ size(queue: string): number;
54
+ clear(queue: string): void;
55
+ }
@@ -0,0 +1,49 @@
1
+ import type { Middleware, Tina4Request, Tina4Response } from "./types.js";
2
+ /** Configuration for the rate limiter */
3
+ export interface RateLimiterConfig {
4
+ /** Maximum number of requests per window. Default: 100 (or TINA4_RATE_LIMIT env) */
5
+ limit?: number;
6
+ /** Window duration in seconds. Default: 60 (or TINA4_RATE_WINDOW env) */
7
+ windowSeconds?: number;
8
+ /** Cleanup interval in milliseconds. Default: 60000 (1 minute) */
9
+ cleanupIntervalMs?: number;
10
+ }
11
+ /**
12
+ * Create a rate limiter middleware using a sliding window algorithm.
13
+ * Tracks requests per IP in an in-memory Map.
14
+ *
15
+ * Response headers:
16
+ * X-RateLimit-Limit — Maximum requests per window
17
+ * X-RateLimit-Remaining — Requests remaining in the current window
18
+ * X-RateLimit-Reset — Unix timestamp (seconds) when the window resets
19
+ * Retry-After — Seconds to wait (only when rate limited)
20
+ *
21
+ * Returns 429 Too Many Requests when the limit is exceeded.
22
+ */
23
+ export declare function rateLimiter(config?: RateLimiterConfig): Middleware;
24
+ /** Rate limit check result */
25
+ export interface RateLimitResult {
26
+ allowed: boolean;
27
+ limit: number;
28
+ remaining: number;
29
+ reset: number;
30
+ retryAfter?: number;
31
+ }
32
+ /**
33
+ * Class-based rate limiter with check/reset/apply methods.
34
+ * Matches the Python/PHP/Ruby API surface.
35
+ */
36
+ export declare class RateLimiter {
37
+ readonly limit: number;
38
+ readonly window: number;
39
+ private store;
40
+ constructor(config?: RateLimiterConfig);
41
+ /** Check if a request from the given IP is allowed. */
42
+ check(ip: string): RateLimitResult;
43
+ /** Clear all tracked request data. */
44
+ reset(): void;
45
+ /** Apply rate limiting to a request/response pair. Sets headers and 429 if exceeded. */
46
+ apply(request: Tina4Request, response: Tina4Response): [Tina4Request, Tina4Response];
47
+ /** Middleware hook — enforces rate limiting before the route handler. */
48
+ beforeRateLimit(request: Tina4Request, response: Tina4Response): [Tina4Request, Tina4Response];
49
+ }
@@ -0,0 +1,25 @@
1
+ import type { IncomingMessage, IncomingHttpHeaders } from "node:http";
2
+ import type { Tina4Request, UploadedFile } from "./types.js";
3
+ /**
4
+ * Wrap Node's `IncomingHttpHeaders` in a Proxy so mixed-case lookups
5
+ * (`req.headers["Content-Type"]`) work alongside the canonical lowercase
6
+ * form Node already provides. Parity with PY-10-03 (Python ships a
7
+ * `CaseInsensitiveDict` for the same reason).
8
+ *
9
+ * The raw object is returned as-is by `Object.keys` / iteration — only
10
+ * string property reads/`in` checks are normalised.
11
+ */
12
+ export declare function makeCaseInsensitiveHeaders(raw: IncomingHttpHeaders): IncomingHttpHeaders;
13
+ export declare function createRequest(req: IncomingMessage): Tina4Request;
14
+ export declare class PayloadTooLargeError extends Error {
15
+ statusCode: number;
16
+ constructor(actual: number, limit: number);
17
+ }
18
+ /**
19
+ * Parse multipart/form-data body into fields and files.
20
+ * Zero-dependency implementation.
21
+ */
22
+ export declare function parseMultipart(body: Buffer, boundary: string): {
23
+ fields: Record<string, string>;
24
+ files: Record<string, UploadedFile | UploadedFile[]>;
25
+ };
@@ -0,0 +1,28 @@
1
+ import type { ServerResponse } from "node:http";
2
+ import type { Tina4Response } from "./types.js";
3
+ /**
4
+ * Set the default templates directory for render().
5
+ * Called by server.ts during startup.
6
+ */
7
+ export declare function setDefaultTemplatesDir(dir: string): void;
8
+ /**
9
+ * Return the global Frond engine, creating a default if needed.
10
+ */
11
+ export declare function getFrond(): Promise<InstanceType<any>>;
12
+ /**
13
+ * Return the singleton Frond engine for built-in framework templates.
14
+ * Syncs custom filters/globals from the user engine.
15
+ */
16
+ export declare function getFrameworkFrond(): Promise<InstanceType<any> | null>;
17
+ /**
18
+ * Register a pre-configured Frond engine for response.render().
19
+ */
20
+ export declare function setFrond(engine: InstanceType<any>): void;
21
+ export declare function createResponse(res: ServerResponse): Tina4Response;
22
+ /**
23
+ * Build a standard error response envelope (standalone helper).
24
+ *
25
+ * Usage:
26
+ * return response(errorResponse("VALIDATION_FAILED", "Email is required", 400), 400);
27
+ */
28
+ export declare function errorResponse(code: string, message: string, status?: number): Record<string, unknown>;
@@ -0,0 +1,12 @@
1
+ import type { RouteDefinition } from "./types.js";
2
+ export declare function discoverRoutes(routesDir: string): Promise<RouteDefinition[]>;
3
+ /**
4
+ * Re-run the most recent route scan — called by POST /__dev/api/reload so a
5
+ * newly-added OR edited file in src/routes/ registers without a server restart.
6
+ * A file is re-imported when it's new or its mtime increased; unchanged files
7
+ * are skipped. The router replaces routes by pattern, so a re-imported route
8
+ * overwrites the stale handler. No-op if discoverRoutes() has never been called.
9
+ */
10
+ export declare function rediscoverRoutes(): Promise<RouteDefinition[]>;
11
+ /** Test-only: reset the seen-files state so tests can replay the same dir. */
12
+ export declare function _resetRouteDiscovery(): void;
@@ -0,0 +1,366 @@
1
+ import type { RouteHandler, RouteDefinition, RouteMeta, Middleware, MiddlewareSpec, Tina4Request, Tina4Response, WebSocketRouteHandler, WebSocketRouteDefinition } from "./types.js";
2
+ /**
3
+ * Whether `TINA4_TRAILING_SLASH_REDIRECT` is enabled.
4
+ *
5
+ * Default: false. When true, a request to `/foo/` that has no exact match
6
+ * but matches `/foo` will be treated as a hit on `/foo` — callers can use
7
+ * the returned pattern to issue a 308 redirect (Python parity).
8
+ */
9
+ export declare function isTrailingSlashRedirectEnabled(): boolean;
10
+ interface MatchResult {
11
+ handler: RouteHandler;
12
+ params: Record<string, string | number>;
13
+ pattern: string;
14
+ meta?: RouteMeta;
15
+ middlewares?: MiddlewareSpec[];
16
+ template?: string;
17
+ secure?: boolean;
18
+ cached?: boolean;
19
+ noAuth?: boolean;
20
+ }
21
+ interface CompiledRoute {
22
+ pattern: string;
23
+ regex: RegExp;
24
+ paramNames: string[];
25
+ paramTypes: string[];
26
+ handler: RouteHandler;
27
+ meta?: RouteMeta;
28
+ filePath?: string;
29
+ middlewares?: MiddlewareSpec[];
30
+ secure?: boolean;
31
+ cached?: boolean;
32
+ noAuth?: boolean;
33
+ cacheStore?: Map<string, {
34
+ data: unknown;
35
+ expires: number;
36
+ }>;
37
+ cacheTtl?: number;
38
+ template?: string;
39
+ }
40
+ /**
41
+ * Thin reference to a registered WebSocket route, enabling chained modifiers
42
+ * — the WS analogue of {@link RouteRef}.
43
+ *
44
+ * Usage:
45
+ * router.websocket("/ws/secure", handler).secure();
46
+ */
47
+ export declare class WsRouteRef {
48
+ private route;
49
+ constructor(route: WebSocketRouteDefinition);
50
+ /** Mark this WS route as requiring a valid JWT on the upgrade handshake. */
51
+ secure(): this;
52
+ }
53
+ /**
54
+ * Thin reference to a registered route, enabling chained modifiers.
55
+ *
56
+ * Usage:
57
+ * router.get("/api/data", handler).secure().cache();
58
+ */
59
+ export declare class RouteRef {
60
+ private route;
61
+ constructor(route: CompiledRoute);
62
+ /** Mark this route as requiring bearer-token authentication. */
63
+ secure(): this;
64
+ /** Opt out of secure-by-default auth (for public write routes). */
65
+ noAuth(): this;
66
+ /** Mark this route's response as cacheable. */
67
+ cache(): this;
68
+ /**
69
+ * Append middleware to this route. Accepts middleware functions and/or
70
+ * string specs (e.g. `"ResponseCache:300"`), resolved when the route runs.
71
+ */
72
+ middleware(...middlewareClasses: MiddlewareSpec[]): this;
73
+ }
74
+ export interface RouteInfo {
75
+ method: string;
76
+ path: string;
77
+ handler: string;
78
+ middlewareCount: number;
79
+ cached: boolean;
80
+ secure: boolean;
81
+ }
82
+ export declare class Router {
83
+ private routes;
84
+ private wsRoutes;
85
+ /** Class-based middleware registered via `use()` / `Router.use()`. */
86
+ private static _classMiddlewares;
87
+ /**
88
+ * Register a class-based middleware (beforeX / afterX convention).
89
+ * Classes are stored globally and executed by MiddlewareRunner.
90
+ */
91
+ static use(middlewareClass: any): void;
92
+ /**
93
+ * Get all registered class-based middleware classes.
94
+ */
95
+ static getClassMiddlewares(): any[];
96
+ /**
97
+ * Clear all registered class-based middleware (useful for testing).
98
+ */
99
+ static clearClassMiddlewares(): void;
100
+ /**
101
+ * Add a raw route definition (used internally and by file-based routing).
102
+ */
103
+ addRoute(definition: RouteDefinition): RouteRef;
104
+ /**
105
+ * Register a GET route programmatically.
106
+ */
107
+ get(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
108
+ /**
109
+ * Register a POST route programmatically.
110
+ */
111
+ post(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
112
+ /**
113
+ * Register a PUT route programmatically.
114
+ */
115
+ put(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
116
+ /**
117
+ * Register a PATCH route programmatically.
118
+ */
119
+ patch(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
120
+ /**
121
+ * Register a DELETE route programmatically.
122
+ */
123
+ delete(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
124
+ /**
125
+ * Register an explicit HEAD route. By default the framework auto-handles
126
+ * HEAD by falling back to the GET route and stripping the body
127
+ * (RFC 9110 §9.3.2). Use this only when you need a HEAD handler that
128
+ * does something different from GET — e.g. cheaper existence-check
129
+ * logic, custom validator headers without the cost of building the body.
130
+ * The framework still strips the response body for you on the way out.
131
+ */
132
+ head(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
133
+ /**
134
+ * Register an explicit OPTIONS route. By default the framework auto-
135
+ * handles OPTIONS by building an Allow header from every method
136
+ * registered for the path and returning 204 (RFC 9110 §9.3.7). Use
137
+ * this to take over that behaviour.
138
+ */
139
+ options(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
140
+ /**
141
+ * Register a route that matches ANY HTTP method.
142
+ */
143
+ any(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
144
+ /**
145
+ * Create a route group with a shared prefix and optional middlewares.
146
+ */
147
+ group(prefix: string, callback: (group: RouteGroup) => void, middlewares?: MiddlewareSpec[]): void;
148
+ /**
149
+ * Match a request method + pathname to a registered route.
150
+ *
151
+ * When `TINA4_TRAILING_SLASH_REDIRECT=true` and the request path ends in
152
+ * a trailing slash that doesn't match a registered route, retry without
153
+ * the trailing slash. Returning the de-slashed pattern lets callers issue
154
+ * a 308 redirect instead of a hard 404 — Python parity.
155
+ */
156
+ match(method: string, path: string): MatchResult | null;
157
+ /**
158
+ * Return the list of HTTP methods registered for ``path``, in canonical
159
+ * order GET / POST / PUT / PATCH / DELETE / HEAD / OPTIONS. Used by the
160
+ * dispatcher to build the ``Allow:`` header on 405 / OPTIONS responses
161
+ * (RFC 9110 §10.2.1, §9.3.7).
162
+ *
163
+ * If GET is registered, HEAD is appended implicitly (HEAD auto-fallback).
164
+ * OPTIONS is appended whenever any method exists for the path (the
165
+ * framework auto-handles OPTIONS).
166
+ */
167
+ methodsAllowedForPath(path: string): string[];
168
+ /** Inner match against a list of compiled routes, no trailing-slash logic. */
169
+ private matchRoute;
170
+ /**
171
+ * Get all registered route definitions.
172
+ */
173
+ getRoutes(): RouteDefinition[];
174
+ /** Alias for getRoutes(). */
175
+ allRoutes(): RouteDefinition[];
176
+ /**
177
+ * List all routes in a debug-friendly format for CLI output.
178
+ */
179
+ listRoutes(): RouteInfo[];
180
+ /**
181
+ * Register a WebSocket route.
182
+ *
183
+ * A WS route is PUBLIC by default (mirrors GET). It can be marked secured in
184
+ * EITHER way the HTTP routes support:
185
+ * • imperatively — `websocket(path, fn, { secured: true })`, or chain the
186
+ * returned ref: `websocket(path, fn).secure()`;
187
+ * • decorator-style — a `_secured` flag on the handler function, set in
188
+ * either order relative to registration (the ref keeps a back-reference
189
+ * to the route so a later `.secure()` / `_secured` still flips it).
190
+ *
191
+ * When secured, the upgrade handshake requires a valid JWT (Authorization
192
+ * header / `bearer` subprotocol / `?token=`) or the upgrade is rejected.
193
+ */
194
+ websocket(path: string, handler: WebSocketRouteHandler, options?: {
195
+ secured?: boolean;
196
+ }): WsRouteRef;
197
+ /**
198
+ * Get all registered WebSocket route definitions.
199
+ */
200
+ getWebSocketRoutes(): WebSocketRouteDefinition[];
201
+ /**
202
+ * Match a WebSocket upgrade request path to a registered ws route.
203
+ * Returns the route only; use {@link matchWebSocketWithParams} when the
204
+ * upgrade handler needs the extracted `{param}` values.
205
+ */
206
+ matchWebSocket(pathname: string): WebSocketRouteDefinition | null;
207
+ /**
208
+ * Match a WebSocket upgrade path AND extract its `{param}` values, using the
209
+ * same pattern compiler as HTTP routes. A literal pattern (`/ws/chat`) still
210
+ * matches exactly with empty params; a parameterised pattern
211
+ * (`/ws/rtc/{room}`) matches `/ws/rtc/abc` and yields `{ room: "abc" }`.
212
+ * (Previously WS matching was exact-string only, so `{param}` routes never
213
+ * matched and `connection.params` was always empty.)
214
+ */
215
+ matchWebSocketWithParams(pathname: string): {
216
+ route: WebSocketRouteDefinition;
217
+ params: Record<string, string>;
218
+ } | null;
219
+ clear(): void;
220
+ /**
221
+ * Register a route for a specific HTTP method.
222
+ * Core registration method — all convenience methods delegate here.
223
+ */
224
+ static add(method: string, path: string, handler: RouteHandler, middleware?: MiddlewareSpec[], swaggerMeta?: RouteMeta, template?: string): RouteRef;
225
+ /**
226
+ * Register a GET route on the default global router.
227
+ */
228
+ static get(path: string, handler: RouteHandler, middleware?: MiddlewareSpec[], swaggerMeta?: RouteMeta, template?: string): RouteRef;
229
+ /**
230
+ * Register a POST route on the default global router.
231
+ */
232
+ static post(path: string, handler: RouteHandler, middleware?: MiddlewareSpec[], swaggerMeta?: RouteMeta, template?: string): RouteRef;
233
+ /**
234
+ * Register a PUT route on the default global router.
235
+ */
236
+ static put(path: string, handler: RouteHandler, middleware?: MiddlewareSpec[], swaggerMeta?: RouteMeta, template?: string): RouteRef;
237
+ /**
238
+ * Register a PATCH route on the default global router.
239
+ */
240
+ static patch(path: string, handler: RouteHandler, middleware?: MiddlewareSpec[], swaggerMeta?: RouteMeta, template?: string): RouteRef;
241
+ /**
242
+ * Register a DELETE route on the default global router.
243
+ */
244
+ static delete(path: string, handler: RouteHandler, middleware?: MiddlewareSpec[], swaggerMeta?: RouteMeta, template?: string): RouteRef;
245
+ /**
246
+ * Register a route that matches ANY HTTP method on the default global router.
247
+ */
248
+ static any(path: string, handler: RouteHandler, middleware?: MiddlewareSpec[], swaggerMeta?: RouteMeta, template?: string): RouteRef;
249
+ /**
250
+ * Register a WebSocket route on the default global router.
251
+ */
252
+ static websocket(path: string, handler: WebSocketRouteHandler, options?: {
253
+ secured?: boolean;
254
+ }): WsRouteRef;
255
+ /**
256
+ * Match a WebSocket upgrade path against routes on the default global router.
257
+ * Returns the matched route definition (with its `authRequired` flag) or null.
258
+ */
259
+ static matchWebSocket(pathname: string): WebSocketRouteDefinition | null;
260
+ static matchWebSocketWithParams(pathname: string): {
261
+ route: WebSocketRouteDefinition;
262
+ params: Record<string, string>;
263
+ } | null;
264
+ /** All WebSocket route definitions on the default global router. */
265
+ static getWebSocketRoutes(): WebSocketRouteDefinition[];
266
+ /**
267
+ * Create a route group on the default global router.
268
+ */
269
+ static group(prefix: string, callback: (group: RouteGroup) => void, middlewares?: MiddlewareSpec[]): void;
270
+ /**
271
+ * Supported typed-parameter constraints. Mirrored verbatim in
272
+ * tina4-python / tina4-php / tina4-ruby for cross-framework parity.
273
+ *
274
+ * Any type name not in this table throws at route registration time —
275
+ * we never silently fall through to the default matcher, because a
276
+ * typo like `{id:inetger}` would otherwise match anything and create
277
+ * a security footgun (see tina4-book#125).
278
+ */
279
+ private static readonly PARAM_TYPE_PATTERNS;
280
+ private compilePattern;
281
+ }
282
+ /**
283
+ * Route group for grouping routes under a shared prefix with optional middlewares.
284
+ */
285
+ export declare class RouteGroup {
286
+ private router;
287
+ private prefix;
288
+ private groupMiddlewares?;
289
+ constructor(router: Router, prefix: string, groupMiddlewares?: MiddlewareSpec[] | undefined);
290
+ private mergeMiddlewares;
291
+ get(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
292
+ post(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
293
+ put(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
294
+ patch(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
295
+ delete(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
296
+ any(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
297
+ /**
298
+ * Nested groups.
299
+ */
300
+ group(prefix: string, callback: (group: RouteGroup) => void, middlewares?: MiddlewareSpec[]): void;
301
+ }
302
+ /**
303
+ * Resolve a string-form middleware spec to a middleware function.
304
+ *
305
+ * Forms (parity with Python/PHP/Ruby):
306
+ * "ResponseCache" → responseCache() with the default/env TTL
307
+ * "ResponseCache:300" → responseCache({ ttl: 300 })
308
+ *
309
+ * The head before the first ":" names the middleware; any trailing
310
+ * colon-separated parts are its arguments (numeric parts are parsed as
311
+ * integers). Unknown names throw so a typo surfaces instead of silently
312
+ * dropping the middleware. `responseCache` is loaded via a dynamic import so
313
+ * the router carries no import-time dependency on the cache module.
314
+ *
315
+ * Exported so route dispatch (and tests) can turn a spec into a runnable
316
+ * middleware.
317
+ */
318
+ export declare function resolveStringMiddleware(spec: string): Promise<Middleware>;
319
+ /**
320
+ * Run the per-route middleware chain. Returns false when it short-circuited
321
+ * and the handler must be skipped.
322
+ *
323
+ * Accepts middleware functions, middleware CLASSES, and string specs
324
+ * (e.g. "ResponseCache:300"). A function or string spec is resolved and
325
+ * invoked as `mw(req, res, next)` exactly as before.
326
+ *
327
+ * A CLASS runs its beforeX hooks through the SAME `MiddlewareRunner.runBefore`
328
+ * and the SAME return-value table as global middleware — no parallel runner.
329
+ * Its afterX hooks run with the global after pass once the handler is done
330
+ * (server.ts / testClient.ts append the route's classes to that list), because
331
+ * "after" means after the handler, not after this function. Every spec used to
332
+ * be invoked as `mw(req, res, next)`, which for a class throws "Class
333
+ * constructor cannot be invoked without 'new'", so a class attached per-route
334
+ * was inert. Python and PHP both ran per-route class hooks already.
335
+ */
336
+ export declare function runRouteMiddlewares(middlewares: MiddlewareSpec[], req: Tina4Request, res: Tina4Response): Promise<boolean>;
337
+ /**
338
+ * Default global router instance.
339
+ * Top-level get(), post(), etc. register routes here.
340
+ * The server merges these routes on startup.
341
+ */
342
+ export declare const defaultRouter: Router;
343
+ /**
344
+ * Top-level route registration functions — mirrors Python's decorator pattern.
345
+ *
346
+ * Usage:
347
+ * import { get, post } from "@tina4/core";
348
+ *
349
+ * get("/hello", async (req, res) => {
350
+ * res.json({ message: "Hello" });
351
+ * });
352
+ *
353
+ * post("/users/{id}", async (req, res) => {
354
+ * res.json({ id: req.params.id }, 201);
355
+ * });
356
+ */
357
+ export declare function get(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
358
+ export declare function post(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
359
+ export declare function put(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
360
+ export declare function patch(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
361
+ export declare function del(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
362
+ export declare function any(path: string, handler: RouteHandler, middlewares?: MiddlewareSpec[], meta?: RouteMeta): RouteRef;
363
+ export declare function websocket(path: string, handler: WebSocketRouteHandler, options?: {
364
+ secured?: boolean;
365
+ }): WsRouteRef;
366
+ export { del as delete };
@@ -0,0 +1,19 @@
1
+ export interface ScssConfig {
2
+ importPaths?: string[];
3
+ variables?: Record<string, string>;
4
+ }
5
+ export declare class ScssCompiler {
6
+ private _importPaths;
7
+ private _variables;
8
+ constructor(config?: ScssConfig);
9
+ /** Compile an SCSS string to CSS. */
10
+ compile(source: string): string;
11
+ /** Compile an SCSS file to CSS. */
12
+ compileFile(filePath: string): string;
13
+ /** Add a directory to the import resolution path. */
14
+ addImportPath(path: string): void;
15
+ /** Set or override an SCSS variable. */
16
+ setVariable(name: string, value: string): void;
17
+ /** Compile all .scss files in a directory into a single CSS output file. */
18
+ compileScss(scssDir?: string, output?: string, minify?: boolean): string;
19
+ }