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.
Files changed (134) hide show
  1. package/CLAUDE.md +16 -3
  2. package/README.md +1 -1
  3. package/package.json +12 -9
  4. package/packages/cli/dist/bin.js +1260 -969
  5. package/packages/core/dist/index.js +1260 -969
  6. package/packages/core/src/devMailbox.ts +20 -44
  7. package/packages/core/src/index.ts +2 -2
  8. package/packages/core/src/messenger.ts +72 -0
  9. package/packages/core/src/queueBackends/kafkaBackend.ts +108 -12
  10. package/packages/core/src/sessionHandlers/childError.ts +72 -0
  11. package/packages/core/src/sessionHandlers/mongoClient.ts +9 -3
  12. package/packages/core/src/sessionHandlers/redisHandler.ts +18 -5
  13. package/packages/core/src/sessionHandlers/respClient.ts +5 -1
  14. package/packages/frond/dist/index.js +74 -31
  15. package/packages/frond/src/engine.ts +99 -33
  16. package/packages/orm/dist/index.js +3055 -2764
  17. package/packages/orm/src/adapters/sqlite.ts +4 -1
  18. package/packages/orm/src/database.ts +108 -8
  19. package/types/cli/src/bin.d.ts +92 -0
  20. package/types/cli/src/commands/build.d.ts +2 -0
  21. package/types/cli/src/commands/generate.d.ts +47 -0
  22. package/types/cli/src/commands/init.d.ts +1 -0
  23. package/types/cli/src/commands/metrics.d.ts +6 -0
  24. package/types/cli/src/commands/migrate.d.ts +1 -0
  25. package/types/cli/src/commands/migrateCreate.d.ts +1 -0
  26. package/types/cli/src/commands/migrateRollback.d.ts +1 -0
  27. package/types/cli/src/commands/migrateStatus.d.ts +1 -0
  28. package/types/cli/src/commands/queue.d.ts +20 -0
  29. package/types/cli/src/commands/routes.d.ts +1 -0
  30. package/types/cli/src/commands/seed.d.ts +1 -0
  31. package/types/cli/src/commands/serve.d.ts +6 -0
  32. package/types/cli/src/commands/test.d.ts +1 -0
  33. package/types/core/src/ai.d.ts +64 -0
  34. package/types/core/src/api.d.ts +262 -0
  35. package/types/core/src/auth.d.ts +154 -0
  36. package/types/core/src/authGate.d.ts +20 -0
  37. package/types/core/src/background.d.ts +34 -0
  38. package/types/core/src/cache.d.ts +160 -0
  39. package/types/core/src/constants.d.ts +38 -0
  40. package/types/core/src/container.d.ts +44 -0
  41. package/types/core/src/context/chunker.d.ts +31 -0
  42. package/types/core/src/context/index.d.ts +93 -0
  43. package/types/core/src/devAdmin.d.ts +179 -0
  44. package/types/core/src/devMailbox.d.ts +54 -0
  45. package/types/core/src/docs.d.ts +141 -0
  46. package/types/core/src/docsAutoDiscovery.d.ts +6 -0
  47. package/types/core/src/dotenv.d.ts +65 -0
  48. package/types/core/src/env.d.ts +28 -0
  49. package/types/core/src/errorOverlay.d.ts +36 -0
  50. package/types/core/src/events.d.ts +75 -0
  51. package/types/core/src/fakeData.d.ts +55 -0
  52. package/types/core/src/feedback.d.ts +90 -0
  53. package/types/core/src/graphql.d.ts +207 -0
  54. package/types/core/src/health.d.ts +22 -0
  55. package/types/core/src/htmlElement.d.ts +75 -0
  56. package/types/core/src/i18n.d.ts +37 -0
  57. package/types/core/src/index.d.ts +93 -0
  58. package/types/core/src/job.d.ts +39 -0
  59. package/types/core/src/logger.d.ts +123 -0
  60. package/types/core/src/mcp.d.ts +248 -0
  61. package/types/core/src/messenger.d.ts +191 -0
  62. package/types/core/src/metrics.d.ts +77 -0
  63. package/types/core/src/middleware.d.ts +207 -0
  64. package/types/core/src/mqtt.d.ts +257 -0
  65. package/types/core/src/mqttMessage.d.ts +67 -0
  66. package/types/core/src/plan.d.ts +96 -0
  67. package/types/core/src/projectIndex.d.ts +56 -0
  68. package/types/core/src/queue.d.ts +219 -0
  69. package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
  70. package/types/core/src/queueBackends/liteBackend.d.ts +119 -0
  71. package/types/core/src/queueBackends/mongoBackend.d.ts +97 -0
  72. package/types/core/src/queueBackends/rabbitmqBackend.d.ts +55 -0
  73. package/types/core/src/rateLimiter.d.ts +49 -0
  74. package/types/core/src/request.d.ts +25 -0
  75. package/types/core/src/response.d.ts +28 -0
  76. package/types/core/src/routeDiscovery.d.ts +12 -0
  77. package/types/core/src/router.d.ts +355 -0
  78. package/types/core/src/scss.d.ts +19 -0
  79. package/types/core/src/server.d.ts +131 -0
  80. package/types/core/src/service.d.ts +115 -0
  81. package/types/core/src/session.d.ts +256 -0
  82. package/types/core/src/sessionHandlers/childError.d.ts +34 -0
  83. package/types/core/src/sessionHandlers/databaseHandler.d.ts +42 -0
  84. package/types/core/src/sessionHandlers/mongoClient.d.ts +24 -0
  85. package/types/core/src/sessionHandlers/mongoHandler.d.ts +61 -0
  86. package/types/core/src/sessionHandlers/redisHandler.d.ts +60 -0
  87. package/types/core/src/sessionHandlers/respClient.d.ts +22 -0
  88. package/types/core/src/sessionHandlers/valkeyHandler.d.ts +65 -0
  89. package/types/core/src/static.d.ts +2 -0
  90. package/types/core/src/test.d.ts +94 -0
  91. package/types/core/src/testClient.d.ts +36 -0
  92. package/types/core/src/testing.d.ts +58 -0
  93. package/types/core/src/types.d.ts +219 -0
  94. package/types/core/src/validator.d.ts +52 -0
  95. package/types/core/src/websocket.d.ts +376 -0
  96. package/types/core/src/websocketBackplane.d.ts +166 -0
  97. package/types/core/src/websocketConnection.d.ts +54 -0
  98. package/types/core/src/wsdl.d.ts +101 -0
  99. package/types/frond/src/engine.d.ts +263 -0
  100. package/types/frond/src/index.d.ts +2 -0
  101. package/types/orm/src/adapters/firebird.d.ts +138 -0
  102. package/types/orm/src/adapters/mongodb.d.ts +81 -0
  103. package/types/orm/src/adapters/mssql.d.ts +70 -0
  104. package/types/orm/src/adapters/mysql.d.ts +66 -0
  105. package/types/orm/src/adapters/odbc.d.ts +97 -0
  106. package/types/orm/src/adapters/postgres.d.ts +85 -0
  107. package/types/orm/src/adapters/sqlite.d.ts +56 -0
  108. package/types/orm/src/autoCrud.d.ts +73 -0
  109. package/types/orm/src/baseModel.d.ts +391 -0
  110. package/types/orm/src/cachedDatabase.d.ts +177 -0
  111. package/types/orm/src/database.d.ts +609 -0
  112. package/types/orm/src/databaseResult.d.ts +85 -0
  113. package/types/orm/src/docstore.d.ts +182 -0
  114. package/types/orm/src/fakeData.d.ts +22 -0
  115. package/types/orm/src/index.d.ts +40 -0
  116. package/types/orm/src/migration.d.ts +275 -0
  117. package/types/orm/src/model.d.ts +7 -0
  118. package/types/orm/src/query.d.ts +14 -0
  119. package/types/orm/src/queryBuilder.d.ts +173 -0
  120. package/types/orm/src/realtime/index.d.ts +7 -0
  121. package/types/orm/src/realtime/models/attachment.d.ts +43 -0
  122. package/types/orm/src/realtime/models/channel.d.ts +32 -0
  123. package/types/orm/src/realtime/models/channelMember.d.ts +32 -0
  124. package/types/orm/src/realtime/models/message.d.ts +36 -0
  125. package/types/orm/src/realtime/models/workspace.d.ts +26 -0
  126. package/types/orm/src/realtime/realtime.d.ts +24 -0
  127. package/types/orm/src/realtime/storage.d.ts +61 -0
  128. package/types/orm/src/seeder.d.ts +118 -0
  129. package/types/orm/src/sqlTranslator.d.ts +134 -0
  130. package/types/orm/src/types.d.ts +138 -0
  131. package/types/orm/src/validation.d.ts +6 -0
  132. package/types/swagger/src/generator.d.ts +46 -0
  133. package/types/swagger/src/index.d.ts +2 -0
  134. package/types/swagger/src/ui.d.ts +11 -0
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tina4 Queue Job — a single queue job with lifecycle methods.
3
+ */
4
+ export interface JobData {
5
+ id: string;
6
+ payload: unknown;
7
+ status: "pending" | "reserved" | "failed" | "dead" | "completed";
8
+ createdAt: string;
9
+ attempts: number;
10
+ delayUntil: string | null;
11
+ priority: number;
12
+ topic: string;
13
+ error?: string;
14
+ }
15
+ export interface JobLifecycle {
16
+ /** Mark this job as completed. */
17
+ complete(): void;
18
+ /** Mark this job as failed with a reason. */
19
+ fail(reason?: string): void;
20
+ /** Reject this job with a reason. Alias for fail(). */
21
+ reject(reason?: string): void;
22
+ /** Re-queue this job with incremented attempts and optional delay. */
23
+ retry(delaySeconds?: number): void;
24
+ /** Return job fields as a flat array of values. */
25
+ toArray(): unknown[];
26
+ /** Return job as a plain object. */
27
+ toHash(): Record<string, unknown>;
28
+ /** Return job as a JSON string. */
29
+ toJson(): string;
30
+ }
31
+ export type QueueJob = JobData & JobLifecycle;
32
+ export interface JobQueueBridge {
33
+ _failJob(topic: string, job: QueueJob, reason: string, maxRetries: number): void;
34
+ _retryJob(topic: string, job: QueueJob, delaySeconds?: number): void;
35
+ _completeJob(topic: string, job: QueueJob): void;
36
+ getMaxRetries(): number;
37
+ }
38
+ /** Create a QueueJob with lifecycle methods bound to a Queue instance. */
39
+ export declare function createJob(data: JobData, queue: JobQueueBridge): QueueJob;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Structured logger for Tina4.
3
+ *
4
+ * Development (TINA4_DEBUG=true): colorized human-readable to stdout + file.
5
+ * Production (TINA4_DEBUG not truthy): clean structured JSON to stdout ONLY —
6
+ * no log file by default (writing logs/tina4.log inside a container bloats the
7
+ * writable layer + disk; 12-factor wants logs on stdout). stdout is ALWAYS on.
8
+ *
9
+ * Default file-output rule (TINA4_LOG_OUTPUT unset): the log FILE is written
10
+ * only in development. An explicit TINA4_LOG_OUTPUT=file/both, OR an explicit
11
+ * TINA4_LOG_FILE path, always forces a file (explicit wins).
12
+ *
13
+ * Env vars:
14
+ * TINA4_LOG_FILE — explicit log file (absolute or relative). Setting it forces a file even in production. Empty = use TINA4_LOG_DIR + tina4.log
15
+ * TINA4_LOG_DIR — directory for log files (default: "logs")
16
+ * TINA4_LOG_FORMAT — "text" | "json" (default: "text")
17
+ * TINA4_LOG_OUTPUT — "stdout" | "file" | "both" (default: "stdout" → file only in dev)
18
+ * TINA4_LOG_ROTATE_SIZE — bytes; 0 disables rotation (default: 10485760 = 10MB)
19
+ * TINA4_LOG_ROTATE_KEEP — number of historical files to keep (default: 5)
20
+ * TINA4_LOG_LEVEL — minimum console level: DEBUG | INFO | WARNING | ERROR | CRITICAL (default: "INFO")
21
+ *
22
+ * Rotation is stdlib roll-your-own:
23
+ * - On each write, statSync the file. If size >= TINA4_LOG_ROTATE_SIZE, rotate.
24
+ * - app.log.{N-1} → app.log.{N}, …, app.log → app.log.1 via fs.renameSync.
25
+ * - Files beyond _KEEP are dropped via fs.unlinkSync.
26
+ * - _SIZE=0 disables rotation entirely.
27
+ */
28
+ export declare class Log {
29
+ private static requestId;
30
+ /**
31
+ * Re-read all log-related env vars. Called on every log() so tests that
32
+ * mutate process.env between calls see the new values without having to
33
+ * call configure() each time.
34
+ */
35
+ private static readEnv;
36
+ /**
37
+ * The single console-threshold predicate: does a message at `level` clear
38
+ * the configured minimum console level? This is the ONE place level
39
+ * comparison lives — both the live log() gate and the public isEnabled()
40
+ * predicate call it, so they can never disagree about what actually prints.
41
+ */
42
+ private static passesThreshold;
43
+ /**
44
+ * Return true if a message at `level` would pass the configured minimum
45
+ * console level (TINA4_LOG_LEVEL) — the same threshold that gates stdout.
46
+ *
47
+ * This reflects CONSOLE (stdout) visibility only. The log file always
48
+ * records every level regardless of this threshold, so don't use it to
49
+ * decide whether something gets persisted — use it to skip building an
50
+ * expensive payload that would not be shown:
51
+ *
52
+ * if (Log.isEnabled("debug")) {
53
+ * Log.debug("state", expensiveSnapshot());
54
+ * }
55
+ *
56
+ * `level` is case-insensitive. "critical" is the highest severity (priority
57
+ * 4 > error 3) and flows through the ordinary threshold check like every
58
+ * other level — there is no toggle. It reuses the same passesThreshold()
59
+ * check the logger itself uses, so it never drifts from what print does.
60
+ */
61
+ static isEnabled(level: string): boolean;
62
+ /**
63
+ * Set the current request ID for log correlation.
64
+ */
65
+ static setRequestId(id: string | undefined): void;
66
+ /**
67
+ * Get the current request ID.
68
+ */
69
+ static getRequestId(): string | undefined;
70
+ /**
71
+ * Configure the log directory / filename. Mostly a no-op now —
72
+ * env vars are re-read on every call. Kept for backwards compatibility.
73
+ */
74
+ static configure(options: {
75
+ logDir?: string;
76
+ logFile?: string;
77
+ }): void;
78
+ /** Log an informational message. */
79
+ static info(message: string, data?: unknown): void;
80
+ /** Log a debug message. */
81
+ static debug(message: string, data?: unknown): void;
82
+ /** Log a warning message. */
83
+ static warning(message: string, data?: unknown): void;
84
+ /** Backwards-compat alias for warning(). */
85
+ static warn(message: string, data?: unknown): void;
86
+ /** Log an error message. */
87
+ static error(message: string, data?: unknown): void;
88
+ /**
89
+ * Log a critical message. CRITICAL is the highest severity (priority 4 >
90
+ * error 3) and ALWAYS emits like every other level — subject only to the
91
+ * console threshold, which it always passes at normal levels — and is always
92
+ * persisted to the log file (Node tees every level to a single tina4.log;
93
+ * critical 4 >= warning 2 so it would be in error.log on a split-file model).
94
+ * Matches Python master parity — there is no enable toggle.
95
+ */
96
+ static critical(message: string, data?: unknown): void;
97
+ /** Check if running in production mode (TINA4_DEBUG is not truthy). */
98
+ private static isProduction;
99
+ /** Get current ISO timestamp */
100
+ private static timestamp;
101
+ /** Ensure the log directory exists */
102
+ private static ensureLogDir;
103
+ /**
104
+ * Roll-your-own rotation, stdlib only.
105
+ *
106
+ * Sequence on each write:
107
+ * 1. statSync the current file. If size < rotateSize, return.
108
+ * 2. Drop any file beyond keep via unlinkSync (cap the historical count).
109
+ * 3. Atomic shift: app.log.{N-1} → app.log.{N}, …, app.log.1 → app.log.2.
110
+ * 4. Rename current app.log → app.log.1.
111
+ * 5. Truncate via writeFileSync(path, "") so subsequent appends start fresh.
112
+ *
113
+ * Sync calls per write are fine — the worst case is contention on a single
114
+ * file, and the OS atomically serialises rename/unlink anyway.
115
+ *
116
+ * `rotateSize` of 0 disables rotation entirely.
117
+ */
118
+ private static rotateIfNeeded;
119
+ /** Write a line to the log file, stripping ANSI codes. */
120
+ private static writeToFile;
121
+ /** Core log method */
122
+ private static log;
123
+ }
@@ -0,0 +1,248 @@
1
+ export interface JsonRpcMessage {
2
+ jsonrpc: "2.0";
3
+ id?: number | string | null;
4
+ method?: string;
5
+ params?: Record<string, unknown>;
6
+ result?: unknown;
7
+ error?: {
8
+ code: number;
9
+ message: string;
10
+ data?: unknown;
11
+ };
12
+ }
13
+ export interface McpToolDefinition {
14
+ name: string;
15
+ description: string;
16
+ inputSchema: JsonSchema;
17
+ handler: (args: Record<string, unknown>) => unknown | Promise<unknown>;
18
+ }
19
+ export interface McpResourceDefinition {
20
+ uri: string;
21
+ name: string;
22
+ description: string;
23
+ mimeType: string;
24
+ handler: () => unknown;
25
+ }
26
+ export interface JsonSchema {
27
+ type: string;
28
+ properties: Record<string, {
29
+ type: string;
30
+ default?: unknown;
31
+ }>;
32
+ required?: string[];
33
+ }
34
+ export interface McpToolParam {
35
+ name: string;
36
+ type: "string" | "integer" | "number" | "boolean" | "array" | "object";
37
+ required?: boolean;
38
+ default?: unknown;
39
+ }
40
+ export declare const PARSE_ERROR = -32700;
41
+ export declare const INVALID_REQUEST = -32600;
42
+ export declare const METHOD_NOT_FOUND = -32601;
43
+ export declare const INVALID_PARAMS = -32602;
44
+ export declare const INTERNAL_ERROR = -32603;
45
+ export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
46
+ export declare const LATEST_PROTOCOL_VERSION: "2025-06-18";
47
+ export declare function encodeResponse(requestId: number | string | null | undefined, result: unknown): string;
48
+ export declare function encodeError(requestId: number | string | null | undefined, code: number, message: string, data?: unknown): string;
49
+ export declare function encodeNotification(method: string, params?: Record<string, unknown>): string;
50
+ export declare function decodeRequest(data: string | Record<string, unknown>): {
51
+ method: string;
52
+ params: Record<string, unknown>;
53
+ requestId: number | string | null;
54
+ };
55
+ /**
56
+ * Build a JSON Schema from an explicit parameter list.
57
+ * Since TypeScript erases types at runtime, we use explicit metadata.
58
+ */
59
+ export declare function schemaFromParams(params: McpToolParam[]): JsonSchema;
60
+ /**
61
+ * Informational only — whether the CONFIGURED host looks local.
62
+ *
63
+ * NOT the security gate. Reads `TINA4_HOST_NAME` (the configured bind address),
64
+ * which on a 0.0.0.0 bind looks "local" while still accepting remote clients.
65
+ * Trust decisions use {@link isRequestAllowed} with the RAW socket peer instead.
66
+ * Kept for diagnostics / back-compat.
67
+ */
68
+ export declare function isLocalhost(): boolean;
69
+ /**
70
+ * Whether an address is a loopback (in-process / same-host) peer.
71
+ *
72
+ * Operates on the RAW socket peer, never X-Forwarded-For. Empty/undefined means
73
+ * an in-process / synthetic request (no socket) and is trusted. The `::ffff:`
74
+ * IPv4-mapped prefix is stripped. NOTE: 0.0.0.0 is a BIND address, never a
75
+ * client address, so it is deliberately NOT loopback.
76
+ *
77
+ * Python master parity: tina4_python.mcp.is_loopback.
78
+ */
79
+ export declare function isLoopback(ip: string | undefined | null): boolean;
80
+ /**
81
+ * Capability gate — whether MCP may run at all.
82
+ *
83
+ * Pure capability, host-INDEPENDENT (Python master parity):
84
+ * 1. `TINA4_MCP` explicit on/off override (sysadmin, any host).
85
+ * 2. Else `TINA4_DEBUG=true` → MCP is a capability of this deployment.
86
+ * 3. Otherwise off.
87
+ *
88
+ * This NO LONGER consults the host. A debug box bound to 0.0.0.0 still "has"
89
+ * the capability, but {@link isRequestAllowed} decides whether a given CALLER
90
+ * may use it — loopback always, remote only with an explicit opt-in plus a
91
+ * valid token. Splitting capability from per-request authorisation closes the
92
+ * hole where a 0.0.0.0 bind auto-exposed DB/file tools to remote
93
+ * unauthenticated callers (the pre-3.13.40 isLocalhost() treated 0.0.0.0 local).
94
+ */
95
+ export declare function mcpEnabled(): boolean;
96
+ /**
97
+ * Per-request authorisation — whether THIS caller may use MCP.
98
+ *
99
+ * @param remoteIp Raw socket peer (`req.socket.remoteAddress`), never XFF.
100
+ * @param hasValidToken True when the request carried a token matching TINA4_MCP_TOKEN.
101
+ *
102
+ * Rules (Python master parity, tina4_python.mcp.is_request_allowed):
103
+ * - Capability off ({@link mcpEnabled} false) → deny.
104
+ * - Loopback peer → allow.
105
+ * - Remote peer → only when TINA4_MCP_REMOTE is truthy AND a valid token was
106
+ * presented. No configured token ⇒ remote can never pass.
107
+ */
108
+ export declare function isRequestAllowed(remoteIp: string | undefined | null, hasValidToken?: boolean): boolean;
109
+ /**
110
+ * Resolve the MCP HTTP port. Default: HTTP server port + 2000.
111
+ *
112
+ * `TINA4_MCP_PORT` overrides directly. The `mainPort` argument is the
113
+ * primary HTTP port (the framework passes `port` from `resolvePortAndHost`).
114
+ */
115
+ export declare function mcpPort(mainPort?: number): number;
116
+ export declare class McpServer {
117
+ static _instances: McpServer[];
118
+ path: string;
119
+ name: string;
120
+ version: string;
121
+ private _tools;
122
+ private _resources;
123
+ private _initialized;
124
+ private _sessions;
125
+ private _sseChannels;
126
+ constructor(mcpPath: string, name?: string, version?: string);
127
+ /** Mint a new session id and remember it. Called on `initialize`. */
128
+ openSession(): string;
129
+ /** True when `sessionId` was issued by this server and is still open. */
130
+ isValidSession(sessionId: string | undefined | null): boolean;
131
+ /** Forget a session (client DELETE or SSE stream close). */
132
+ closeSession(sessionId: string | undefined | null): boolean;
133
+ /**
134
+ * Pick the protocol version to run on. Echo the client's requested version
135
+ * when we support it (proper negotiation), else fall back to the newest we
136
+ * speak so an unversioned/old client still connects.
137
+ */
138
+ negotiateProtocolVersion(requested: string | undefined | null): string;
139
+ private _peekMethod;
140
+ /**
141
+ * Streamable HTTP POST handler. initialize mints a session id (returned in
142
+ * the Mcp-Session-Id response header); a non-initialize request with an
143
+ * unknown session id is a 404 (client re-inits); a notification is 202; else
144
+ * 200 with the JSON-RPC response as application/json (which the spec permits
145
+ * for a POST that resolves to a single response).
146
+ */
147
+ dispatchHttp(raw: string | Record<string, unknown>, sessionId?: string): Promise<{
148
+ status: number;
149
+ headers: Record<string, string>;
150
+ body: string;
151
+ }>;
152
+ /**
153
+ * Legacy HTTP+SSE POST /message handler. When a live SSE stream is open for
154
+ * `sessionId`, run the message and push the response down that stream (202
155
+ * here); with no open stream it degrades to an inline Streamable HTTP
156
+ * response, so the same path serves a legacy SSE client and a plain POST.
157
+ */
158
+ dispatchSseMessage(raw: string | Record<string, unknown>, sessionId?: string): Promise<{
159
+ status: number;
160
+ headers: Record<string, string>;
161
+ body: string;
162
+ }>;
163
+ /**
164
+ * Async generator of SSE frames for the legacy HTTP+SSE transport. Emits the
165
+ * `endpoint` event first (naming the POST target), then each queued JSON-RPC
166
+ * response as it arrives, with periodic keep-alive comments. Registers the
167
+ * per-session channel up front and tears it down (plus the session) when the
168
+ * client disconnects and the generator is closed.
169
+ */
170
+ sseStream(sessionId: string, endpointUrl: string, keepaliveMs?: number): AsyncGenerator<string>;
171
+ registerTool(name: string, handler: (args: Record<string, unknown>) => unknown, description?: string, schema?: JsonSchema): void;
172
+ registerResource(uri: string, handler: () => unknown, description?: string, mimeType?: string): void;
173
+ handleMessage(rawData: string | Record<string, unknown>): Promise<string>;
174
+ private _handleInitialize;
175
+ private _handleInitialized;
176
+ private _handlePing;
177
+ private _handleToolsList;
178
+ private _handleToolsCall;
179
+ private _handleResourcesList;
180
+ private _handleResourcesRead;
181
+ /** Coerce a route handler's parsed body into what the dispatchers accept. */
182
+ private _normalizeBody;
183
+ /**
184
+ * Register HTTP routes for this MCP server on the Tina4 router. Mounts both
185
+ * supported transports on `path`:
186
+ * POST {path} — Streamable HTTP (current transport)
187
+ * POST {path}/message — legacy HTTP+SSE message sink (+ inline fallback)
188
+ * GET {path}/sse — legacy HTTP+SSE stream (persistent)
189
+ *
190
+ * A Streamable HTTP client (Claude Code `--transport http`) POSTs to `{path}`
191
+ * and reads the JSON-RPC response inline, with an Mcp-Session-Id header on
192
+ * initialize. A legacy SSE client GETs `{path}/sse`, gets the endpoint event,
193
+ * and its responses stream back on that connection.
194
+ */
195
+ registerRoutes(router: {
196
+ post: (pattern: string, handler: (req: unknown, res: unknown) => unknown) => {
197
+ noAuth: () => unknown;
198
+ };
199
+ get: (pattern: string, handler: (req: unknown, res: unknown) => unknown) => {
200
+ noAuth: () => unknown;
201
+ };
202
+ }): void;
203
+ /**
204
+ * Write/update .claude/settings.json with this MCP server config.
205
+ */
206
+ writeClaudeConfig(port?: number): void;
207
+ }
208
+ /**
209
+ * The default `/__dev/mcp` MCP server with the built-in dev tools registered.
210
+ *
211
+ * This is the single shared instance backing BOTH the browser REST shim
212
+ * (`/__dev/api/mcp/tools` + `/__dev/api/mcp/call`) and the JSON-RPC + SSE
213
+ * endpoints (`/__dev/mcp[/message]` + `/__dev/mcp/sse`) that real MCP clients
214
+ * (Claude Code/Desktop) speak. Tools are registered exactly once (idempotent).
215
+ * Mirrors Python's default MCP server used by `get_api_handlers()`.
216
+ */
217
+ export declare function getDefaultDevServer(): McpServer;
218
+ /**
219
+ * Register a function as an MCP tool.
220
+ *
221
+ * Usage:
222
+ * const greet = mcpTool("greet", "Say hello", server, [
223
+ * { name: "name", type: "string" },
224
+ * ])((args) => `Hello, ${args.name}!`);
225
+ *
226
+ * Returns the original function with _mcpToolName attached.
227
+ */
228
+ export declare function mcpTool(name: string, description?: string, server?: McpServer, params?: McpToolParam[]): <T extends (args: Record<string, unknown>) => unknown>(fn: T) => T & {
229
+ _mcpToolName: string;
230
+ };
231
+ /**
232
+ * Register a function as an MCP resource.
233
+ *
234
+ * Usage:
235
+ * const tables = mcpResource("app://tables", "Database tables", "application/json", server)(
236
+ * () => ["users", "products"]
237
+ * );
238
+ */
239
+ export declare function mcpResource(uri: string, description?: string, mimeType?: string, server?: McpServer): <T extends () => unknown>(fn: T) => T & {
240
+ _mcpResourceUri: string;
241
+ };
242
+ /**
243
+ * Register all built-in dev tools on the given McpServer (the api_* reflection
244
+ * tools plus code_search, the fuzzy FTS grounding tool).
245
+ */
246
+ export declare function registerDevTools(server: McpServer): void;
247
+ /** Alias for registerDevTools — parity with PHP/Ruby/Python. */
248
+ export declare const register: typeof registerDevTools;
@@ -0,0 +1,191 @@
1
+ import { DevMailbox } from "./devMailbox.js";
2
+ export interface SendResult {
3
+ success: boolean;
4
+ message: string;
5
+ id?: string;
6
+ }
7
+ /**
8
+ * Raised when an IMAP read fails to connect, authenticate, or speak the
9
+ * protocol (a `NO`/`BAD` tagged response, a refused/reset socket, a TLS or
10
+ * DNS failure). Distinct from a SUCCESSFUL fetch that simply has no messages —
11
+ * that still returns an empty result ([] / 0 / {}), NOT an error.
12
+ *
13
+ * inbox()/read()/unread()/search()/folders() LOG and then RAISE this on a
14
+ * connection/protocol failure so a dead mailbox is never silently mistaken for
15
+ * an empty one. send() is unchanged — it keeps returning { success, error }.
16
+ */
17
+ export declare class MessengerConnectionError extends Error {
18
+ constructor(message: string);
19
+ }
20
+ export interface EmailMessage {
21
+ id: string;
22
+ type: "inbox" | "outbox";
23
+ from: string;
24
+ to: string[];
25
+ cc: string[];
26
+ bcc: string[];
27
+ reply_to?: string;
28
+ subject: string;
29
+ body: string;
30
+ /** Plain-text alternative. Carried on the dev path too, so the captured message
31
+ * is the message: a mailbox that shows you something other than what you wrote
32
+ * is worse than no mailbox. */
33
+ text?: string;
34
+ html: boolean;
35
+ attachments: string[];
36
+ date: string;
37
+ read: boolean;
38
+ }
39
+ interface MessengerOptions {
40
+ host?: string;
41
+ port?: number;
42
+ username?: string;
43
+ password?: string;
44
+ fromAddress?: string;
45
+ fromName?: string;
46
+ encryption?: string;
47
+ /** @deprecated Use encryption instead */
48
+ useTls?: boolean;
49
+ imapHost?: string;
50
+ imapPort?: number;
51
+ imapUser?: string;
52
+ imapPass?: string;
53
+ /** IMAP transport security: "tls" (default), "starttls", or "none". */
54
+ imapEncryption?: string;
55
+ }
56
+ export interface ImapMessage {
57
+ uid: string;
58
+ subject: string;
59
+ from: string;
60
+ to: string;
61
+ date: string;
62
+ snippet: string;
63
+ seen: boolean;
64
+ }
65
+ export interface ImapFullMessage {
66
+ uid: string;
67
+ subject: string;
68
+ from: string;
69
+ to: string;
70
+ cc: string;
71
+ date: string;
72
+ bodyText: string;
73
+ bodyHtml: string;
74
+ headers: Record<string, string>;
75
+ }
76
+ export declare class Messenger {
77
+ private host;
78
+ private port;
79
+ private username;
80
+ private password;
81
+ private fromAddress;
82
+ private fromName;
83
+ private encryption;
84
+ private useTls;
85
+ /** Whether an SMTP host was actually configured (see the constructor). */
86
+ private smtpConfigured;
87
+ /** The local mailbox, present only when this messenger captures. */
88
+ devMailbox: DevMailbox | null;
89
+ private imapHost;
90
+ private imapPort;
91
+ private imapUser;
92
+ private imapPass;
93
+ private imapEncryption;
94
+ constructor(options?: MessengerOptions);
95
+ /**
96
+ * Read-only IMAP encryption mode for inspection / tests.
97
+ * Returns one of "tls", "starttls", "none", "ssl".
98
+ */
99
+ getImapEncryption(): string;
100
+ /**
101
+ * Send an email via SMTP.
102
+ */
103
+ /**
104
+ * Should send() capture locally instead of talking to SMTP?
105
+ *
106
+ * Availability decides, not verbosity. With no SMTP host configured sending is
107
+ * impossible, so simulate it into a folder rather than failing -- that is what
108
+ * makes a laptop with no mail server usable. TINA4_MAIL_CAPTURE forces capture
109
+ * even when a host IS configured.
110
+ *
111
+ * TINA4_DEBUG deliberately does NOT gate this, and neither does NODE_ENV. Debug
112
+ * must still be able to send, and the old `NODE_ENV !== "production"` clause
113
+ * silently swallowed every staging email.
114
+ */
115
+ private shouldCapture;
116
+ /** The local mailbox, created on first capture and reused after. */
117
+ private getDevMailbox;
118
+ send(to: string | string[], subject: string, body: string, html?: boolean, text?: string, cc?: string | string[], bcc?: string | string[], replyTo?: string, attachments?: string[], headers?: Record<string, string>): Promise<SendResult>;
119
+ /**
120
+ * Test the SMTP connection without sending an email.
121
+ */
122
+ testConnection(): Promise<{
123
+ success: boolean;
124
+ message: string;
125
+ }>;
126
+ /**
127
+ * Connect to the IMAP server via raw TCP/TLS.
128
+ * Returns the socket and reads the greeting.
129
+ */
130
+ private imapConnect;
131
+ /**
132
+ * Disconnect from IMAP cleanly.
133
+ */
134
+ private imapDisconnect;
135
+ /**
136
+ * Fetch latest messages from a folder.
137
+ * Returns list of message summaries.
138
+ */
139
+ inbox(limit?: number, offset?: number, folder?: string): Promise<ImapMessage[]>;
140
+ /**
141
+ * Read a single message by sequence number or UID.
142
+ */
143
+ read(uid: string, folder?: string): Promise<ImapFullMessage>;
144
+ /**
145
+ * Search messages using IMAP search criteria.
146
+ */
147
+ search(folder?: string, subject?: string, sender?: string, since?: string, before?: string, unseenOnly?: boolean, limit?: number): Promise<ImapMessage[]>;
148
+ /**
149
+ * Delete a message by UID.
150
+ */
151
+ deleteMessage(uid: string, folder?: string): Promise<void>;
152
+ /**
153
+ * Mark a message as read.
154
+ */
155
+ markRead(uid: string, folder?: string): Promise<void>;
156
+ /**
157
+ * Count unseen messages in a folder.
158
+ */
159
+ unread(folder?: string): Promise<number>;
160
+ /**
161
+ * List available IMAP folders/mailboxes.
162
+ */
163
+ folders(): Promise<string[]>;
164
+ /**
165
+ * Test IMAP connectivity without reading.
166
+ */
167
+ testImapConnection(): Promise<{
168
+ success: boolean;
169
+ message: string;
170
+ }>;
171
+ }
172
+ /**
173
+ * Create a Messenger configured for the current environment.
174
+ *
175
+ * Returns ONE concrete type, always. It used to return `Messenger | DevMailbox`,
176
+ * and those two shared NO sending method -- DevMailbox has capture(), Messenger has
177
+ * send() -- so the documented call threw TypeError whenever the dev branch was
178
+ * taken. That is nodejs#41. Capture is now a branch inside Messenger.send(), so the
179
+ * object you get back has one send() with one signature either way.
180
+ *
181
+ * The gate is availability, not verbosity:
182
+ * - no TINA4_MAIL_HOST -> capture (sending is impossible, so simulate it)
183
+ * - TINA4_MAIL_CAPTURE truthy -> capture even with SMTP configured
184
+ * - otherwise -> send, EVEN WITH TINA4_DEBUG ON
185
+ *
186
+ * TINA4_DEBUG no longer forces capture: debug must still be able to send real mail.
187
+ * The `NODE_ENV !== "production"` clause is also gone -- it captured even with SMTP
188
+ * configured and debug off, which silently ate every staging email.
189
+ */
190
+ export declare function createMessenger(): Messenger;
191
+ export {};
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Two-tier analysis:
3
+ * 1. Quick metrics (instant): LOC, file counts, class/function counts
4
+ * 2. Full analysis (on-demand, cached): cyclomatic complexity, maintainability
5
+ * index, coupling, Halstead metrics, violations
6
+ *
7
+ * Zero dependencies — uses only Node.js built-in modules.
8
+ */
9
+ interface FunctionInfo {
10
+ name: string;
11
+ line: number;
12
+ complexity: number;
13
+ loc: number;
14
+ args: string[];
15
+ file?: string;
16
+ }
17
+ /**
18
+ * Stop a function being charged for the complexity of the functions nested
19
+ * inside it.
20
+ *
21
+ * Each function's raw score is measured over its whole span, so a branch inside
22
+ * a nested function landed on BOTH that function and every function enclosing
23
+ * it. The over-count compounded with depth: an IIFE wrapper or a registrar
24
+ * defining twenty inner handlers absorbed the entire file's complexity and
25
+ * topped the offenders list, hiding the genuine hot spots.
26
+ *
27
+ * The correction is exact. A raw score is 1 + every decision in the span, so
28
+ * (raw - 1) is the total decision count of a function's whole subtree.
29
+ * Subtracting that for each DIRECT child leaves the function's own branches:
30
+ *
31
+ * own(F) = raw(F) - sum over direct children C of (raw(C) - 1)
32
+ *
33
+ * Anything the extractor does NOT list is deliberately unaffected: nothing
34
+ * subtracts it, so its decisions stay with the function that contains it -
35
+ * moved, never lost.
36
+ */
37
+ export declare function chargeNestedComplexityToTheNestedFunction(functions: FunctionInfo[]): FunctionInfo[];
38
+ export declare function quickMetrics(root?: string): Record<string, any>;
39
+ export declare function fullAnalysis(root?: string): Record<string, any>;
40
+ /** Severity ranking for sorting (higher = more severe). */
41
+ export declare const SEVERITY_RANK: Record<string, number>;
42
+ export interface Offender {
43
+ file: string;
44
+ line: number;
45
+ kind: string;
46
+ severity: "error" | "warn" | "info";
47
+ score: number;
48
+ detail: string;
49
+ }
50
+ export interface OffendersResult {
51
+ offenders: Offender[];
52
+ summary: Record<string, any>;
53
+ }
54
+ /**
55
+ * Rank the worst code-quality issues into a single "top offenders" list.
56
+ *
57
+ * Reuses {@link fullAnalysis} (does NOT re-analyze — the result is mtime-cached).
58
+ * Each offender is `{ file, line, kind, severity, score, detail }`.
59
+ *
60
+ * Rules (one offender per matching condition — SAME scoring as the master):
61
+ * - function complexity > 10 → kind "complexity"
62
+ * severity "error" if > 20 else "warn"; score = complexity
63
+ * - file loc > 500 → kind "large_file" (warn); score = loc / 100
64
+ * - file functions > 20 → kind "too_many_functions" (warn); score = functions / 4
65
+ * - file maintainability < 40 → kind "low_maintainability"
66
+ * severity "error" if < 20 else "warn"; score = 50 - mi
67
+ * - file has_tests === false → kind "untested" (info); score = loc / 100
68
+ *
69
+ * Sorted by (severity rank, score) DESCENDING and truncated to `top`.
70
+ *
71
+ * Returns `{ offenders, summary }` where summary carries the headline numbers
72
+ * the CLI prints (files_analyzed, total_functions, avg_complexity,
73
+ * avg_maintainability, scan_mode, scan_root, total_offenders).
74
+ */
75
+ export declare function offenders(root?: string, top?: number): OffendersResult;
76
+ export declare function fileDetail(filePath: string): Record<string, any>;
77
+ export {};