tina4-nodejs 3.13.94 → 3.13.96
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 +158 -30
- package/README.md +1 -1
- package/package.json +3 -1
- package/packages/cli/dist/bin.js +30911 -28444
- package/packages/cli/src/commands/metrics.ts +17 -11
- package/packages/cli/src/commands/serve.ts +10 -9
- package/packages/core/dist/index.js +30810 -28261
- package/packages/core/public/css/tina4.min.css +1 -1
- package/packages/core/src/ai.ts +7 -1
- package/packages/core/src/auth.ts +191 -39
- package/packages/core/src/background.ts +19 -19
- package/packages/core/src/cache.ts +492 -49
- package/packages/core/src/devAdmin.ts +79 -32
- package/packages/core/src/dispatchPipeline.ts +285 -0
- package/packages/core/src/dotenv.ts +185 -40
- package/packages/core/src/index.ts +6 -7
- package/packages/core/src/logger.ts +257 -36
- package/packages/core/src/mcp.ts +1 -1
- package/packages/core/src/messenger.ts +294 -106
- package/packages/core/src/metrics.ts +199 -961
- package/packages/core/src/middleware.ts +390 -123
- package/packages/core/src/queue.ts +188 -32
- package/packages/core/src/queueBackends/kafkaBackend.ts +1 -1
- package/packages/core/src/queueBackends/liteBackend.ts +13 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +101 -9
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +22 -4
- package/packages/core/src/rateLimiter.ts +10 -5
- package/packages/core/src/request.ts +34 -16
- package/packages/core/src/response.ts +46 -1
- package/packages/core/src/router.ts +29 -4
- package/packages/core/src/server.ts +886 -421
- package/packages/core/src/session.ts +244 -27
- package/packages/core/src/sessionHandlers/databaseHandler.ts +338 -48
- package/packages/core/src/sessionHandlers/memcachedHandler.ts +181 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +293 -208
- package/packages/core/src/sessionHandlers/mongoHandler.ts +88 -8
- package/packages/core/src/sessionHandlers/respClient.ts +16 -147
- package/packages/core/src/sessionHandlers/sqlClient.ts +290 -0
- package/packages/core/src/sessionHandlers/syncBridge.ts +190 -0
- package/packages/core/src/sessionHandlers/syncSocket.ts +236 -0
- package/packages/core/src/testClient.ts +18 -5
- package/packages/core/src/trustedProxy.ts +249 -0
- package/packages/core/src/types.ts +29 -5
- package/packages/core/src/websocket.ts +66 -0
- package/packages/orm/dist/index.js +22717 -20168
- package/packages/orm/src/adapters/firebird.ts +183 -56
- package/packages/orm/src/adapters/mongodb.ts +25 -4
- package/packages/orm/src/adapters/mssql.ts +114 -29
- package/packages/orm/src/adapters/mysql.ts +103 -40
- package/packages/orm/src/adapters/odbc.ts +44 -21
- package/packages/orm/src/adapters/postgres.ts +118 -26
- package/packages/orm/src/adapters/sqlDialect.ts +120 -0
- package/packages/orm/src/adapters/sqlite.ts +60 -24
- package/packages/orm/src/autoCrud.ts +12 -10
- package/packages/orm/src/baseModel.ts +135 -40
- package/packages/orm/src/cachedDatabase.ts +43 -19
- package/packages/orm/src/connectTimeout.ts +265 -0
- package/packages/orm/src/database.ts +241 -197
- package/packages/orm/src/databaseResult.ts +51 -28
- package/packages/orm/src/databaseUrl.ts +484 -0
- package/packages/orm/src/docstore.ts +386 -145
- package/packages/orm/src/index.ts +13 -6
- package/packages/orm/src/migration.ts +44 -11
- package/packages/orm/src/model.ts +4 -0
- package/packages/orm/src/queryBuilder.ts +47 -6
- package/packages/orm/src/sqlTranslator.ts +310 -4
- package/packages/orm/src/types.ts +21 -77
- package/packages/swagger/dist/index.js +78 -20
- package/packages/swagger/src/generator.ts +172 -29
- package/types/core/src/ai.d.ts +1 -1
- package/types/core/src/auth.d.ts +28 -5
- package/types/core/src/background.d.ts +3 -3
- package/types/core/src/cache.d.ts +15 -12
- package/types/core/src/dispatchPipeline.d.ts +117 -0
- package/types/core/src/dotenv.d.ts +38 -16
- package/types/core/src/index.d.ts +6 -9
- package/types/core/src/logger.d.ts +93 -16
- package/types/core/src/messenger.d.ts +47 -6
- package/types/core/src/metrics.d.ts +25 -61
- package/types/core/src/middleware.d.ts +134 -11
- package/types/core/src/queue.d.ts +54 -5
- package/types/core/src/queueBackends/kafkaBackend.d.ts +1 -1
- package/types/core/src/queueBackends/liteBackend.d.ts +9 -0
- package/types/core/src/queueBackends/mongoBackend.d.ts +24 -2
- package/types/core/src/queueBackends/rabbitmqBackend.d.ts +3 -3
- package/types/core/src/router.d.ts +14 -3
- package/types/core/src/server.d.ts +15 -4
- package/types/core/src/session.d.ts +87 -2
- package/types/core/src/sessionHandlers/databaseHandler.d.ts +60 -5
- package/types/core/src/sessionHandlers/memcachedHandler.d.ts +60 -0
- package/types/core/src/sessionHandlers/mongoClient.d.ts +16 -5
- package/types/core/src/sessionHandlers/mongoHandler.d.ts +51 -3
- package/types/core/src/sessionHandlers/respClient.d.ts +2 -2
- package/types/core/src/sessionHandlers/sqlClient.d.ts +39 -0
- package/types/core/src/sessionHandlers/syncBridge.d.ts +91 -0
- package/types/core/src/sessionHandlers/syncSocket.d.ts +49 -0
- package/types/core/src/trustedProxy.d.ts +44 -0
- package/types/core/src/types.d.ts +28 -5
- package/types/core/src/websocket.d.ts +26 -0
- package/types/orm/src/adapters/firebird.d.ts +55 -10
- package/types/orm/src/adapters/mongodb.d.ts +2 -2
- package/types/orm/src/adapters/mssql.d.ts +18 -11
- package/types/orm/src/adapters/mysql.d.ts +11 -10
- package/types/orm/src/adapters/odbc.d.ts +9 -12
- package/types/orm/src/adapters/postgres.d.ts +11 -10
- package/types/orm/src/adapters/sqlDialect.d.ts +71 -0
- package/types/orm/src/adapters/sqlite.d.ts +15 -3
- package/types/orm/src/baseModel.d.ts +45 -9
- package/types/orm/src/cachedDatabase.d.ts +18 -5
- package/types/orm/src/connectTimeout.d.ts +100 -0
- package/types/orm/src/database.d.ts +78 -28
- package/types/orm/src/databaseResult.d.ts +29 -15
- package/types/orm/src/databaseUrl.d.ts +125 -0
- package/types/orm/src/docstore.d.ts +102 -43
- package/types/orm/src/index.d.ts +6 -4
- package/types/orm/src/migration.d.ts +4 -3
- package/types/orm/src/queryBuilder.d.ts +23 -3
- package/types/orm/src/sqlTranslator.d.ts +126 -2
- package/types/orm/src/types.d.ts +21 -38
- package/packages/core/src/scss.ts +0 -623
- package/packages/core/src/sessionHandlers/redisHandler.ts +0 -219
- package/types/core/src/scss.d.ts +0 -19
- package/types/core/src/sessionHandlers/redisHandler.d.ts +0 -60
|
@@ -70,6 +70,20 @@ interface CacheBackend {
|
|
|
70
70
|
set(key: string, value: unknown, ttl: number): Promise<void>;
|
|
71
71
|
delete(key: string): Promise<boolean>;
|
|
72
72
|
clear(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Evict expired entries and return HOW MANY were actually evicted.
|
|
75
|
+
*
|
|
76
|
+
* REQUIRED, not optional. It used to be neither declared nor implemented, so
|
|
77
|
+
* the module-level sweep() found no backend method and returned a permanent
|
|
78
|
+
* 0: the one API whose job is reclaiming expired space did nothing and
|
|
79
|
+
* reported success. Declaring it here makes "every provider can sweep" a
|
|
80
|
+
* compile-time fact instead of a runtime hope.
|
|
81
|
+
*
|
|
82
|
+
* 0 is the HONEST answer on redis/valkey/memcached/mongodb - they expire
|
|
83
|
+
* entries server-side, so there is nothing left for us to evict. It is the
|
|
84
|
+
* WRONG answer for memory, file and database, which own their own expiry.
|
|
85
|
+
*/
|
|
86
|
+
sweep(): Promise<number>;
|
|
73
87
|
stats(): Promise<{
|
|
74
88
|
hits: number;
|
|
75
89
|
misses: number;
|
|
@@ -107,18 +121,7 @@ export declare function createBackend(config?: {
|
|
|
107
121
|
cacheDir?: string;
|
|
108
122
|
maxEntries?: number;
|
|
109
123
|
}): Promise<CacheBackend>;
|
|
110
|
-
|
|
111
|
-
* Response cache middleware for GET requests.
|
|
112
|
-
* Caches the full response body, content-type, and status code through the
|
|
113
|
-
* unified async backend. Cache key is method + url (including query string).
|
|
114
|
-
*
|
|
115
|
-
* The middleware is ASYNC: the before-path awaits `backend.get` (serve hit) and
|
|
116
|
-
* the after-path awaits `backend.set` (store on the captured `res.raw.end`).
|
|
117
|
-
* The framework's middleware chain (`runRouteMiddlewares` / `MiddlewareChain`)
|
|
118
|
-
* already awaits middleware, so async is transparent. Honors ttl/statusCodes/
|
|
119
|
-
* maxEntries. With the default `memory` backend behaviour is unchanged; a
|
|
120
|
-
* redis/etc. backend distributes cross-instance.
|
|
121
|
-
*/
|
|
124
|
+
export declare function _getResponseBackend(config?: ResponseCacheConfig): Promise<CacheBackend>;
|
|
122
125
|
export declare function responseCache(config?: ResponseCacheConfig): Middleware;
|
|
123
126
|
/**
|
|
124
127
|
* Clear all cached responses (the responseCache middleware backend).
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dispatch pipeline: the concerns of `dispatch`, named and extracted.
|
|
3
|
+
*
|
|
4
|
+
* `dispatch` was a 485-line closure at cyclomatic complexity 65 against a
|
|
5
|
+
* ceiling of 10, on the path of every request, nested inside `startServer`
|
|
6
|
+
* (which is why that measured 45 as well). These are its concerns as
|
|
7
|
+
* standalone functions, so each can be read and tested without standing up a
|
|
8
|
+
* server.
|
|
9
|
+
*
|
|
10
|
+
* PROLOGUE_STAGES run before anything else, in order. They are extracted FIRST
|
|
11
|
+
* because they close over nothing from `startServer` - only the raw
|
|
12
|
+
* request/response - so no context object is needed for them at all. The later
|
|
13
|
+
* stages need `router`, `staticDir`, `port` and `middleware`, and follow.
|
|
14
|
+
* `sessionAutoStart` is the one prologue stage that runs INSIDE the dispatch
|
|
15
|
+
* try-block, so a TINA4_SESSION_STRICT refusal renders a 500 like every other
|
|
16
|
+
* request error instead of rejecting `dispatch` into an unhandled rejection
|
|
17
|
+
* that takes the worker down (ADR-0021; parity with Python, where the raise
|
|
18
|
+
* leaves the request path and the ASGI server turns it into a 500).
|
|
19
|
+
*
|
|
20
|
+
* Ordering here is BEHAVIOUR, not taste:
|
|
21
|
+
* * `headStripIntercept` MUST run before anything can write. Node streams its
|
|
22
|
+
* response, so there is no single exit point to strip at - the interception
|
|
23
|
+
* IS the mechanism (ADR-0011: the CONTRACT is the outcome, and Ruby and
|
|
24
|
+
* Python satisfy it by stripping late at their single return instead).
|
|
25
|
+
* * `sessionAutoStart` wraps `end` after that, so its save-and-set-cookie
|
|
26
|
+
* runs on the real `end` rather than on the HEAD interceptor's.
|
|
27
|
+
*
|
|
28
|
+
* @see tina4-ruby/lib/tina4/dispatch_pipeline.rb - the same extraction, and
|
|
29
|
+
* the source of the stage-list-as-data pattern.
|
|
30
|
+
*/
|
|
31
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
32
|
+
import type { Tina4Request } from "./types.js";
|
|
33
|
+
/**
|
|
34
|
+
* The prologue, in order. Exported as DATA so the pipeline can be asserted and
|
|
35
|
+
* compared across frameworks without reading an implementation.
|
|
36
|
+
*/
|
|
37
|
+
export declare const PROLOGUE_STAGES: readonly ["resetRequestCaches", "headStripIntercept", "sessionAutoStart"];
|
|
38
|
+
/**
|
|
39
|
+
* After the prologue, before a route is looked up.
|
|
40
|
+
*
|
|
41
|
+
* `wrapResponseEnd` MUST come before the global pass: it installs the end()
|
|
42
|
+
* wrapper that injects the dev toolbar and captures the request, and a
|
|
43
|
+
* middleware that short-circuits still has to be captured.
|
|
44
|
+
*/
|
|
45
|
+
export declare const REQUEST_STAGES: readonly ["blockAiPortReload", "wrapResponseEnd", "runGlobalMiddlewarePass"];
|
|
46
|
+
/**
|
|
47
|
+
* A matched route, in order - and the order is BEHAVIOUR (ADR-0012):
|
|
48
|
+
* POST-MATCH globals -> auth gate -> the route's OWN middleware -> handler.
|
|
49
|
+
*
|
|
50
|
+
* The globals run BEFORE the gate so a rate limiter can throttle a brute-force
|
|
51
|
+
* login and an access log records the 401 - neither is possible if they only
|
|
52
|
+
* run on authenticated requests. The route's own middleware stays AFTER the
|
|
53
|
+
* gate, so middleware attached to a secured route never processes an
|
|
54
|
+
* unauthenticated request.
|
|
55
|
+
*
|
|
56
|
+
* `runGlobalMiddlewarePass` appears here AND in REQUEST_STAGES on purpose:
|
|
57
|
+
* one function, two phases. That split IS ADR-0012.
|
|
58
|
+
*/
|
|
59
|
+
export declare const ROUTE_STAGES: readonly ["runGlobalMiddlewarePass", "enforceRouteAuth", "runRouteMiddlewares", "invokeRouteHandler", "renderIfTemplateRoute"];
|
|
60
|
+
/**
|
|
61
|
+
* Nothing matched a route: the fallback chain, walked until one answers.
|
|
62
|
+
*
|
|
63
|
+
* Order is BEHAVIOUR: a template beats the landing page (so a project's own
|
|
64
|
+
* pages/index.twig wins at "/"), 405 beats static (a known path with the wrong
|
|
65
|
+
* method is not a missing file), and the 404 is terminal.
|
|
66
|
+
*
|
|
67
|
+
* This chain runs AFTER matching because routes beat files (ADR-0010): a file
|
|
68
|
+
* from a build step or a careless deploy must never shadow a reviewed route.
|
|
69
|
+
*
|
|
70
|
+
* server.ts holds the same order as an array of the real FUNCTIONS - that is
|
|
71
|
+
* what dispatch actually walks. dispatchPipeline.test.ts asserts the two agree,
|
|
72
|
+
* so this list cannot drift from the runner.
|
|
73
|
+
*/
|
|
74
|
+
export declare const FALLBACK_STAGES: readonly ["serveTemplateFallback", "serveLandingPage", "serveMethodNotAllowed", "serveStaticAsset", "serveNotFound"];
|
|
75
|
+
/** The catch arm. Everything above throws into this one. */
|
|
76
|
+
export declare const ERROR_STAGES: readonly ["renderDispatchError"];
|
|
77
|
+
/**
|
|
78
|
+
* Request-scoped DB query cache boundary.
|
|
79
|
+
*
|
|
80
|
+
* Clears the request-scoped cache on every live connection at the START of each
|
|
81
|
+
* request so it never serves rows across requests (persistent-mode connections
|
|
82
|
+
* are left alone). The ORM is loaded lazily and may be absent, so this is
|
|
83
|
+
* best-effort: a failure here must never break a request. Mirrors Python's
|
|
84
|
+
* dispatcher calling `Database.reset_request_caches()`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function resetRequestCaches(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* RFC 9110 s9.3.2: the server MUST NOT send content in a HEAD response.
|
|
89
|
+
*
|
|
90
|
+
* Intercepts `write` / `end` so every code path - an explicit `Router.head()`
|
|
91
|
+
* handler, the GET auto-fallback, 405 and 404 responses - drops its body.
|
|
92
|
+
* Content-Length is preserved when present, so cache validators, link checkers
|
|
93
|
+
* and monitoring probes still see the size the equivalent GET would have sent.
|
|
94
|
+
*
|
|
95
|
+
* No-op for any method other than HEAD.
|
|
96
|
+
*
|
|
97
|
+
* @param rawReq Node's incoming message, read for the method
|
|
98
|
+
* @param rawRes Node's server response, whose write/end are replaced in place
|
|
99
|
+
*/
|
|
100
|
+
export declare function headStripIntercept(rawReq: IncomingMessage, rawRes: ServerResponse): void;
|
|
101
|
+
/**
|
|
102
|
+
* Auto-start the session: read the cookie, create the session, then save it and
|
|
103
|
+
* set the cookie when the response ends.
|
|
104
|
+
*
|
|
105
|
+
* The incoming cookie is read by the SAME configured name the write side emits
|
|
106
|
+
* (`TINA4_SESSION_NAME`, default `tina4_session`) via the shared
|
|
107
|
+
* `sessionCookieName()` resolver - otherwise a renamed cookie would be written
|
|
108
|
+
* but never read back and the session would silently never resume. A whole
|
|
109
|
+
* cookie pair is matched by its exact `name=` prefix (split on ";", trim,
|
|
110
|
+
* startsWith) so `tina4_session` never matches `tina4_session_foo=` nor a value
|
|
111
|
+
* mid-header. Parity with Python `core/server._init_session`.
|
|
112
|
+
*
|
|
113
|
+
* @param rawReq Node's incoming message, read for cookies and the proxy scheme
|
|
114
|
+
* @param rawRes Node's server response, whose `end` is wrapped
|
|
115
|
+
* @param req The Tina4 request the session is attached to
|
|
116
|
+
*/
|
|
117
|
+
export declare function sessionAutoStart(rawReq: IncomingMessage, rawRes: ServerResponse, req: Tina4Request): Promise<void>;
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Load environment variables from a
|
|
2
|
+
* Load environment variables from a root DIRECTORY or a single .env file.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `.env.local` FIRST then `.env`, both with override=false (the default): the
|
|
8
|
-
* real env (already present) wins over both, `.env.local` fills local-only keys,
|
|
9
|
-
* and `.env` fills the rest. Do NOT load `.env.local` with override=true — that
|
|
10
|
-
* would let a stray gitignored `.env.local` clobber an explicitly set real env
|
|
11
|
-
* var (e.g. a production TINA4_SECRET).
|
|
4
|
+
* Pass a **directory** and it loads `<dir>/.env.local` then `<dir>/.env`, both
|
|
5
|
+
* first-wins, which IS the precedence real-env > `.env.local` > `.env`. That is
|
|
6
|
+
* the canonical form in all four frameworks.
|
|
12
7
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
8
|
+
* Before this, the ordering was the CALLER's job and this doc comment was the
|
|
9
|
+
* only place it was written down: load `.env.local` first, then `.env`, both
|
|
10
|
+
* with override=false. Every caller had to remember, and getting it wrong
|
|
11
|
+
* (override=true on `.env.local`) lets a stray gitignored file clobber an
|
|
12
|
+
* explicitly set real env var such as a production TINA4_SECRET. A rule nobody
|
|
13
|
+
* can forget beats a rule written in a comment.
|
|
17
14
|
*
|
|
18
|
-
*
|
|
15
|
+
* A **file** path still works exactly as before: only that file is read, and the
|
|
16
|
+
* caller owns the ordering.
|
|
17
|
+
*
|
|
18
|
+
* By default this does NOT override existing process.env values — it is
|
|
19
|
+
* first-wins, which is how a real env var always beats both files.
|
|
20
|
+
*
|
|
21
|
+
* Resolution order when `path` is omitted:
|
|
22
|
+
* 1. `TINA4_ENV_FILE` env var (if set and non-empty) — the named file, plus
|
|
23
|
+
* `.env.local` BESIDE it, so pointing at `.env.staging` does not silently
|
|
24
|
+
* stop honouring local overrides
|
|
25
|
+
* 2. the current working directory, as a root
|
|
26
|
+
*
|
|
27
|
+
* @param path - A root directory (canonical) OR a path to a single .env file.
|
|
19
28
|
* @param override - When true, overwrite keys already present in process.env.
|
|
20
|
-
* @returns The parsed key-value pairs
|
|
29
|
+
* @returns The parsed key-value pairs. For the directory form this is the merge
|
|
30
|
+
* of both files, with `.env.local` winning on a duplicate key.
|
|
21
31
|
*/
|
|
22
32
|
export declare function loadEnv(path?: string, override?: boolean): Record<string, string>;
|
|
23
33
|
/**
|
|
@@ -35,7 +45,19 @@ export declare function getEnv(key: string, defaultValue?: string): string | und
|
|
|
35
45
|
* @returns The environment variable value.
|
|
36
46
|
* @throws Error if the variable is not set.
|
|
37
47
|
*/
|
|
38
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Validate that required environment variables exist, and return them.
|
|
50
|
+
*
|
|
51
|
+
* Takes VARARGS and returns a map, matching Python, PHP and Ruby. It used to
|
|
52
|
+
* take one key and return that value, so checking five variables meant five
|
|
53
|
+
* calls that each failed on the first problem - an operator fixing a deployment
|
|
54
|
+
* got one name per restart instead of the whole list.
|
|
55
|
+
*
|
|
56
|
+
* @param keys - Variable names that must be set.
|
|
57
|
+
* @returns Every requested key mapped to its value.
|
|
58
|
+
* @throws Error naming ALL missing variables, not just the first.
|
|
59
|
+
*/
|
|
60
|
+
export declare function requireEnv(...keys: string[]): Record<string, string>;
|
|
39
61
|
/**
|
|
40
62
|
* Check if an environment variable exists (is defined in process.env).
|
|
41
63
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { Tina4Request, Tina4Response, RouteHandler, RouteDefinition, RouteMeta, Tina4Config, Middleware, MiddlewareSpec, UploadedFile, CookieOptions, WebSocketRouteHandler, WebSocketRouteDefinition, } from "./types.js";
|
|
1
|
+
export type { Tina4Request, Tina4Response, RouteHandler, RouteDefinition, RouteMeta, Tina4Config, Middleware, MiddlewareClass, MiddlewareSpec, UploadedFile, CookieOptions, WebSocketRouteHandler, WebSocketRouteDefinition, } from "./types.js";
|
|
2
2
|
export { startServer, resolvePortAndHost, handle, start, stop, httpReason, resolveTemplate, resetTemplateCache, templateAutoRoutingEnabled, isBannerSuppressed } from "./server.js";
|
|
3
3
|
export { background, stopAllBackgroundTasks, backgroundTaskCount } from "./background.js";
|
|
4
4
|
export { Router, RouteGroup, RouteRef, WsRouteRef, defaultRouter, runRouteMiddlewares, resolveStringMiddleware, isTrailingSlashRedirectEnabled } from "./router.js";
|
|
@@ -15,15 +15,14 @@ export { Env } from "./env.js";
|
|
|
15
15
|
export { Log } from "./logger.js";
|
|
16
16
|
export { createHealthRoute, createHealthRoutes, healthPath } from "./health.js";
|
|
17
17
|
export { rateLimiter } from "./rateLimiter.js";
|
|
18
|
+
export { isTrustedProxy, trustedProxyNetworks, resolveClientIp, resetTrustedProxyCache } from "./trustedProxy.js";
|
|
18
19
|
export type { RateLimiterConfig } from "./rateLimiter.js";
|
|
19
20
|
export { HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED, HTTP_NO_CONTENT, HTTP_MOVED, HTTP_REDIRECT, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_METHOD_NOT_ALLOWED, HTTP_CONFLICT, HTTP_GONE, HTTP_UNPROCESSABLE, HTTP_TOO_MANY, HTTP_SERVER_ERROR, HTTP_BAD_GATEWAY, HTTP_UNAVAILABLE, APPLICATION_JSON, APPLICATION_XML, APPLICATION_FORM, APPLICATION_OCTET, TEXT_HTML, TEXT_PLAIN, TEXT_CSV, TEXT_XML, } from "./constants.js";
|
|
20
|
-
export { getToken, validToken, getPayload, hashPassword, checkPassword, authMiddleware, refreshToken, authenticateRequest, validateApiKey, ensureDevSecret, Auth, } from "./auth.js";
|
|
21
|
-
export { Session, FileSessionHandler, RedisSessionHandler, buildSessionCookie, isSecureScheme, sessionCookieName } from "./session.js";
|
|
21
|
+
export { getToken, validToken, getPayload, hashPassword, checkPassword, authMiddleware, refreshToken, authenticateRequest, validateApiKey, ensureDevSecret, resolveAlgorithm, algorithmAvailable, availableAlgorithms, Auth, } from "./auth.js";
|
|
22
|
+
export { Session, FileSessionHandler, RedisSessionHandler, buildSessionCookie, isSecureScheme, isValidSessionId, sessionCookieName, VALID_SESSION_BACKENDS, CANONICAL_SESSION_BACKENDS } from "./session.js";
|
|
22
23
|
export type { SessionConfig, SessionHandler } from "./session.js";
|
|
23
24
|
export { I18n } from "./i18n.js";
|
|
24
25
|
export { FakeData } from "./fakeData.js";
|
|
25
|
-
export { ScssCompiler } from "./scss.js";
|
|
26
|
-
export type { ScssConfig } from "./scss.js";
|
|
27
26
|
export { Queue } from "./queue.js";
|
|
28
27
|
export type { QueueConfig, QueueJob, ProcessOptions } from "./queue.js";
|
|
29
28
|
export { createJob } from "./job.js";
|
|
@@ -38,7 +37,7 @@ export { WebSocketServer, devReloadWs, computeAcceptKey, parseUpgradeHeaders, bu
|
|
|
38
37
|
export type { WebSocketClient } from "./websocket.js";
|
|
39
38
|
export { ServiceRunner, Tina4Service, matchCronField, matchesCron } from "./service.js";
|
|
40
39
|
export type { ServiceOptions, ServiceContext, ServiceHandler, ServiceInfo } from "./service.js";
|
|
41
|
-
export { responseCache, clearCache, cacheStats, cacheGet, cacheSet, cacheDelete, cacheClear, cacheBackendStats, createBackend, _resetBackend } from "./cache.js";
|
|
40
|
+
export { responseCache, clearCache, cacheStats, cacheGet, cacheSet, cacheDelete, cacheClear, cacheBackendStats, sweep, createBackend, _resetBackend } from "./cache.js";
|
|
42
41
|
export type { ResponseCacheConfig, CacheBackend } from "./cache.js";
|
|
43
42
|
export { Api } from "./api.js";
|
|
44
43
|
export type { ApiResult, ApiOptions, ApiTransport, DownloadResult, UploadOptions } from "./api.js";
|
|
@@ -56,7 +55,7 @@ export { HtmlElement, htmlElement, addHtmlHelpers, Raw, SafeString } from "./htm
|
|
|
56
55
|
export { renderErrorOverlay, renderProductionError, isDebugMode } from "./errorOverlay.js";
|
|
57
56
|
export { AI_TOOLS, isInstalled, showMenu, installSelected, installAll, generateContext } from "./ai.js";
|
|
58
57
|
export type { AiTool } from "./ai.js";
|
|
59
|
-
export type { ImapMessage, ImapFullMessage } from "./messenger.js";
|
|
58
|
+
export type { ImapMessage, ImapFullMessage, ImapAttachment } from "./messenger.js";
|
|
60
59
|
export { LiteBackend } from "./queueBackends/liteBackend.js";
|
|
61
60
|
export { RabbitMQBackend, parseAmqpUrl } from "./queueBackends/rabbitmqBackend.js";
|
|
62
61
|
export type { RabbitMQConfig } from "./queueBackends/rabbitmqBackend.js";
|
|
@@ -70,8 +69,6 @@ export { MongoSessionHandler } from "./sessionHandlers/mongoHandler.js";
|
|
|
70
69
|
export type { MongoSessionConfig } from "./sessionHandlers/mongoHandler.js";
|
|
71
70
|
export { ValkeySessionHandler } from "./sessionHandlers/valkeyHandler.js";
|
|
72
71
|
export type { ValkeySessionConfig } from "./sessionHandlers/valkeyHandler.js";
|
|
73
|
-
export { RedisNpmSessionHandler } from "./sessionHandlers/redisHandler.js";
|
|
74
|
-
export type { RedisNpmSessionConfig } from "./sessionHandlers/redisHandler.js";
|
|
75
72
|
export { tests, assertEqual, assertRaises, assertTrue, assertFalse, runAll, reset } from "./testing.js";
|
|
76
73
|
export { TestClient, TestResponse } from "./testClient.js";
|
|
77
74
|
export { Tina4Test, AssertionError as Tina4AssertionError } from "./test.js";
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Structured logger for Tina4.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* FORMAT IS TEXT BY DEFAULT, and TINA4_LOG_FORMAT=json is the ONLY thing that
|
|
5
|
+
* selects JSON. Nothing else may. Until 3.13.95 an unset TINA4_DEBUG silently
|
|
6
|
+
* flipped BOTH sinks to JSON here, and "production" meant four different things
|
|
7
|
+
* across the four frameworks (Node: !TINA4_DEBUG; Ruby: TINA4_ENV/RACK_ENV/
|
|
8
|
+
* RUBY_ENV == "production"; Python: only configure(production=True); PHP: no
|
|
9
|
+
* switch at all, JSON always) — same machine, same .env, four log formats. That
|
|
10
|
+
* implicit switch is deleted; an object passed as the message is still
|
|
11
|
+
* JSON-encoded INLINE inside the text line, which is the only JSON a default
|
|
12
|
+
* install emits.
|
|
13
|
+
*
|
|
14
|
+
* TINA4_DEBUG still decides COLOUR — a terminal concern, not a format one — so
|
|
15
|
+
* a production pipe gets clean uncoloured bytes and a dev terminal stays
|
|
16
|
+
* readable.
|
|
8
17
|
*
|
|
9
18
|
* Default file-output rule (TINA4_LOG_OUTPUT unset): the log FILE is written
|
|
10
19
|
* only in development. An explicit TINA4_LOG_OUTPUT=file/both, OR an explicit
|
|
11
|
-
* TINA4_LOG_FILE path, always forces a file (explicit wins).
|
|
20
|
+
* TINA4_LOG_FILE path, always forces a file (explicit wins). stdout is ALWAYS on.
|
|
12
21
|
*
|
|
13
22
|
* Env vars:
|
|
14
23
|
* 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
24
|
* TINA4_LOG_DIR — directory for log files (default: "logs")
|
|
16
|
-
* TINA4_LOG_FORMAT — "text" | "json" (default: "text")
|
|
25
|
+
* TINA4_LOG_FORMAT — "text" | "json" (default: "text") — the ONLY format switch
|
|
17
26
|
* TINA4_LOG_OUTPUT — "stdout" | "file" | "both" (default: "stdout" → file only in dev)
|
|
18
27
|
* TINA4_LOG_ROTATE_SIZE — bytes; 0 disables rotation (default: 10485760 = 10MB)
|
|
19
28
|
* TINA4_LOG_ROTATE_KEEP — number of historical files to keep (default: 5)
|
|
20
29
|
* TINA4_LOG_LEVEL — minimum console level: DEBUG | INFO | WARNING | ERROR | CRITICAL (default: "INFO")
|
|
30
|
+
* TINA4_LOG_STRICT — truthy: a log-write failure THROWS instead of being swallowed (default: off)
|
|
31
|
+
*
|
|
32
|
+
* Every one of these is read LAZILY, on each log() call — a script, worker, CLI
|
|
33
|
+
* tool or test that never boots a server still gets the operator's configuration.
|
|
21
34
|
*
|
|
22
35
|
* Rotation is stdlib roll-your-own:
|
|
23
36
|
* - On each write, statSync the file. If size >= TINA4_LOG_ROTATE_SIZE, rotate.
|
|
@@ -27,6 +40,21 @@
|
|
|
27
40
|
*/
|
|
28
41
|
export declare class Log {
|
|
29
42
|
private static requestId;
|
|
43
|
+
/**
|
|
44
|
+
* What configure() was explicitly told, held HERE rather than written back
|
|
45
|
+
* into process.env (ADR-0041).
|
|
46
|
+
*
|
|
47
|
+
* configure() used to assign to process.env.TINA4_LOG_DIR / _LOG_FILE. That
|
|
48
|
+
* reached the right answer -- the argument won -- through a mechanism no
|
|
49
|
+
* other framework has: it DESTROYED the operator's value for the rest of the
|
|
50
|
+
* process, and every child process spawned afterwards inherited the
|
|
51
|
+
* argument instead of what the operator set. Reading configuration must not
|
|
52
|
+
* write it. Keeping the explicit values in their own slot means resolution
|
|
53
|
+
* is explicit > env > default with the environment left intact and still
|
|
54
|
+
* readable.
|
|
55
|
+
*/
|
|
56
|
+
private static explicitLogDir;
|
|
57
|
+
private static explicitLogFile;
|
|
30
58
|
/**
|
|
31
59
|
* Re-read all log-related env vars. Called on every log() so tests that
|
|
32
60
|
* mutate process.env between calls see the new values without having to
|
|
@@ -68,23 +96,65 @@ export declare class Log {
|
|
|
68
96
|
*/
|
|
69
97
|
static getRequestId(): string | undefined;
|
|
70
98
|
/**
|
|
71
|
-
* Configure
|
|
72
|
-
*
|
|
99
|
+
* Configure where logs are written.
|
|
100
|
+
*
|
|
101
|
+
* Logs land in a `logs/` folder by default. The argument OVERRIDES that, and
|
|
102
|
+
* it accepts a DIRECTORY or a FILE PATH:
|
|
103
|
+
*
|
|
104
|
+
* configure() -> ./logs/tina4.log + ./logs/error.log
|
|
105
|
+
* configure("/var/log/myapp") -> /var/log/myapp/tina4.log + error.log
|
|
106
|
+
* configure("/var/log/myapp/app.log") -> that exact file (no error.log sibling)
|
|
107
|
+
* configure({ logDir, logFile }) -> the explicit object form still works
|
|
108
|
+
*
|
|
109
|
+
* A plain string used to be accepted and silently ignored, because only the
|
|
110
|
+
* object form was read - so the call that works in the other three
|
|
111
|
+
* frameworks produced no log file here and said nothing (feature 2 of the
|
|
112
|
+
* audit, D4). Both forms now work.
|
|
73
113
|
*/
|
|
74
|
-
static configure(options
|
|
114
|
+
static configure(options?: string | {
|
|
75
115
|
logDir?: string;
|
|
76
116
|
logFile?: string;
|
|
77
117
|
}): void;
|
|
118
|
+
/**
|
|
119
|
+
* Forget what configure() was told, so resolution falls back to the
|
|
120
|
+
* environment and then the built-in defaults. Parity with PHP's Log::reset().
|
|
121
|
+
*
|
|
122
|
+
* This exists because the explicit values are now HELD here rather than
|
|
123
|
+
* written back into process.env, and that makes them STICKY for the life of
|
|
124
|
+
* the process -- which is right for an application (configure() at boot is
|
|
125
|
+
* the operator's instruction and a later stray env write should not silently
|
|
126
|
+
* re-point the logs) and wrong for a long-lived test process that wants to
|
|
127
|
+
* drive the logger purely from the environment afterwards.
|
|
128
|
+
*
|
|
129
|
+
* I removed this method once for having no callers. That was correct about
|
|
130
|
+
* the grep and wrong about the code: the full suite is the caller. Before the
|
|
131
|
+
* explicit slots existed, configure() ASSIGNED to process.env, so a later
|
|
132
|
+
* direct assignment simply overwrote it and env-driven cases kept working by
|
|
133
|
+
* accident. test/logger.test.ts depends on exactly that -- it configures a
|
|
134
|
+
* file early, then runs the whole default-output block off the environment
|
|
135
|
+
* (see its own note: "these cases must NOT route through Log.configure").
|
|
136
|
+
* Three of those cases failed on the lab until this came back.
|
|
137
|
+
*/
|
|
138
|
+
static reset(): void;
|
|
139
|
+
/**
|
|
140
|
+
* TINA4_LOG_APPEND — append (default) or overwrite on startup.
|
|
141
|
+
*
|
|
142
|
+
* APPEND IS THE DEFAULT: a log you can lose by restarting the process is not
|
|
143
|
+
* a log. Set it false for one file per run (a short CLI, a test fixture, a
|
|
144
|
+
* container shipping logs elsewhere); the files are truncated once here at
|
|
145
|
+
* configure time, never per line.
|
|
146
|
+
*/
|
|
147
|
+
private static applyAppendMode;
|
|
78
148
|
/** Log an informational message. */
|
|
79
|
-
static info(message:
|
|
149
|
+
static info(message: unknown, data?: unknown): void;
|
|
80
150
|
/** Log a debug message. */
|
|
81
|
-
static debug(message:
|
|
151
|
+
static debug(message: unknown, data?: unknown): void;
|
|
82
152
|
/** Log a warning message. */
|
|
83
|
-
static warning(message:
|
|
153
|
+
static warning(message: unknown, data?: unknown): void;
|
|
84
154
|
/** Backwards-compat alias for warning(). */
|
|
85
|
-
static warn(message:
|
|
155
|
+
static warn(message: unknown, data?: unknown): void;
|
|
86
156
|
/** Log an error message. */
|
|
87
|
-
static error(message:
|
|
157
|
+
static error(message: unknown, data?: unknown): void;
|
|
88
158
|
/**
|
|
89
159
|
* Log a critical message. CRITICAL is the highest severity (priority 4 >
|
|
90
160
|
* error 3) and ALWAYS emits like every other level — subject only to the
|
|
@@ -93,7 +163,7 @@ export declare class Log {
|
|
|
93
163
|
* critical 4 >= warning 2 so it would be in error.log on a split-file model).
|
|
94
164
|
* Matches Python master parity — there is no enable toggle.
|
|
95
165
|
*/
|
|
96
|
-
static critical(message:
|
|
166
|
+
static critical(message: unknown, data?: unknown): void;
|
|
97
167
|
/** Check if running in production mode (TINA4_DEBUG is not truthy). */
|
|
98
168
|
private static isProduction;
|
|
99
169
|
/** Get current ISO timestamp */
|
|
@@ -116,7 +186,14 @@ export declare class Log {
|
|
|
116
186
|
* `rotateSize` of 0 disables rotation entirely.
|
|
117
187
|
*/
|
|
118
188
|
private static rotateIfNeeded;
|
|
119
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Write a line to the log file, stripping ANSI codes.
|
|
191
|
+
*
|
|
192
|
+
* A failure is swallowed by default — logging must never crash the app. With
|
|
193
|
+
* TINA4_LOG_STRICT truthy it is RE-THROWN instead: an app that believes it is
|
|
194
|
+
* writing an audit trail into a read-only directory, and is not, is worse off
|
|
195
|
+
* than one that dies at the first line. Same contract in all four frameworks.
|
|
196
|
+
*/
|
|
120
197
|
private static writeToFile;
|
|
121
198
|
/** Core log method */
|
|
122
199
|
private static log;
|
|
@@ -2,7 +2,13 @@ import { DevMailbox } from "./devMailbox.js";
|
|
|
2
2
|
export interface SendResult {
|
|
3
3
|
success: boolean;
|
|
4
4
|
message: string;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The real Message-ID on success, `null` on failure — but ALWAYS present, so a
|
|
7
|
+
* caller reading `result.id` gets one shape from both branches (G6). It used to
|
|
8
|
+
* be omitted on the failure path, handing back `undefined` there and a string on
|
|
9
|
+
* success.
|
|
10
|
+
*/
|
|
11
|
+
id: string | null;
|
|
6
12
|
}
|
|
7
13
|
/**
|
|
8
14
|
* Raised when an IMAP read fails to connect, authenticate, or speak the
|
|
@@ -62,15 +68,33 @@ export interface ImapMessage {
|
|
|
62
68
|
snippet: string;
|
|
63
69
|
seen: boolean;
|
|
64
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* An attachment from a read() message. `content` is the RAW DECODED BYTES of the
|
|
73
|
+
* part (transfer-decoded from base64 / quoted-printable), the SAME convention as
|
|
74
|
+
* req.files[x].content — raw bytes, not base64 — so an attachment is downloadable
|
|
75
|
+
* as-is; `size` is that decoded byte length. Parity with Python's read()
|
|
76
|
+
* attachment dict {filename, content_type, size, content}, in Node's idiomatic
|
|
77
|
+
* camelCase (ADR-0008 / G5). #69 folded the bytes in HERE — there is no separate
|
|
78
|
+
* carrier (Python retired its attachments_data in 3.13.96).
|
|
79
|
+
*/
|
|
80
|
+
export interface ImapAttachment {
|
|
81
|
+
filename: string;
|
|
82
|
+
contentType: string;
|
|
83
|
+
size: number;
|
|
84
|
+
content: Buffer;
|
|
85
|
+
}
|
|
65
86
|
export interface ImapFullMessage {
|
|
66
87
|
uid: string;
|
|
67
88
|
subject: string;
|
|
68
89
|
from: string;
|
|
69
90
|
to: string;
|
|
70
91
|
cc: string;
|
|
92
|
+
/** ISO-8601, parsed from the Date header (parity with Python's _iso_date). */
|
|
71
93
|
date: string;
|
|
72
94
|
bodyText: string;
|
|
73
95
|
bodyHtml: string;
|
|
96
|
+
/** Attachments, each carrying its decoded bytes. Empty when the message has none (G5 / #69). */
|
|
97
|
+
attachments: ImapAttachment[];
|
|
74
98
|
headers: Record<string, string>;
|
|
75
99
|
}
|
|
76
100
|
export declare class Messenger {
|
|
@@ -116,6 +140,13 @@ export declare class Messenger {
|
|
|
116
140
|
/** The local mailbox, created on first capture and reused after. */
|
|
117
141
|
private getDevMailbox;
|
|
118
142
|
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>;
|
|
143
|
+
/**
|
|
144
|
+
* Render a Frond template STRING and send it as an HTML email (G7, parity with
|
|
145
|
+
* Python's send_template). Extra send() options (cc, bcc, replyTo, attachments,
|
|
146
|
+
* headers) pass through. If the Frond package cannot be loaded the raw template
|
|
147
|
+
* is sent verbatim (matches Python's ImportError fallback) rather than failing.
|
|
148
|
+
*/
|
|
149
|
+
sendTemplate(to: string | string[], subject: string, template: string, data?: Record<string, unknown>, cc?: string | string[], bcc?: string | string[], replyTo?: string, attachments?: string[], headers?: Record<string, string>): Promise<SendResult>;
|
|
119
150
|
/**
|
|
120
151
|
* Test the SMTP connection without sending an email.
|
|
121
152
|
*/
|
|
@@ -136,23 +167,33 @@ export declare class Messenger {
|
|
|
136
167
|
* Fetch latest messages from a folder.
|
|
137
168
|
* Returns list of message summaries.
|
|
138
169
|
*/
|
|
139
|
-
inbox(
|
|
170
|
+
inbox(folder?: string, limit?: number, offset?: number): Promise<ImapMessage[]>;
|
|
140
171
|
/**
|
|
141
|
-
* Read a single message by
|
|
172
|
+
* Read a single message by its IMAP UID.
|
|
142
173
|
*/
|
|
143
|
-
read(uid: string, folder?: string): Promise<ImapFullMessage>;
|
|
174
|
+
read(uid: string, folder?: string): Promise<ImapFullMessage | null>;
|
|
144
175
|
/**
|
|
145
176
|
* Search messages using IMAP search criteria.
|
|
146
177
|
*/
|
|
147
178
|
search(folder?: string, subject?: string, sender?: string, since?: string, before?: string, unseenOnly?: boolean, limit?: number): Promise<ImapMessage[]>;
|
|
148
179
|
/**
|
|
149
|
-
* Delete a message by UID.
|
|
180
|
+
* Delete a message by UID (mark \Deleted, then EXPUNGE).
|
|
181
|
+
*
|
|
182
|
+
* `delete` is the one cross-framework name (python/php/ruby/node all spell it
|
|
183
|
+
* `delete`). `deleteMessage` remains as a DEPRECATED alias for one release.
|
|
150
184
|
*/
|
|
185
|
+
delete(uid: string, folder?: string): Promise<void>;
|
|
186
|
+
/** @deprecated Use {@link delete} — kept as an alias for one release (G7). */
|
|
151
187
|
deleteMessage(uid: string, folder?: string): Promise<void>;
|
|
152
188
|
/**
|
|
153
|
-
* Mark a message as read.
|
|
189
|
+
* Mark a message as read (+FLAGS \Seen).
|
|
154
190
|
*/
|
|
155
191
|
markRead(uid: string, folder?: string): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Mark a message as unread (-FLAGS \Seen) — the inverse of markRead (G7,
|
|
194
|
+
* parity with Python's mark_unread).
|
|
195
|
+
*/
|
|
196
|
+
markUnread(uid: string, folder?: string): Promise<void>;
|
|
156
197
|
/**
|
|
157
198
|
* Count unseen messages in a folder.
|
|
158
199
|
*/
|
|
@@ -1,77 +1,41 @@
|
|
|
1
|
+
export declare function quickMetrics(root?: string): Record<string, any>;
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
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
|
|
3
|
+
* The native metrics engine could not produce a payload.
|
|
6
4
|
*
|
|
7
|
-
*
|
|
5
|
+
* Thrown instead of falling back to a second implementation.
|
|
8
6
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
line: number;
|
|
12
|
-
complexity: number;
|
|
13
|
-
loc: number;
|
|
14
|
-
args: string[];
|
|
15
|
-
file?: string;
|
|
7
|
+
export declare class MetricsEngineError extends Error {
|
|
8
|
+
constructor(message: string);
|
|
16
9
|
}
|
|
10
|
+
export declare const SEVERITY_RANK: Record<string, number>;
|
|
17
11
|
/**
|
|
18
|
-
*
|
|
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:
|
|
12
|
+
* Return [directory to scan, scanMode] for any metrics producer.
|
|
30
13
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* subtracts it, so its decisions stay with the function that contains it -
|
|
35
|
-
* moved, never lost.
|
|
14
|
+
* The engine is language-agnostic and cannot know which directory holds a
|
|
15
|
+
* framework package, so root resolution and the "framework" label stay here,
|
|
16
|
+
* shared by the census and the engine adapter so the two never disagree.
|
|
36
17
|
*/
|
|
37
|
-
export declare function
|
|
38
|
-
|
|
18
|
+
export declare function resolveScanTarget(root?: string): [string, string];
|
|
19
|
+
/** Absolute path to the tina4 CLI binary, or null when it is not installed. */
|
|
20
|
+
export declare function enginePath(): string | null;
|
|
21
|
+
/** Full code analysis from the native engine, shaped for the dashboard. */
|
|
39
22
|
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
23
|
export interface OffendersResult {
|
|
51
|
-
offenders:
|
|
24
|
+
offenders: Record<string, any>[];
|
|
52
25
|
summary: Record<string, any>;
|
|
53
26
|
}
|
|
54
27
|
/**
|
|
55
|
-
*
|
|
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`.
|
|
28
|
+
* Top code-health offenders from the native engine.
|
|
70
29
|
*
|
|
71
|
-
*
|
|
72
|
-
* the CLI
|
|
73
|
-
*
|
|
30
|
+
* The engine ranks and severity-tags them, and its own --fail-on gate reads the
|
|
31
|
+
* same list, so the CLI and the dashboard can never disagree about what counts
|
|
32
|
+
* as an offender.
|
|
74
33
|
*/
|
|
75
34
|
export declare function offenders(root?: string, top?: number): OffendersResult;
|
|
35
|
+
/**
|
|
36
|
+
* Per-file metrics from the native engine.
|
|
37
|
+
*
|
|
38
|
+
* The engine accepts a single file for --path, so one code path serves both the
|
|
39
|
+
* whole-tree scan and one file.
|
|
40
|
+
*/
|
|
76
41
|
export declare function fileDetail(filePath: string): Record<string, any>;
|
|
77
|
-
export {};
|