tina4-nodejs 3.13.98 → 3.13.100
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 +24 -25
- package/package.json +1 -2
- package/packages/cli/dist/bin.js +20698 -18983
- package/packages/cli/src/bin.ts +28 -71
- package/packages/cli/src/commands/migrate.ts +36 -75
- package/packages/cli/src/commands/migrateRollback.ts +10 -1
- package/packages/cli/src/commands/test.ts +92 -21
- package/packages/core/dist/index.js +20561 -18828
- package/packages/core/public/js/tina4-dev-admin.min.js +23 -19
- package/packages/core/src/ai.ts +38 -13
- package/packages/core/src/api.ts +13 -5
- package/packages/core/src/background.ts +9 -3
- package/packages/core/src/devAdmin.ts +135 -20
- package/packages/core/src/dispatchPipeline.ts +185 -1
- package/packages/core/src/docs.ts +33 -5
- package/packages/core/src/env.ts +1 -1
- package/packages/core/src/errorOverlay.ts +39 -48
- package/packages/core/src/fakeData.ts +15 -0
- package/packages/core/src/index.ts +17 -6
- package/packages/core/src/logger.ts +892 -572
- package/packages/core/src/mcp.ts +9 -1
- package/packages/core/src/messenger.ts +31 -4
- package/packages/core/src/middleware.ts +169 -43
- package/packages/core/src/portTakeover.ts +232 -0
- package/packages/core/src/request.ts +57 -8
- package/packages/core/src/response.ts +67 -0
- package/packages/core/src/router.ts +35 -7
- package/packages/core/src/server.ts +450 -190
- package/packages/core/src/static.ts +81 -12
- package/packages/core/src/testClient.ts +126 -137
- package/packages/core/src/testing.ts +16 -12
- package/packages/core/src/types.ts +21 -9
- package/packages/core/src/version.ts +66 -0
- package/packages/core/src/websocket.ts +2 -2
- package/packages/core/src/websocketBackplane.ts +2 -2
- package/packages/frond/dist/index.js +149 -49
- package/packages/frond/src/engine.ts +234 -52
- package/packages/orm/dist/index.js +10941 -9231
- package/packages/orm/src/adapters/firebird.ts +200 -27
- package/packages/orm/src/adapters/mongodb.ts +160 -10
- package/packages/orm/src/adapters/mssql.ts +38 -11
- package/packages/orm/src/adapters/mysql.ts +24 -1
- package/packages/orm/src/adapters/odbc.ts +127 -29
- package/packages/orm/src/adapters/postgres.ts +18 -0
- package/packages/orm/src/adapters/sqlite.ts +93 -14
- package/packages/orm/src/autoCrud.ts +72 -8
- package/packages/orm/src/baseModel.ts +323 -71
- package/packages/orm/src/cachedDatabase.ts +48 -1
- package/packages/orm/src/database.ts +162 -59
- package/packages/orm/src/fakeData.ts +6 -2
- package/packages/orm/src/index.ts +4 -1
- package/packages/orm/src/migration.ts +95 -52
- package/packages/orm/src/query.ts +16 -4
- package/packages/orm/src/seeder.ts +43 -25
- package/packages/orm/src/sqlTranslator.ts +104 -19
- package/packages/orm/src/types.ts +97 -21
- package/packages/orm/src/validation.ts +5 -1
- package/packages/swagger/dist/index.js +3 -2
- package/packages/swagger/src/generator.ts +19 -4
- package/packages/swagger/src/ui.ts +6 -4
- package/types/cli/src/bin.d.ts +0 -22
- package/types/core/src/ai.d.ts +29 -0
- package/types/core/src/api.d.ts +11 -4
- package/types/core/src/background.d.ts +5 -2
- package/types/core/src/devAdmin.d.ts +35 -0
- package/types/core/src/dispatchPipeline.d.ts +41 -1
- package/types/core/src/errorOverlay.d.ts +13 -13
- package/types/core/src/index.d.ts +9 -6
- package/types/core/src/logger.d.ts +111 -185
- package/types/core/src/middleware.d.ts +40 -5
- package/types/core/src/portTakeover.d.ts +50 -0
- package/types/core/src/request.d.ts +15 -0
- package/types/core/src/response.d.ts +29 -0
- package/types/core/src/server.d.ts +92 -0
- package/types/core/src/testClient.d.ts +29 -3
- package/types/core/src/testing.d.ts +16 -12
- package/types/core/src/types.d.ts +21 -9
- package/types/core/src/version.d.ts +11 -0
- package/types/core/src/websocketBackplane.d.ts +1 -1
- package/types/frond/src/engine.d.ts +60 -8
- package/types/orm/src/adapters/firebird.d.ts +61 -2
- package/types/orm/src/adapters/mongodb.d.ts +20 -0
- package/types/orm/src/adapters/mssql.d.ts +11 -0
- package/types/orm/src/adapters/mysql.d.ts +11 -0
- package/types/orm/src/adapters/odbc.d.ts +35 -4
- package/types/orm/src/adapters/postgres.d.ts +11 -0
- package/types/orm/src/adapters/sqlite.d.ts +23 -4
- package/types/orm/src/baseModel.d.ts +45 -25
- package/types/orm/src/cachedDatabase.d.ts +27 -1
- package/types/orm/src/database.d.ts +56 -6
- package/types/orm/src/index.d.ts +3 -2
- package/types/orm/src/migration.d.ts +23 -5
- package/types/orm/src/query.d.ts +3 -0
- package/types/orm/src/seeder.d.ts +15 -2
- package/types/orm/src/sqlTranslator.d.ts +17 -4
- package/types/orm/src/types.d.ts +75 -16
- package/packages/core/src/errorOverlay.test.ts +0 -122
|
@@ -4,7 +4,18 @@ export declare class TestResponse {
|
|
|
4
4
|
readonly body: string;
|
|
5
5
|
readonly headers: Record<string, string>;
|
|
6
6
|
readonly contentType: string;
|
|
7
|
-
|
|
7
|
+
/** Every value sent per header name (lowercased), in emission order. */
|
|
8
|
+
private readonly headerList;
|
|
9
|
+
constructor(statusCode: number, headerList: Record<string, string[]>, body: string);
|
|
10
|
+
/**
|
|
11
|
+
* Every value sent for `name` (case-insensitive), in emission order.
|
|
12
|
+
*
|
|
13
|
+
* A header sent once returns a one-item array; a header never sent returns
|
|
14
|
+
* an empty array. This is the one place a duplicate response header (two
|
|
15
|
+
* `Set-Cookie`) is visible — `headers[name]` always collapses to the LAST
|
|
16
|
+
* value, same as before (TC-HEADER-COLLAPSE, TC-DEC-02).
|
|
17
|
+
*/
|
|
18
|
+
getHeaderList(name: string): string[];
|
|
8
19
|
/** Parse body as JSON. */
|
|
9
20
|
json(): unknown;
|
|
10
21
|
/** Return body as a string. */
|
|
@@ -17,8 +28,23 @@ export interface RequestOptions {
|
|
|
17
28
|
headers?: Record<string, string>;
|
|
18
29
|
}
|
|
19
30
|
export declare class TestClient {
|
|
20
|
-
|
|
31
|
+
/** An explicitly-injected router (test isolation); undefined means "use the live server's router, or defaultRouter". */
|
|
32
|
+
private readonly explicitRouter;
|
|
33
|
+
private ctxPromise;
|
|
21
34
|
constructor(router?: Router);
|
|
35
|
+
/**
|
|
36
|
+
* Resolve (and memoise) the DispatchContext this client dispatches
|
|
37
|
+
* through.
|
|
38
|
+
*
|
|
39
|
+
* An explicitly-injected router always gets its OWN standalone context
|
|
40
|
+
* (buildDispatchContext) — the test-isolation contract an injected router
|
|
41
|
+
* has always had: a dedicated Router never races with whatever else is
|
|
42
|
+
* registered on defaultRouter or a live server. With no injected router,
|
|
43
|
+
* the LIVE server's context wins when one is running in this process
|
|
44
|
+
* (getLiveDispatchContext — maximum fidelity, mirrors Ruby's
|
|
45
|
+
* `RackApp.current`), else a standalone context bound to defaultRouter.
|
|
46
|
+
*/
|
|
47
|
+
private context;
|
|
22
48
|
/** Send a GET request. */
|
|
23
49
|
get(path: string, options?: RequestOptions): Promise<TestResponse>;
|
|
24
50
|
/** Send a POST request. */
|
|
@@ -29,7 +55,7 @@ export declare class TestClient {
|
|
|
29
55
|
patch(path: string, options?: RequestOptions): Promise<TestResponse>;
|
|
30
56
|
/** Send a DELETE request. */
|
|
31
57
|
delete(path: string, options?: RequestOptions): Promise<TestResponse>;
|
|
32
|
-
/** Build a mock request
|
|
58
|
+
/** Build a mock request/response pair and dispatch it through the REAL pipeline (runDispatch). */
|
|
33
59
|
private _request;
|
|
34
60
|
/** Gather the captured status/headers/body into a TestResponse and free the socket. */
|
|
35
61
|
private _collect;
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tina4 Node.js — Inline testing framework.
|
|
3
3
|
*
|
|
4
|
-
* Attach test
|
|
4
|
+
* Attach test expectations to functions and run them all at once.
|
|
5
5
|
*
|
|
6
|
-
* import { tests,
|
|
6
|
+
* import { tests, expectEqual, expectRaises, runAll } from "./testing.js";
|
|
7
7
|
*
|
|
8
8
|
* const add = tests(
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* expectEqual([5, 3], 8),
|
|
10
|
+
* expectRaises(Error, [null]),
|
|
11
11
|
* )(function add(a: number, b: number | null = null): number {
|
|
12
12
|
* if (b === null) throw new Error("b required");
|
|
13
13
|
* return a + b;
|
|
14
14
|
* });
|
|
15
15
|
*
|
|
16
16
|
* runAll();
|
|
17
|
+
*
|
|
18
|
+
* The builders are named expect* — DESCRIPTORS that record an expectation for the
|
|
19
|
+
* runner — deliberately distinct from the xUnit assert* on Tina4Test (test.ts),
|
|
20
|
+
* so importing the wrong surface can never silently change call semantics.
|
|
17
21
|
*/
|
|
18
22
|
interface Assertion {
|
|
19
23
|
type: "equal" | "raises" | "true" | "false";
|
|
@@ -31,14 +35,14 @@ interface TestResults {
|
|
|
31
35
|
message?: string;
|
|
32
36
|
}>;
|
|
33
37
|
}
|
|
34
|
-
/**
|
|
35
|
-
export declare function
|
|
36
|
-
/**
|
|
37
|
-
export declare function
|
|
38
|
-
/**
|
|
39
|
-
export declare function
|
|
40
|
-
/**
|
|
41
|
-
export declare function
|
|
38
|
+
/** Expect that calling the function with `args` returns `expected`. */
|
|
39
|
+
export declare function expectEqual(args: unknown[], expected: unknown): Assertion;
|
|
40
|
+
/** Expect that calling the function with `args` throws an instance of `errorClass`. */
|
|
41
|
+
export declare function expectRaises(errorClass: new (...a: unknown[]) => Error, args: unknown[]): Assertion;
|
|
42
|
+
/** Expect that calling the function with `args` returns a truthy value. */
|
|
43
|
+
export declare function expectTrue(args: unknown[]): Assertion;
|
|
44
|
+
/** Expect that calling the function with `args` returns a falsy value. */
|
|
45
|
+
export declare function expectFalse(args: unknown[]): Assertion;
|
|
42
46
|
/**
|
|
43
47
|
* Attach inline test assertions to a function.
|
|
44
48
|
*
|
|
@@ -18,20 +18,32 @@ export interface Tina4Request extends IncomingMessage {
|
|
|
18
18
|
/**
|
|
19
19
|
* Path params. Typed params arrive coerced: `{id:int}`/`{id:integer}` and
|
|
20
20
|
* `{p:float}`/`{p:number}` are JS `number`s; every other type and untyped
|
|
21
|
-
* `{id}` stay `string` (parity with Python/PHP/Ruby).
|
|
21
|
+
* `{id}` stay `string` (parity with Python/PHP/Ruby). ROUTE-ONLY — never
|
|
22
|
+
* the query string or body (REQ-PARAM-POLLUTION, 3.13.99). Writable: the
|
|
23
|
+
* router assigns it AFTER createRequest() builds the wire-derived fields
|
|
24
|
+
* below, once a route has matched.
|
|
22
25
|
*/
|
|
23
26
|
params: Record<string, string | number>;
|
|
24
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Core wire-derived fields below are `readonly` (REQ-IMMUTABILITY-DIVERGE,
|
|
29
|
+
* 3.13.99) — set once in createRequest() and never reassigned afterward,
|
|
30
|
+
* matching PHP's `readonly` properties and Ruby's writer-less attr_reader
|
|
31
|
+
* (the two languages already at this posture; this is TS-compile-time
|
|
32
|
+
* only, like PHP/Ruby's enforcement is at their own language boundary).
|
|
33
|
+
* `params`/`body`/`files`/`session`/`user` stay mutable: the router and
|
|
34
|
+
* middleware legitimately set them after construction.
|
|
35
|
+
*/
|
|
36
|
+
readonly query: Record<string, string>;
|
|
25
37
|
/**
|
|
26
38
|
* Request path only — no query string. Matches `request.path` in
|
|
27
39
|
* Python/PHP/Ruby. Example: `/users/42`.
|
|
28
40
|
*/
|
|
29
|
-
path: string;
|
|
41
|
+
readonly path: string;
|
|
30
42
|
/**
|
|
31
43
|
* Raw query string with no leading "?". Matches `request.query_string`
|
|
32
44
|
* (Python/Ruby) and `request.queryString` (PHP). Example: `"page=2"`.
|
|
33
45
|
*/
|
|
34
|
-
queryString: string;
|
|
46
|
+
readonly queryString: string;
|
|
35
47
|
/**
|
|
36
48
|
* Full absolute URL — `scheme://host[:port]/path[?query]`.
|
|
37
49
|
* Honours X-Forwarded-Proto / X-Forwarded-Host. Matches PHP/Ruby/Python parity.
|
|
@@ -39,19 +51,19 @@ export interface Tina4Request extends IncomingMessage {
|
|
|
39
51
|
* Note: this overrides Node's native `IncomingMessage.url` (which contains
|
|
40
52
|
* only path+query). Inside Tina4 handlers, `req.url` is always the full URL.
|
|
41
53
|
*/
|
|
42
|
-
url: string;
|
|
54
|
+
readonly url: string;
|
|
43
55
|
body: unknown;
|
|
44
|
-
ip: string;
|
|
56
|
+
readonly ip: string;
|
|
45
57
|
/**
|
|
46
58
|
* Raw socket peer address - NEVER honours X-Forwarded-For (which any
|
|
47
59
|
* caller can spoof), so it can be trusted for security decisions.
|
|
48
60
|
* Empty for in-process / synthetic requests. Parity with Python's
|
|
49
61
|
* request.remote_ip and PHP's Request::$remoteIp.
|
|
50
62
|
*/
|
|
51
|
-
remoteIp: string;
|
|
63
|
+
readonly remoteIp: string;
|
|
52
64
|
files: Record<string, UploadedFile | UploadedFile[]>;
|
|
53
|
-
cookies: Record<string, string>;
|
|
54
|
-
contentType: string;
|
|
65
|
+
readonly cookies: Record<string, string>;
|
|
66
|
+
readonly contentType: string;
|
|
55
67
|
/**
|
|
56
68
|
* NULL when the session backend was unusable for this request (ADR-0021).
|
|
57
69
|
* The request path logs the failure and degrades rather than 500-ing, so a
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk up from this file to the nearest package.json carrying a non-empty
|
|
3
|
+
* `version` field. Stops at the first hit (nearest wins), so a published
|
|
4
|
+
* `@tina4/core` install resolves its OWN package.json, and the monorepo dev
|
|
5
|
+
* tree resolves the workspace root's -- both the real, current version.
|
|
6
|
+
* Falls back to "0.0.0" only if none is found within the walk (a layout with
|
|
7
|
+
* no package.json anywhere in its ancestry at all).
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveFrameworkVersion(): string;
|
|
10
|
+
/** Resolved once at module load -- every @tina4/core surface imports this. */
|
|
11
|
+
export declare const TINA4_VERSION: string;
|
|
@@ -155,7 +155,7 @@ export declare class WsBackplaneManager {
|
|
|
155
155
|
/** Minimal logger shape so the manager doesn't import the logger module. */
|
|
156
156
|
export interface WsBackplaneLogger {
|
|
157
157
|
info(message: string): void;
|
|
158
|
-
|
|
158
|
+
warning(message: string): void;
|
|
159
159
|
error(message: string): void;
|
|
160
160
|
}
|
|
161
161
|
/** Build the cross-framework envelope. Exported for tests. */
|
|
@@ -14,6 +14,15 @@ export interface LiveResponse {
|
|
|
14
14
|
}
|
|
15
15
|
/** WebSocket broadcaster hook wired by @tina4/core so pushLive can broadcast. */
|
|
16
16
|
export type LiveBroadcaster = (wsPath: string | null, name: string, envelope: string) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Cache for parsed filter chains: expr string -> [variable, filters].
|
|
19
|
+
* Exported (like TEMPLATE_CACHE_MAX) so the ADR-0004 bound has something for
|
|
20
|
+
* a test to inspect directly — module-level state has no instance to read
|
|
21
|
+
* off, unlike `compiled`/`compiledStrings`/`fragmentCache`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const filterChainCache: Map<string, [string, [string, unknown[]][]]>;
|
|
24
|
+
/** Cache for parsed dotted/bracket paths: expr string -> [parts, fromBracket]. Exported for the same reason as filterChainCache. */
|
|
25
|
+
export declare const pathParseCache: Map<string, [string[], boolean[]]>;
|
|
17
26
|
/**
|
|
18
27
|
* Hard cap on the template caches — `compiled` and `compiledStrings`
|
|
19
28
|
* (ADR-0004, parity with PHP/Python/Ruby TEMPLATE_CACHE_MAX).
|
|
@@ -26,6 +35,18 @@ export type LiveBroadcaster = (wsPath: string | null, name: string, envelope: st
|
|
|
26
35
|
* dynamically adds an entry per distinct string.
|
|
27
36
|
*/
|
|
28
37
|
export declare const TEMPLATE_CACHE_MAX = 256;
|
|
38
|
+
/**
|
|
39
|
+
* Hard cap on every per-expression memo cache — `filterChainCache` and
|
|
40
|
+
* `pathParseCache` (ADR-0004, parity with PHP's MEMO_CACHE_MAX and the
|
|
41
|
+
* Python master's `@lru_cache(maxsize=1024)` on the equivalent module-level
|
|
42
|
+
* parsers). Deliberately higher than TEMPLATE_CACHE_MAX: one entry here is a
|
|
43
|
+
* small parsed-path array, orders of magnitude smaller than a token list.
|
|
44
|
+
*
|
|
45
|
+
* Also reused for `fragmentCache` (the `{% cache %}` tag's runtime store):
|
|
46
|
+
* TEMPLATE_CACHE_MAX, not this one — a rendered fragment is a whole HTML
|
|
47
|
+
* string, the same order of magnitude as a compiled template.
|
|
48
|
+
*/
|
|
49
|
+
export declare const MEMO_CACHE_MAX = 1024;
|
|
29
50
|
/**
|
|
30
51
|
* Set the session ID used by formToken() / form_token() for CSRF session binding.
|
|
31
52
|
*/
|
|
@@ -95,20 +116,16 @@ export declare class Frond {
|
|
|
95
116
|
sandbox(filters?: string[], tags?: string[], vars?: string[]): Frond;
|
|
96
117
|
unsandbox(): Frond;
|
|
97
118
|
/**
|
|
98
|
-
* Register a custom filter
|
|
99
|
-
*
|
|
100
|
-
* the live instance's local filter map also receives the addition
|
|
101
|
-
* immediately. Mirrors Python's _ClassOrInstanceMethod dual-call.
|
|
119
|
+
* Register a custom filter on this instance only. Use the static method
|
|
120
|
+
* for process-global registration. tina4: ADR-0052.
|
|
102
121
|
*/
|
|
103
122
|
addFilter(name: string, fn: FilterFn): void;
|
|
104
123
|
/**
|
|
105
|
-
* Register a global variable
|
|
106
|
-
* at class level — see ``addFilter`` for the dual-call semantics.
|
|
124
|
+
* Register a global variable on this instance only.
|
|
107
125
|
*/
|
|
108
126
|
addGlobal(name: string, value: unknown): void;
|
|
109
127
|
/**
|
|
110
|
-
* Register a custom test
|
|
111
|
-
* ``addFilter`` for the dual-call semantics.
|
|
128
|
+
* Register a custom test on this instance only.
|
|
112
129
|
*/
|
|
113
130
|
addTest(name: string, fn: TestFn): void;
|
|
114
131
|
/**
|
|
@@ -126,6 +143,16 @@ export declare class Frond {
|
|
|
126
143
|
/** Render a debug dump of a value as HTML — parity with PHP/Ruby/Python.
|
|
127
144
|
* Gated on TINA4_DEBUG=true. Returns empty string in production. */
|
|
128
145
|
renderDump(value: unknown): string;
|
|
146
|
+
/**
|
|
147
|
+
* Load a template's source, CONFINED under the templates directory.
|
|
148
|
+
*
|
|
149
|
+
* Every path-taking tag ({% include %}, {% extends %}, {% import %},
|
|
150
|
+
* {% from ... import %}) funnels through this one loader, so this single guard
|
|
151
|
+
* confines them all (TAG-DEC-01): a name that is absolute, climbs out with a
|
|
152
|
+
* `..` up-level segment, or resolves through a symlink to a location OUTSIDE
|
|
153
|
+
* the templates root is REFUSED -- the outside file is never read. Template
|
|
154
|
+
* -side analogue of the static-asset confinement (feature 41 / ADR-0050).
|
|
155
|
+
*/
|
|
129
156
|
private load;
|
|
130
157
|
/** Execute pre-tokenized template against context. */
|
|
131
158
|
private executeCached;
|
|
@@ -133,6 +160,31 @@ export declare class Frond {
|
|
|
133
160
|
private executeWithSource;
|
|
134
161
|
private execute;
|
|
135
162
|
private extractBlocks;
|
|
163
|
+
/**
|
|
164
|
+
* Depth-aware block substitution against `source` (typically the
|
|
165
|
+
* fully-resolved root template).
|
|
166
|
+
*
|
|
167
|
+
* A single regex `.replace()` pass (the flat `pattern` this replaces in
|
|
168
|
+
* renderWithBlocks) pairs an OUTER block's open tag with the FIRST
|
|
169
|
+
* `{% endblock %}` found -- which, when the outer block wraps a NESTED
|
|
170
|
+
* `{% block %}`, is the nested block's own close tag, not the outer's.
|
|
171
|
+
* That silently truncates the outer block's captured content and drops
|
|
172
|
+
* everything after the inner endblock (the root-nested-block
|
|
173
|
+
* content-loss bug). This scans with an open/close depth counter
|
|
174
|
+
* instead (mirroring extractBlocks), so an outer block always captures
|
|
175
|
+
* its FULL body, nested child blocks included.
|
|
176
|
+
*
|
|
177
|
+
* The content chosen for each block -- the child override in `blocks`
|
|
178
|
+
* if present, else the block's own default body -- is then recursively
|
|
179
|
+
* substituted against the SAME `blocks` map before being tokenized and
|
|
180
|
+
* rendered, so a block nested inside another block resolves correctly
|
|
181
|
+
* regardless of which template in the inheritance chain declared the
|
|
182
|
+
* nesting (the root, an intermediate, however many levels deep).
|
|
183
|
+
*
|
|
184
|
+
* `{{ parent() }}` / `{{ super() }}` inside a block still render that
|
|
185
|
+
* block's OWN default content at this level (lazy, on first call).
|
|
186
|
+
*/
|
|
187
|
+
private substituteBlocks;
|
|
136
188
|
private renderWithBlocks;
|
|
137
189
|
private renderTokens;
|
|
138
190
|
/**
|
|
@@ -89,9 +89,42 @@ export declare class FirebirdAdapter implements DatabaseAdapter {
|
|
|
89
89
|
private db;
|
|
90
90
|
private transaction;
|
|
91
91
|
private _lastInsertId;
|
|
92
|
+
/** Resolved node-firebird config, kept so a dead connection can re-attach. */
|
|
93
|
+
private fbConfig;
|
|
94
|
+
private static readonly DEAD_CONN_MARKERS;
|
|
95
|
+
/** Is this a dead-socket error worth a transparent reconnect (not a logical SQL error)? */
|
|
96
|
+
static isDeadConnection(err: unknown): boolean;
|
|
92
97
|
constructor(config: FirebirdConfig | string);
|
|
93
98
|
/** Connect to Firebird. Must be called before using the adapter. */
|
|
99
|
+
/** ADR-0044 required adapter capability. */
|
|
100
|
+
getDatabaseType(): string;
|
|
101
|
+
/** ADR-0044: readable/writable native boolean. */
|
|
102
|
+
autocommit: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* ADR-0044 / DBA-P02: every built-in adapter can guarantee an atomic
|
|
105
|
+
* multi-row batch by default. A test-only deployment representing one
|
|
106
|
+
* that cannot sets this false so executeMany rejects BEFORE the first
|
|
107
|
+
* write rather than risking partial durability.
|
|
108
|
+
*/
|
|
109
|
+
supportsAtomicBatch: boolean;
|
|
94
110
|
connect(): Promise<void>;
|
|
111
|
+
private attachOnce;
|
|
112
|
+
/**
|
|
113
|
+
* Attach with a BOUNDED retry (FB-DEC-03). node-firebird's SRP login over
|
|
114
|
+
* WireCrypt is intermittently flaky (~12% measured historically), and a flake
|
|
115
|
+
* surfaces as an auth/handshake error indistinguishable from a real one, so a
|
|
116
|
+
* bounded retry-all is the robust, honest handling: a transient handshake
|
|
117
|
+
* failure recovers, while a genuine bad credential still fails after the bound
|
|
118
|
+
* -- never skipped, never papered over.
|
|
119
|
+
*/
|
|
120
|
+
private attachWithRetry;
|
|
121
|
+
/**
|
|
122
|
+
* Run a node-firebird op; on a DEAD-connection error (outside an explicit
|
|
123
|
+
* transaction) re-attach once and retry (FB-DEC-01). Inside a transaction the
|
|
124
|
+
* error surfaces -- atomicity beats resilience, and the caller rolls back.
|
|
125
|
+
*/
|
|
126
|
+
private withReconnect;
|
|
127
|
+
private reconnectFirebird;
|
|
95
128
|
private parseUrl;
|
|
96
129
|
private ensureConnected;
|
|
97
130
|
/** Translate SQL for Firebird dialect. */
|
|
@@ -121,6 +154,28 @@ export declare class FirebirdAdapter implements DatabaseAdapter {
|
|
|
121
154
|
private statementHandle;
|
|
122
155
|
private queryPromise;
|
|
123
156
|
private executePromise;
|
|
157
|
+
/**
|
|
158
|
+
* The real affected-row count. node-firebird gives NO DML count of its own
|
|
159
|
+
* (the callback result is undefined -- MEASURED), but Firebird 5 multi-row
|
|
160
|
+
* RETURNING surfaces one row per affected row, so `... RETURNING 1` + the row
|
|
161
|
+
* count IS the real count (FB-AFFECTED-FAB replaces the hardcoded 1). RETURNING
|
|
162
|
+
* a constant, not `*`, so a large update/delete does not materialise full rows.
|
|
163
|
+
*/
|
|
164
|
+
private executeReturningCount;
|
|
165
|
+
/**
|
|
166
|
+
* Firebird has no generic last_insert_id -- read the GEN_<TABLE>_ID generator
|
|
167
|
+
* the row's BEFORE INSERT trigger drew from (FB-LASTID-GAP). Column-name-
|
|
168
|
+
* independent, so correct for a non-`id` PK too. null when the table has no
|
|
169
|
+
* such generator (GEN_ID then throws -> caught).
|
|
170
|
+
*/
|
|
171
|
+
private readGeneratorId;
|
|
172
|
+
/**
|
|
173
|
+
* Read a node-firebird BLOB column into a Buffer. A BLOB arrives as a STREAMING
|
|
174
|
+
* FUNCTION (fn((err, name, emitter) => emitter.on('data'|'end'))), NOT a Buffer
|
|
175
|
+
* -- MEASURED -- so the old decodeBlobs no-op leaked the function to the caller
|
|
176
|
+
* and no bytes round-tripped (FB-BLOB-SRP-UNVERIFIED).
|
|
177
|
+
*/
|
|
178
|
+
private readBlob;
|
|
124
179
|
execute(sql: string, params?: unknown[]): unknown;
|
|
125
180
|
executeMany(sql: string, paramsList: unknown[][]): {
|
|
126
181
|
totalAffected: number;
|
|
@@ -133,8 +188,12 @@ export declare class FirebirdAdapter implements DatabaseAdapter {
|
|
|
133
188
|
executeAsync(sql: string, params?: unknown[]): Promise<unknown>;
|
|
134
189
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
|
|
135
190
|
queryAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
136
|
-
/**
|
|
137
|
-
*
|
|
191
|
+
/**
|
|
192
|
+
* Read out any BLOB columns to Buffers. node-firebird returns a BLOB as a
|
|
193
|
+
* STREAMING FUNCTION, not a Buffer (MEASURED), so a column whose value is a
|
|
194
|
+
* function is read via readBlob(); everything else passes through unchanged
|
|
195
|
+
* (FB-BLOB-SRP-UNVERIFIED -- the old no-op leaked the function to the caller).
|
|
196
|
+
*/
|
|
138
197
|
private decodeBlobs;
|
|
139
198
|
fetch<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): T[];
|
|
140
199
|
fetchAsync<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): Promise<T[]>;
|
|
@@ -24,6 +24,17 @@ export declare class MongodbAdapter implements DatabaseAdapter {
|
|
|
24
24
|
private _dbName;
|
|
25
25
|
constructor(config: MongoConfig | string);
|
|
26
26
|
/** Connect to MongoDB. Must be called before using the adapter. */
|
|
27
|
+
/** ADR-0044 required adapter capability. */
|
|
28
|
+
getDatabaseType(): string;
|
|
29
|
+
/** ADR-0044: readable/writable native boolean. */
|
|
30
|
+
autocommit: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* ADR-0044 / DBA-P02: every built-in adapter can guarantee an atomic
|
|
33
|
+
* multi-row batch by default. A test-only deployment representing one
|
|
34
|
+
* that cannot sets this false so executeMany rejects BEFORE the first
|
|
35
|
+
* write rather than risking partial durability.
|
|
36
|
+
*/
|
|
37
|
+
supportsAtomicBatch: boolean;
|
|
27
38
|
connect(): Promise<void>;
|
|
28
39
|
private ensureConnected;
|
|
29
40
|
/** Execute a SQL-like statement translated to a MongoDB operation. */
|
|
@@ -43,6 +54,15 @@ export declare class MongodbAdapter implements DatabaseAdapter {
|
|
|
43
54
|
fetchAsync<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): Promise<T[]>;
|
|
44
55
|
fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
|
|
45
56
|
fetchOneAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Atomic, monotonic, concurrency-safe next id — feature 16. A
|
|
59
|
+
* findOneAndUpdate($inc) on the tina4_sequences collection, keyed by _id (its
|
|
60
|
+
* built-in unique index makes concurrent first-use upserts race-safe: two
|
|
61
|
+
* callers can never create two counters for one table). Seeds from
|
|
62
|
+
* MAX(pkColumn) the FIRST time only ($setOnInsert). Throws on an impossible
|
|
63
|
+
* empty result rather than returning a fixed id that could collide with a row.
|
|
64
|
+
*/
|
|
65
|
+
getNextId(table: string, pkColumn?: string): Promise<number>;
|
|
46
66
|
insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
|
|
47
67
|
insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
|
|
48
68
|
update(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): DatabaseResult;
|
|
@@ -22,6 +22,17 @@ export declare class MssqlAdapter implements DatabaseAdapter {
|
|
|
22
22
|
private _inTransaction;
|
|
23
23
|
constructor(config: MssqlConfig | string);
|
|
24
24
|
/** Connect to MSSQL. Must be called before using the adapter. */
|
|
25
|
+
/** ADR-0044 required adapter capability. */
|
|
26
|
+
getDatabaseType(): string;
|
|
27
|
+
/** ADR-0044: readable/writable native boolean. */
|
|
28
|
+
autocommit: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* ADR-0044 / DBA-P02: every built-in adapter can guarantee an atomic
|
|
31
|
+
* multi-row batch by default. A test-only deployment representing one
|
|
32
|
+
* that cannot sets this false so executeMany rejects BEFORE the first
|
|
33
|
+
* write rather than risking partial durability.
|
|
34
|
+
*/
|
|
35
|
+
supportsAtomicBatch: boolean;
|
|
25
36
|
connect(): Promise<void>;
|
|
26
37
|
private parseUrl;
|
|
27
38
|
private ensureConnected;
|
|
@@ -21,6 +21,17 @@ export declare class MysqlAdapter implements DatabaseAdapter {
|
|
|
21
21
|
private _inTransaction;
|
|
22
22
|
constructor(config: MysqlConfig | string);
|
|
23
23
|
/** Connect to MySQL. Must be called before using the adapter. */
|
|
24
|
+
/** ADR-0044 required adapter capability. */
|
|
25
|
+
getDatabaseType(): string;
|
|
26
|
+
/** ADR-0044: readable/writable native boolean. */
|
|
27
|
+
autocommit: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* ADR-0044 / DBA-P02: every built-in adapter can guarantee an atomic
|
|
30
|
+
* multi-row batch by default. A test-only deployment representing one
|
|
31
|
+
* that cannot sets this false so executeMany rejects BEFORE the first
|
|
32
|
+
* write rather than risking partial durability.
|
|
33
|
+
*/
|
|
34
|
+
supportsAtomicBatch: boolean;
|
|
24
35
|
connect(): Promise<void>;
|
|
25
36
|
private ensureConnected;
|
|
26
37
|
private queryPromise;
|
|
@@ -2,6 +2,10 @@ import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } fro
|
|
|
2
2
|
export interface OdbcConfig {
|
|
3
3
|
/** Full ODBC connection string, e.g. "DSN=MyDSN" or "DRIVER={SQL Server};SERVER=host;DATABASE=db" */
|
|
4
4
|
connectionString: string;
|
|
5
|
+
/** Optional username; appended as UID when not already in the connection string. */
|
|
6
|
+
username?: string;
|
|
7
|
+
/** Optional password; appended as PWD when not already in the connection string. */
|
|
8
|
+
password?: string;
|
|
5
9
|
}
|
|
6
10
|
export declare class OdbcAdapter implements DatabaseAdapter {
|
|
7
11
|
private config;
|
|
@@ -16,6 +20,14 @@ export declare class OdbcAdapter implements DatabaseAdapter {
|
|
|
16
20
|
constructor(config: OdbcConfig | string);
|
|
17
21
|
/** Extract the raw ODBC connection string from config. */
|
|
18
22
|
private getConnectionString;
|
|
23
|
+
/**
|
|
24
|
+
* The connection string with credentials applied. ODBC has no separate-
|
|
25
|
+
* credentials API (odbc.connect() reads only the string), so a username/
|
|
26
|
+
* password passed to Database.create() must be folded in as UID/PWD - the
|
|
27
|
+
* adapter used to drop them. Never used for diagnostics (describeTarget reads
|
|
28
|
+
* the raw string), so the password never reaches an error message.
|
|
29
|
+
*/
|
|
30
|
+
private effectiveConnectionString;
|
|
19
31
|
/**
|
|
20
32
|
* The address for a diagnostic message. ODBC hides it inside an opaque
|
|
21
33
|
* driver keyword string, so this reads the standard keywords and falls back to
|
|
@@ -24,6 +36,17 @@ export declare class OdbcAdapter implements DatabaseAdapter {
|
|
|
24
36
|
*/
|
|
25
37
|
private describeTarget;
|
|
26
38
|
/** Connect to the ODBC data source. Must be called before using the adapter. */
|
|
39
|
+
/** ADR-0044 required adapter capability. */
|
|
40
|
+
getDatabaseType(): string;
|
|
41
|
+
/** ADR-0044: readable/writable native boolean. */
|
|
42
|
+
autocommit: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* ADR-0044 / DBA-P02: every built-in adapter can guarantee an atomic
|
|
45
|
+
* multi-row batch by default. A test-only deployment representing one
|
|
46
|
+
* that cannot sets this false so executeMany rejects BEFORE the first
|
|
47
|
+
* write rather than risking partial durability.
|
|
48
|
+
*/
|
|
49
|
+
supportsAtomicBatch: boolean;
|
|
27
50
|
connect(): Promise<void>;
|
|
28
51
|
private ensureConnected;
|
|
29
52
|
execute(sql: string, params?: unknown[]): unknown;
|
|
@@ -56,18 +79,20 @@ export declare class OdbcAdapter implements DatabaseAdapter {
|
|
|
56
79
|
totalAffected: number;
|
|
57
80
|
lastId?: number | bigint;
|
|
58
81
|
}>;
|
|
82
|
+
/** The real affected-row count from an odbc result, when the driver reports it. */
|
|
83
|
+
private affectedCount;
|
|
59
84
|
/** Run a SELECT and return all matching rows. */
|
|
60
85
|
queryAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
61
86
|
/** Run a SELECT with optional LIMIT/OFFSET pagination. */
|
|
62
87
|
fetchAsync<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): Promise<T[]>;
|
|
63
88
|
/** Run a SELECT and return the first row or null. */
|
|
64
89
|
fetchOneAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null>;
|
|
65
|
-
/** Insert a single row
|
|
66
|
-
insertAsync(table: string, data: Record<string, unknown>): Promise<DatabaseResult>;
|
|
90
|
+
/** Insert a single row, or a list of rows as a batch. */
|
|
91
|
+
insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
|
|
67
92
|
/** Update rows in a table matching filter. */
|
|
68
|
-
updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): Promise<DatabaseResult>;
|
|
93
|
+
updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
|
|
69
94
|
/** Delete rows from a table. */
|
|
70
|
-
deleteAsync(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[]): Promise<DatabaseResult>;
|
|
95
|
+
deleteAsync(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): Promise<DatabaseResult>;
|
|
71
96
|
/** Begin a transaction. */
|
|
72
97
|
startTransactionAsync(): Promise<void>;
|
|
73
98
|
/** Commit the current transaction. */
|
|
@@ -78,6 +103,12 @@ export declare class OdbcAdapter implements DatabaseAdapter {
|
|
|
78
103
|
tablesAsync(): Promise<string[]>;
|
|
79
104
|
/** Get column metadata for a table using ODBC catalog functions. */
|
|
80
105
|
columnsAsync(table: string): Promise<ColumnInfo[]>;
|
|
106
|
+
/**
|
|
107
|
+
* The table's primary-key columns from the ODBC catalog (SQLPrimaryKeys),
|
|
108
|
+
* lower-cased for case-insensitive matching. Empty on any target that does not
|
|
109
|
+
* report them - the write-guard then requires an explicit filter.
|
|
110
|
+
*/
|
|
111
|
+
private primaryKeyColumns;
|
|
81
112
|
/** Check whether a table exists. */
|
|
82
113
|
tableExistsAsync(name: string): Promise<boolean>;
|
|
83
114
|
/** Create a table from a FieldDefinition map. Uses generic SQL — works with most ODBC sources. */
|
|
@@ -21,6 +21,17 @@ export declare class PostgresAdapter implements DatabaseAdapter {
|
|
|
21
21
|
private _inTransaction;
|
|
22
22
|
constructor(config: PostgresConfig | string);
|
|
23
23
|
/** Connect to PostgreSQL. Must be called before using the adapter. */
|
|
24
|
+
/** ADR-0044 required adapter capability. */
|
|
25
|
+
getDatabaseType(): string;
|
|
26
|
+
/** ADR-0044: readable/writable native boolean. */
|
|
27
|
+
autocommit: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* ADR-0044 / DBA-P02: every built-in adapter can guarantee an atomic
|
|
30
|
+
* multi-row batch by default. A test-only deployment representing one
|
|
31
|
+
* that cannot sets this false so executeMany rejects BEFORE the first
|
|
32
|
+
* write rather than risking partial durability.
|
|
33
|
+
*/
|
|
34
|
+
supportsAtomicBatch: boolean;
|
|
24
35
|
connect(): Promise<void>;
|
|
25
36
|
private ensureConnected;
|
|
26
37
|
/** Convert ? placeholders to $1, $2, ... for pg. */
|
|
@@ -2,6 +2,24 @@ import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } fro
|
|
|
2
2
|
export declare class SQLiteAdapter implements DatabaseAdapter {
|
|
3
3
|
private db;
|
|
4
4
|
private _lastInsertId;
|
|
5
|
+
/** ADR-0044: readable/writable native boolean. */
|
|
6
|
+
autocommit: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* ADR-0044 / DBA-P02: every built-in adapter can guarantee an atomic
|
|
9
|
+
* multi-row batch by default. A test-only deployment representing one that
|
|
10
|
+
* cannot (a standalone MongoDB without a replica set is the motivating real
|
|
11
|
+
* case) sets this false so executeMany rejects BEFORE the first write.
|
|
12
|
+
*/
|
|
13
|
+
supportsAtomicBatch: boolean;
|
|
14
|
+
/** ADR-0044 required adapter capability. */
|
|
15
|
+
getDatabaseType(): string;
|
|
16
|
+
/**
|
|
17
|
+
* ADR-0044 canonical lifecycle name. A genuine no-op: `node:sqlite` opens
|
|
18
|
+
* the file synchronously in the constructor (see the timeout note below),
|
|
19
|
+
* so by the time a caller could reach connect() the adapter is already
|
|
20
|
+
* connected — repeated calls open no additional physical connection.
|
|
21
|
+
*/
|
|
22
|
+
connect(): void;
|
|
5
23
|
/**
|
|
6
24
|
* TINA4_DATABASE_CONNECT_TIMEOUT DOES NOT APPLY HERE, deliberately.
|
|
7
25
|
*
|
|
@@ -16,10 +34,7 @@ export declare class SQLiteAdapter implements DatabaseAdapter {
|
|
|
16
34
|
*/
|
|
17
35
|
constructor(dbPath: string);
|
|
18
36
|
execute(sql: string, params?: unknown[]): unknown;
|
|
19
|
-
executeMany(sql: string, paramsList: unknown[][]):
|
|
20
|
-
totalAffected: number;
|
|
21
|
-
lastId?: number | bigint;
|
|
22
|
-
};
|
|
37
|
+
executeMany(sql: string, paramsList: unknown[][]): DatabaseResult;
|
|
23
38
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
|
|
24
39
|
fetch<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): T[];
|
|
25
40
|
fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
|
|
@@ -33,6 +48,10 @@ export declare class SQLiteAdapter implements DatabaseAdapter {
|
|
|
33
48
|
getTables(): string[];
|
|
34
49
|
getColumns(table: string): ColumnInfo[];
|
|
35
50
|
lastInsertId(): number | bigint | null;
|
|
51
|
+
private _closed;
|
|
52
|
+
/** ADR-0044 (DBA-L02): idempotent — node:sqlite's DatabaseSync.close()
|
|
53
|
+
* throws when called on an already-closed database, so a second close()
|
|
54
|
+
* must not reach it. */
|
|
36
55
|
close(): void;
|
|
37
56
|
/**
|
|
38
57
|
* Atomically increment and return the next value of a tina4_sequences row.
|