tina4-nodejs 3.13.20 → 3.13.23
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 +13 -8
- package/package.json +1 -1
- package/packages/core/src/cache.ts +8 -4
- package/packages/core/src/server.ts +26 -1
- package/packages/orm/src/cachedDatabase.ts +261 -15
- package/packages/orm/src/database.ts +112 -40
- package/packages/orm/src/index.ts +2 -1
package/CLAUDE.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.13.
|
|
1
|
+
# CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.13.23)
|
|
2
2
|
|
|
3
3
|
> This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
|
|
4
4
|
|
|
5
5
|
## What This Project Is
|
|
6
6
|
|
|
7
|
-
Tina4 for Node.js/TypeScript v3.13.
|
|
7
|
+
Tina4 for Node.js/TypeScript v3.13.23 — The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
|
|
8
8
|
|
|
9
9
|
The philosophy: zero ceremony, batteries included, file system as source of truth.
|
|
10
10
|
|
|
@@ -589,8 +589,12 @@ db.getColumns(table): { name, type, nullable?, default?, primaryKey? }[]
|
|
|
589
589
|
// auto-creates Postgres sequences, and uses native Firebird generators.
|
|
590
590
|
db.getNextId(table, pkColumn?, generatorName?): number
|
|
591
591
|
|
|
592
|
-
//
|
|
593
|
-
db.
|
|
592
|
+
// DB query cache — request-scoped auto cache is ON by default (TINA4_AUTO_CACHING=true,
|
|
593
|
+
// TTL TINA4_AUTO_CACHING_TTL=5s): dedupes identical db.fetch()/ORM reads within a request,
|
|
594
|
+
// flushed on any write. Persistent cross-request cache opt-in via TINA4_DB_CACHE=true
|
|
595
|
+
// (TTL TINA4_DB_CACHE_TTL=30s). cacheStats()/cacheClear() are now real (the DB query cache
|
|
596
|
+
// is wired — previously db.cacheStats() hardcoded size:0 and db.cacheClear() was a no-op).
|
|
597
|
+
db.cacheStats(): { enabled, size, ttl, mode } // mode: "request" | "persistent" | "off"
|
|
594
598
|
db.cacheClear(): void
|
|
595
599
|
|
|
596
600
|
// Connection pool access (null when pooling disabled)
|
|
@@ -895,13 +899,14 @@ const u = cacheGet("user:1");
|
|
|
895
899
|
cacheDelete("user:1");
|
|
896
900
|
cacheClear();
|
|
897
901
|
|
|
898
|
-
cacheStats(); // { hits, misses, size, backend }
|
|
902
|
+
cacheStats(); // { hits, misses, size, backend } — now reflects the real KV backend
|
|
903
|
+
// (previously it wrongly read the response-cache middleware store)
|
|
899
904
|
```
|
|
900
905
|
|
|
901
906
|
Environment:
|
|
902
907
|
- `TINA4_CACHE_BACKEND` — `memory` | `redis` | `file` (default: `memory`)
|
|
903
908
|
- `TINA4_CACHE_URL` — `redis://localhost:6379` (redis backend only)
|
|
904
|
-
- `TINA4_CACHE_TTL` — default TTL seconds (default: `
|
|
909
|
+
- `TINA4_CACHE_TTL` — default TTL seconds (default: `60`)
|
|
905
910
|
- `TINA4_CACHE_MAX_ENTRIES` — max entries (default: `1000`)
|
|
906
911
|
|
|
907
912
|
## Firebird-Specific Rules
|
|
@@ -1098,12 +1103,12 @@ When adding new features, add a corresponding `test/<feature>.test.ts` file.
|
|
|
1098
1103
|
## v3 Features Summary
|
|
1099
1104
|
|
|
1100
1105
|
- **45 built-in features**, zero third-party dependencies
|
|
1101
|
-
- **3,
|
|
1106
|
+
- **3,708 tests** passing across all modules
|
|
1102
1107
|
- **Race-safe `getNextId()`** with atomic sequence table (`tina4_sequences`) for SQLite/MySQL/MSSQL; PostgreSQL auto-creates sequences
|
|
1103
1108
|
- **Frond template engine optimizations**: pre-compiled regexes, lazy loop context (copy-on-write), filter chain caching, path split caching, inline common filters (11-15% speedup)
|
|
1104
1109
|
- **Production server auto-detect**: `npx tina4nodejs serve --production` auto-uses cluster mode
|
|
1105
1110
|
- **`npx tina4nodejs generate`**: model, route, migration, middleware scaffolding
|
|
1106
|
-
- **Database**: 5 engines (SQLite, PostgreSQL, MySQL, MSSQL, Firebird), query caching (`TINA4_DB_CACHE=true`)
|
|
1111
|
+
- **Database**: 5 engines (SQLite, PostgreSQL, MySQL, MSSQL, Firebird), DB query caching — request-scoped auto cache **on by default** (`TINA4_AUTO_CACHING=true`, TTL `TINA4_AUTO_CACHING_TTL=5`s) dedupes identical `db.fetch()`/ORM reads within a request and flushes on writes; persistent cross-request cache opt-in via `TINA4_DB_CACHE=true` (TTL `TINA4_DB_CACHE_TTL=30`s). `db.cacheStats()`/`db.cacheClear()` are now real (the DB query cache is wired; was dead code)
|
|
1107
1112
|
- **Sessions**: file backend (default). `TINA4_SESSION_SAMESITE` env var (default: Lax)
|
|
1108
1113
|
- **Queue**: file/RabbitMQ/Kafka/MongoDB backends, configured via env vars
|
|
1109
1114
|
- **Cache**: memory/Redis/file backends
|
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Environment:
|
|
23
23
|
* TINA4_CACHE_BACKEND — memory | redis | file (default: memory)
|
|
24
24
|
* TINA4_CACHE_URL — redis://localhost:6379 (redis only)
|
|
25
|
-
* TINA4_CACHE_TTL — default TTL in seconds (default:
|
|
25
|
+
* TINA4_CACHE_TTL — default TTL in seconds (default: 60)
|
|
26
26
|
* TINA4_CACHE_MAX_ENTRIES — max entries (default: 1000)
|
|
27
27
|
*/
|
|
28
28
|
|
|
@@ -455,9 +455,13 @@ export function clearCache(): void {
|
|
|
455
455
|
store.clear();
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
/**
|
|
459
|
-
|
|
460
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Get KV cache stats — reports the same backend that cacheGet/cacheSet/cacheDelete use,
|
|
460
|
+
* so a value stored via cacheSet() is reflected here. Mirrors cache_stats() in the
|
|
461
|
+
* Python / PHP / Ruby frameworks. (Identical to cacheBackendStats(), kept for parity naming.)
|
|
462
|
+
*/
|
|
463
|
+
export function cacheStats(): { hits: number; misses: number; size: number; backend: string } {
|
|
464
|
+
return _getBackend().stats();
|
|
461
465
|
}
|
|
462
466
|
|
|
463
467
|
// ── Module-level direct cache API (backend-aware) ─────────────────
|
|
@@ -610,6 +610,12 @@ function deployGallery(name) {
|
|
|
610
610
|
// Allows handle() to route requests without requiring a reference to the server.
|
|
611
611
|
let _dispatchFn: ((rawReq: IncomingMessage, rawRes: ServerResponse) => Promise<void>) | null = null;
|
|
612
612
|
|
|
613
|
+
// Lazily-resolved Database.resetRequestCaches binding (or null if the ORM is
|
|
614
|
+
// not installed). Memoised so the dynamic import happens once, then every
|
|
615
|
+
// request reuses the resolved function — see the request-scoped cache boundary
|
|
616
|
+
// in dispatch().
|
|
617
|
+
let _resetRequestCaches: Promise<(() => void) | null> | undefined;
|
|
618
|
+
|
|
613
619
|
/** Module-level server handle for start()/stop() parity. */
|
|
614
620
|
let _serverHandle: { close: () => void; router: Router; port: number } | null = null;
|
|
615
621
|
|
|
@@ -914,6 +920,25 @@ ${reset}
|
|
|
914
920
|
const req = createRequest(rawReq);
|
|
915
921
|
const res = createResponse(rawRes);
|
|
916
922
|
|
|
923
|
+
// Request-scoped DB query cache boundary: clear the request-scoped cache on
|
|
924
|
+
// every live connection at the START of each request so it never serves
|
|
925
|
+
// rows across requests (persistent-mode connections are left alone). The
|
|
926
|
+
// ORM is loaded lazily and may be absent — failures here must never break a
|
|
927
|
+
// request, so this is best-effort. Mirrors Python's dispatcher calling
|
|
928
|
+
// Database.reset_request_caches(). The cached() promise resolves once on
|
|
929
|
+
// first use; subsequent requests reuse the resolved module.
|
|
930
|
+
if (_resetRequestCaches === undefined) {
|
|
931
|
+
_resetRequestCaches = import("../../orm/src/index.js")
|
|
932
|
+
.then((orm) => orm.resetRequestCaches as () => void)
|
|
933
|
+
.catch(() => null);
|
|
934
|
+
}
|
|
935
|
+
try {
|
|
936
|
+
const reset = await _resetRequestCaches;
|
|
937
|
+
if (reset) reset();
|
|
938
|
+
} catch {
|
|
939
|
+
/* ORM not installed / cache unavailable — non-fatal */
|
|
940
|
+
}
|
|
941
|
+
|
|
917
942
|
// RFC 9110 §9.3.2: the server MUST NOT send content in a HEAD response.
|
|
918
943
|
// Intercept rawRes.write / rawRes.end so every code path — explicit
|
|
919
944
|
// Router.head() handler, GET auto-fallback, 405 / 404 responses — drops
|
|
@@ -975,7 +1000,7 @@ ${reset}
|
|
|
975
1000
|
} as typeof rawRes.end;
|
|
976
1001
|
}
|
|
977
1002
|
|
|
978
|
-
// res.render()
|
|
1003
|
+
// res.render() is handled natively by response.ts via Frond
|
|
979
1004
|
|
|
980
1005
|
try {
|
|
981
1006
|
// Run middleware chain
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tina4 Cached Database — Transparent query cache decorator for DatabaseAdapter.
|
|
3
3
|
*
|
|
4
|
-
* Wraps any DatabaseAdapter and caches SELECT results from fetch() and fetchOne()
|
|
5
|
-
* Write operations (insert, update, delete, execute
|
|
4
|
+
* Wraps any DatabaseAdapter and caches SELECT results from fetch() and fetchOne()
|
|
5
|
+
* (plus their *Async variants). Write operations (insert, update, delete, execute,
|
|
6
|
+
* createTable, addColumn) flush the entire cache when caching is enabled.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
* TINA4_DB_CACHE=true # enable (default: false)
|
|
9
|
-
* TINA4_DB_CACHE_TTL=30 # TTL in seconds (default: 30)
|
|
8
|
+
* One store, two layers (mirrors the Python master — tina4_python/database/connection.py):
|
|
10
9
|
*
|
|
11
|
-
*
|
|
10
|
+
* • request-scoped (DEFAULT ON, off-switch TINA4_AUTO_CACHING=false) — dedupes
|
|
11
|
+
* identical SELECTs to protect the DB from rapid repeat reads. Cleared at the
|
|
12
|
+
* START of every HTTP request (via Database.resetRequestCaches()) AND on any
|
|
13
|
+
* write, with a short safety TTL (TINA4_AUTO_CACHING_TTL, default 5s) for
|
|
14
|
+
* non-request contexts (scripts/workers).
|
|
15
|
+
* • persistent (opt-in, TINA4_DB_CACHE=true) — cross-request TTL cache that is
|
|
16
|
+
* NOT cleared per request; entries expire by TINA4_DB_CACHE_TTL (default 30s).
|
|
17
|
+
*
|
|
18
|
+
* enabled = persistent || requestScoped
|
|
19
|
+
* mode = persistent ? "persistent" : (requestScoped ? "request" : "off")
|
|
20
|
+
* ttl = persistent ? 30 : 5 (env-overridable)
|
|
21
|
+
*
|
|
22
|
+
* Usage (the framework wires this automatically at the adapter bind path):
|
|
12
23
|
* import { CachedDatabaseAdapter } from "@tina4/orm";
|
|
13
24
|
* import { SQLiteAdapter } from "./adapters/sqlite.js";
|
|
14
25
|
*
|
|
15
26
|
* const raw = new SQLiteAdapter("./data/app.db");
|
|
16
27
|
* const db = new CachedDatabaseAdapter(raw);
|
|
17
28
|
* db.fetch("SELECT * FROM users"); // cached on second call
|
|
18
|
-
* db.cacheStats(); // { enabled
|
|
29
|
+
* db.cacheStats(); // { enabled, mode, hits, misses, size, ttl }
|
|
19
30
|
*/
|
|
20
31
|
|
|
21
32
|
import { QueryCache } from "./sqlTranslation.js";
|
|
@@ -25,26 +36,109 @@ function isTruthy(val: string | undefined): boolean {
|
|
|
25
36
|
return ["true", "1", "yes", "on"].includes((val ?? "").trim().toLowerCase());
|
|
26
37
|
}
|
|
27
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Options for wrapping an adapter with a query cache. When several pooled
|
|
41
|
+
* connections must share one cache store (so a write on any connection
|
|
42
|
+
* invalidates reads cached by all of them), pass the same `sharedCache`.
|
|
43
|
+
*/
|
|
44
|
+
export interface CachedAdapterOptions {
|
|
45
|
+
/** Force-enable the persistent (cross-request) layer. Defaults to TINA4_DB_CACHE. */
|
|
46
|
+
persistent?: boolean;
|
|
47
|
+
/** Force-enable the request-scoped layer. Defaults to TINA4_AUTO_CACHING (default true). */
|
|
48
|
+
requestScoped?: boolean;
|
|
49
|
+
/** Override the effective TTL (seconds). Defaults to the mode-appropriate env var. */
|
|
50
|
+
ttl?: number;
|
|
51
|
+
/** Share a single QueryCache store across multiple wrappers (pooled connections). */
|
|
52
|
+
sharedCache?: QueryCache;
|
|
53
|
+
}
|
|
54
|
+
|
|
28
55
|
export class CachedDatabaseAdapter implements DatabaseAdapter {
|
|
56
|
+
/**
|
|
57
|
+
* Live wrappers, so the request dispatcher can clear the request-scoped cache
|
|
58
|
+
* on every connection at the start of each HTTP request. Mirrors Python's
|
|
59
|
+
* `Database._instances` WeakSet. A WeakSet lets closed connections be GC'd.
|
|
60
|
+
*/
|
|
61
|
+
private static instances: Set<CachedDatabaseAdapter> = new Set();
|
|
62
|
+
|
|
29
63
|
private adapter: DatabaseAdapter;
|
|
30
64
|
private cache: QueryCache;
|
|
65
|
+
/** Persistent (cross-request) layer — TINA4_DB_CACHE. */
|
|
66
|
+
private cachePersistent: boolean;
|
|
67
|
+
/** Request-scoped layer — TINA4_AUTO_CACHING (default ON). */
|
|
68
|
+
private cacheRequestScoped: boolean;
|
|
31
69
|
private enabled: boolean;
|
|
32
70
|
private ttl: number;
|
|
33
71
|
private hits: number = 0;
|
|
34
72
|
private misses: number = 0;
|
|
35
73
|
|
|
36
|
-
constructor(adapter: DatabaseAdapter,
|
|
74
|
+
constructor(adapter: DatabaseAdapter, options: CachedAdapterOptions = {}) {
|
|
37
75
|
this.adapter = adapter;
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
40
|
-
|
|
76
|
+
this.cachePersistent = options.persistent ?? isTruthy(process.env.TINA4_DB_CACHE);
|
|
77
|
+
this.cacheRequestScoped = options.requestScoped
|
|
78
|
+
?? (process.env.TINA4_AUTO_CACHING === undefined ? true : isTruthy(process.env.TINA4_AUTO_CACHING));
|
|
79
|
+
this.enabled = this.cachePersistent || this.cacheRequestScoped;
|
|
80
|
+
|
|
81
|
+
if (options.ttl !== undefined) {
|
|
82
|
+
this.ttl = options.ttl;
|
|
83
|
+
} else if (this.cachePersistent) {
|
|
84
|
+
this.ttl = parseInt(process.env.TINA4_DB_CACHE_TTL ?? "30", 10);
|
|
85
|
+
} else {
|
|
86
|
+
this.ttl = parseInt(process.env.TINA4_AUTO_CACHING_TTL ?? "5", 10);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.cache = options.sharedCache ?? new QueryCache({ defaultTtl: this.ttl, maxSize: 10000 });
|
|
90
|
+
CachedDatabaseAdapter.instances.add(this);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Cache mode helpers ────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/** Current cache mode: "persistent" | "request" | "off". */
|
|
96
|
+
cacheMode(): "persistent" | "request" | "off" {
|
|
97
|
+
return this.cachePersistent ? "persistent" : (this.cacheRequestScoped ? "request" : "off");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Whether either cache layer is active. */
|
|
101
|
+
cacheEnabled(): boolean {
|
|
102
|
+
return this.enabled;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Clear the request-scoped cache at the start of an HTTP request.
|
|
107
|
+
* No-op in persistent mode (cross-request entries survive to their TTL).
|
|
108
|
+
* Cumulative hit/miss counters are preserved. Mirrors Python's
|
|
109
|
+
* `Database.cache_new_request()`.
|
|
110
|
+
*/
|
|
111
|
+
cacheNewRequest(): void {
|
|
112
|
+
if (this.cacheRequestScoped && !this.cachePersistent) {
|
|
113
|
+
this.cache.clear();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Clear the request-scoped cache on every live wrapper. The request
|
|
119
|
+
* dispatcher calls this at the start of each HTTP request so request-scoped
|
|
120
|
+
* caching never serves rows across requests. Persistent-mode connections are
|
|
121
|
+
* left alone. Mirrors Python's `Database.reset_request_caches()` classmethod.
|
|
122
|
+
*/
|
|
123
|
+
static resetRequestCaches(): void {
|
|
124
|
+
for (const inst of CachedDatabaseAdapter.instances) {
|
|
125
|
+
try {
|
|
126
|
+
inst.cacheNewRequest();
|
|
127
|
+
} catch {
|
|
128
|
+
/* a closed/broken wrapper must not break the request boundary */
|
|
129
|
+
}
|
|
130
|
+
}
|
|
41
131
|
}
|
|
42
132
|
|
|
43
|
-
// ── Cache
|
|
133
|
+
// ── Cache stats / management ──────────────────────────────
|
|
44
134
|
|
|
45
|
-
cacheStats(): {
|
|
135
|
+
cacheStats(): {
|
|
136
|
+
enabled: boolean; mode: "persistent" | "request" | "off";
|
|
137
|
+
hits: number; misses: number; size: number; ttl: number;
|
|
138
|
+
} {
|
|
46
139
|
return {
|
|
47
140
|
enabled: this.enabled,
|
|
141
|
+
mode: this.cacheMode(),
|
|
48
142
|
hits: this.hits,
|
|
49
143
|
misses: this.misses,
|
|
50
144
|
size: this.cache.size(),
|
|
@@ -52,17 +146,19 @@ export class CachedDatabaseAdapter implements DatabaseAdapter {
|
|
|
52
146
|
};
|
|
53
147
|
}
|
|
54
148
|
|
|
149
|
+
/** Flush the query cache and reset counters. Mirrors Python `cache_clear()`. */
|
|
55
150
|
cacheClear(): void {
|
|
56
151
|
this.cache.clear();
|
|
57
152
|
this.hits = 0;
|
|
58
153
|
this.misses = 0;
|
|
59
154
|
}
|
|
60
155
|
|
|
156
|
+
/** Clear the entire query cache (called on writes). */
|
|
61
157
|
private invalidate(): void {
|
|
62
158
|
this.cache.clear();
|
|
63
159
|
}
|
|
64
160
|
|
|
65
|
-
// ── DatabaseAdapter interface
|
|
161
|
+
// ── DatabaseAdapter interface — writes flush, reads cache ──
|
|
66
162
|
|
|
67
163
|
execute(sql: string, params?: unknown[]): unknown {
|
|
68
164
|
if (this.enabled) this.invalidate();
|
|
@@ -75,6 +171,22 @@ export class CachedDatabaseAdapter implements DatabaseAdapter {
|
|
|
75
171
|
}
|
|
76
172
|
|
|
77
173
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[] {
|
|
174
|
+
// The Node ORM reads most rows through query() (find/where/all/relationships),
|
|
175
|
+
// so caching here is what makes ORM reads dedupe — matching the Python master
|
|
176
|
+
// where every ORM read flows through the cached db.fetch(). Same store, same
|
|
177
|
+
// counters, flushed on writes.
|
|
178
|
+
if (this.enabled) {
|
|
179
|
+
const key = QueryCache.queryKey(sql + ":Q", params as unknown[] | undefined);
|
|
180
|
+
const cached = this.cache.get<T[]>(key);
|
|
181
|
+
if (cached !== undefined) {
|
|
182
|
+
this.hits++;
|
|
183
|
+
return cached;
|
|
184
|
+
}
|
|
185
|
+
const result = this.adapter.query<T>(sql, params);
|
|
186
|
+
this.cache.set(key, result, this.ttl);
|
|
187
|
+
this.misses++;
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
78
190
|
return this.adapter.query(sql, params);
|
|
79
191
|
}
|
|
80
192
|
|
|
@@ -150,6 +262,7 @@ export class CachedDatabaseAdapter implements DatabaseAdapter {
|
|
|
150
262
|
}
|
|
151
263
|
|
|
152
264
|
close(): void {
|
|
265
|
+
CachedDatabaseAdapter.instances.delete(this);
|
|
153
266
|
this.adapter.close();
|
|
154
267
|
}
|
|
155
268
|
|
|
@@ -171,8 +284,141 @@ export class CachedDatabaseAdapter implements DatabaseAdapter {
|
|
|
171
284
|
this.adapter.addColumn?.(table, colName, def);
|
|
172
285
|
}
|
|
173
286
|
|
|
287
|
+
// ── Async passthroughs (PostgreSQL/MySQL/MSSQL/Firebird/Mongo) ──
|
|
288
|
+
//
|
|
289
|
+
// The async adapters implement *Async methods; the Database wrapper and the
|
|
290
|
+
// ORM read/write path prefer those when present. We mirror them here so the
|
|
291
|
+
// cache sits in front of the async path too. Reads cache; writes flush.
|
|
292
|
+
|
|
293
|
+
async fetchAsync<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): Promise<T[]> {
|
|
294
|
+
if (this.enabled) {
|
|
295
|
+
const key = QueryCache.queryKey(sql + `:L${limit}:S${skip}`, params as unknown[] | undefined);
|
|
296
|
+
const cached = this.cache.get<T[]>(key);
|
|
297
|
+
if (cached !== undefined) {
|
|
298
|
+
this.hits++;
|
|
299
|
+
return cached;
|
|
300
|
+
}
|
|
301
|
+
const result = (this.adapter as any).fetchAsync
|
|
302
|
+
? await (this.adapter as any).fetchAsync(sql, params, limit, skip)
|
|
303
|
+
: this.adapter.fetch<T>(sql, params, limit, skip);
|
|
304
|
+
this.cache.set(key, result, this.ttl);
|
|
305
|
+
this.misses++;
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
return (this.adapter as any).fetchAsync
|
|
309
|
+
? await (this.adapter as any).fetchAsync(sql, params, limit, skip)
|
|
310
|
+
: this.adapter.fetch<T>(sql, params, limit, skip);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async fetchOneAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null> {
|
|
314
|
+
if (this.enabled) {
|
|
315
|
+
const key = QueryCache.queryKey(sql + ":ONE", params as unknown[] | undefined);
|
|
316
|
+
const cached = this.cache.get<T | null>(key);
|
|
317
|
+
if (cached !== undefined) {
|
|
318
|
+
this.hits++;
|
|
319
|
+
return cached;
|
|
320
|
+
}
|
|
321
|
+
const result = (this.adapter as any).fetchOneAsync
|
|
322
|
+
? await (this.adapter as any).fetchOneAsync(sql, params)
|
|
323
|
+
: this.adapter.fetchOne<T>(sql, params);
|
|
324
|
+
this.cache.set(key, result, this.ttl);
|
|
325
|
+
this.misses++;
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
return (this.adapter as any).fetchOneAsync
|
|
329
|
+
? await (this.adapter as any).fetchOneAsync(sql, params)
|
|
330
|
+
: this.adapter.fetchOne<T>(sql, params);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async queryAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]> {
|
|
334
|
+
if (this.enabled) {
|
|
335
|
+
const key = QueryCache.queryKey(sql + ":Q", params as unknown[] | undefined);
|
|
336
|
+
const cached = this.cache.get<T[]>(key);
|
|
337
|
+
if (cached !== undefined) {
|
|
338
|
+
this.hits++;
|
|
339
|
+
return cached;
|
|
340
|
+
}
|
|
341
|
+
const result = (this.adapter as any).queryAsync
|
|
342
|
+
? await (this.adapter as any).queryAsync(sql, params)
|
|
343
|
+
: this.adapter.query<T>(sql, params);
|
|
344
|
+
this.cache.set(key, result, this.ttl);
|
|
345
|
+
this.misses++;
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
return (this.adapter as any).queryAsync
|
|
349
|
+
? await (this.adapter as any).queryAsync(sql, params)
|
|
350
|
+
: this.adapter.query<T>(sql, params);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async executeAsync(sql: string, params?: unknown[]): Promise<unknown> {
|
|
354
|
+
if (this.enabled) this.invalidate();
|
|
355
|
+
return (this.adapter as any).executeAsync
|
|
356
|
+
? await (this.adapter as any).executeAsync(sql, params)
|
|
357
|
+
: this.adapter.execute(sql, params);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult> {
|
|
361
|
+
if (this.enabled) this.invalidate();
|
|
362
|
+
return (this.adapter as any).insertAsync
|
|
363
|
+
? await (this.adapter as any).insertAsync(table, data)
|
|
364
|
+
: this.adapter.insert(table, data);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown>, params?: unknown[]): Promise<DatabaseResult> {
|
|
368
|
+
if (this.enabled) this.invalidate();
|
|
369
|
+
return (this.adapter as any).updateAsync
|
|
370
|
+
? await (this.adapter as any).updateAsync(table, data, filter, params)
|
|
371
|
+
: this.adapter.update(table, data, filter);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async deleteAsync(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): Promise<DatabaseResult> {
|
|
375
|
+
if (this.enabled) this.invalidate();
|
|
376
|
+
return (this.adapter as any).deleteAsync
|
|
377
|
+
? await (this.adapter as any).deleteAsync(table, filter, params)
|
|
378
|
+
: this.adapter.delete(table, filter as Record<string, unknown>);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async startTransactionAsync(): Promise<void> {
|
|
382
|
+
if ((this.adapter as any).startTransactionAsync) await (this.adapter as any).startTransactionAsync();
|
|
383
|
+
else this.adapter.startTransaction();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async commitAsync(): Promise<void> {
|
|
387
|
+
if ((this.adapter as any).commitAsync) await (this.adapter as any).commitAsync();
|
|
388
|
+
else this.adapter.commit();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async rollbackAsync(): Promise<void> {
|
|
392
|
+
if ((this.adapter as any).rollbackAsync) await (this.adapter as any).rollbackAsync();
|
|
393
|
+
else this.adapter.rollback();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async tableExistsAsync(name: string): Promise<boolean> {
|
|
397
|
+
return (this.adapter as any).tableExistsAsync
|
|
398
|
+
? await (this.adapter as any).tableExistsAsync(name)
|
|
399
|
+
: this.adapter.tableExists(name);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async tablesAsync(): Promise<string[]> {
|
|
403
|
+
return (this.adapter as any).tablesAsync
|
|
404
|
+
? await (this.adapter as any).tablesAsync()
|
|
405
|
+
: this.adapter.tables();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async columnsAsync(table: string): Promise<ColumnInfo[]> {
|
|
409
|
+
return (this.adapter as any).columnsAsync
|
|
410
|
+
? await (this.adapter as any).columnsAsync(table)
|
|
411
|
+
: this.adapter.columns(table);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async createTableAsync(name: string, columns: Record<string, FieldDefinition>): Promise<void> {
|
|
415
|
+
if (this.enabled) this.invalidate();
|
|
416
|
+
if ((this.adapter as any).createTableAsync) await (this.adapter as any).createTableAsync(name, columns);
|
|
417
|
+
else this.adapter.createTable(name, columns);
|
|
418
|
+
}
|
|
419
|
+
|
|
174
420
|
/**
|
|
175
|
-
* Access the underlying adapter directly.
|
|
421
|
+
* Access the underlying (unwrapped) adapter directly.
|
|
176
422
|
*/
|
|
177
423
|
getAdapter(): DatabaseAdapter {
|
|
178
424
|
return this.adapter;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import type { DatabaseAdapter, DatabaseResult as DatabaseWriteResult, ColumnInfo, FieldDefinition } from "./types.js";
|
|
3
3
|
import { DatabaseResult } from "./databaseResult.js";
|
|
4
|
+
import { CachedDatabaseAdapter, type CachedAdapterOptions } from "./cachedDatabase.js";
|
|
5
|
+
import { QueryCache } from "./sqlTranslation.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* v3.13.12 — strip trailing `;` and whitespace from user-supplied SQL
|
|
@@ -128,8 +130,45 @@ export function extractLastInsertId(result: unknown): number | bigint | null {
|
|
|
128
130
|
let activeAdapter: DatabaseAdapter | null = null;
|
|
129
131
|
const namedAdapters: Map<string, DatabaseAdapter> = new Map();
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Wrap a raw adapter with the query cache so BOTH `db.fetch()` (via the
|
|
135
|
+
* Database wrapper) AND ORM reads (via `getAdapter()` / `getNamedAdapter()`)
|
|
136
|
+
* are cached through the same store and counters.
|
|
137
|
+
*
|
|
138
|
+
* Idempotent: an already-wrapped adapter is returned as-is, so re-binding the
|
|
139
|
+
* same adapter (or binding the adapter a Database wrapper already holds) never
|
|
140
|
+
* double-wraps. `options.sharedCache` backs all pooled connections with one
|
|
141
|
+
* store so a write on any connection invalidates reads cached by all of them.
|
|
142
|
+
*
|
|
143
|
+
* Caching is ON by default (request-scoped, TINA4_AUTO_CACHING) and additionally
|
|
144
|
+
* persistent when TINA4_DB_CACHE=true. Off-switch: TINA4_AUTO_CACHING=false (and
|
|
145
|
+
* TINA4_DB_CACHE unset) — then the wrapper passes everything straight through.
|
|
146
|
+
*/
|
|
147
|
+
export function wrapWithCache(adapter: DatabaseAdapter, options?: CachedAdapterOptions): DatabaseAdapter {
|
|
148
|
+
if (adapter instanceof CachedDatabaseAdapter) return adapter;
|
|
149
|
+
return new CachedDatabaseAdapter(adapter, options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolve the underlying wrapped adapter for a given raw adapter — used so the
|
|
154
|
+
* Database wrapper and `getAdapter()` end up holding the SAME
|
|
155
|
+
* CachedDatabaseAdapter instance (one cache, one set of counters).
|
|
156
|
+
*/
|
|
157
|
+
export function setAdapter(adapter: DatabaseAdapter): DatabaseAdapter {
|
|
158
|
+
activeAdapter = wrapWithCache(adapter);
|
|
159
|
+
return activeAdapter;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Clear the request-scoped query cache on every live connection at the start of
|
|
164
|
+
* each HTTP request, so request-scoped caching never serves rows across
|
|
165
|
+
* requests. Persistent-mode connections (TINA4_DB_CACHE=true) are untouched.
|
|
166
|
+
*
|
|
167
|
+
* The request dispatcher calls this. Mirrors Python's
|
|
168
|
+
* `Database.reset_request_caches()`.
|
|
169
|
+
*/
|
|
170
|
+
export function resetRequestCaches(): void {
|
|
171
|
+
CachedDatabaseAdapter.resetRequestCaches();
|
|
133
172
|
}
|
|
134
173
|
|
|
135
174
|
/**
|
|
@@ -161,7 +200,9 @@ export function bindDatabase(adapter: DatabaseAdapter, name?: string): void {
|
|
|
161
200
|
if (name === undefined) {
|
|
162
201
|
setAdapter(adapter);
|
|
163
202
|
} else {
|
|
164
|
-
|
|
203
|
+
// Named connections are cached too, so ORM models pointed at them with
|
|
204
|
+
// `static _db = name` get the same request-scoped/persistent caching.
|
|
205
|
+
namedAdapters.set(name, wrapWithCache(adapter));
|
|
165
206
|
}
|
|
166
207
|
}
|
|
167
208
|
|
|
@@ -177,7 +218,7 @@ export function getAdapter(): DatabaseAdapter {
|
|
|
177
218
|
* Models reference it via `static _db = 'name'`.
|
|
178
219
|
*/
|
|
179
220
|
export function setNamedAdapter(name: string, adapter: DatabaseAdapter): void {
|
|
180
|
-
namedAdapters.set(name, adapter);
|
|
221
|
+
namedAdapters.set(name, wrapWithCache(adapter));
|
|
181
222
|
}
|
|
182
223
|
|
|
183
224
|
/**
|
|
@@ -463,29 +504,35 @@ export class Database {
|
|
|
463
504
|
const parsed = parseDatabaseUrl(url, username, password);
|
|
464
505
|
|
|
465
506
|
if (pool > 0) {
|
|
466
|
-
// Pooled mode — create all adapters eagerly
|
|
507
|
+
// Pooled mode — create all adapters eagerly, then wrap each with the
|
|
508
|
+
// query cache backed by ONE shared store so a write on any pooled
|
|
509
|
+
// connection invalidates reads cached by all of them.
|
|
510
|
+
const sharedCache = new QueryCache({ maxSize: 10000 });
|
|
467
511
|
const adapters: DatabaseAdapter[] = [];
|
|
468
512
|
for (let i = 0; i < pool; i++) {
|
|
469
|
-
|
|
513
|
+
const raw = await createAdapterFromUrl(url, username, password);
|
|
514
|
+
adapters.push(wrapWithCache(raw, { sharedCache }));
|
|
470
515
|
}
|
|
471
516
|
|
|
472
|
-
// Set the first adapter as the global default
|
|
473
|
-
|
|
517
|
+
// Set the first adapter as the global default (already cache-wrapped).
|
|
518
|
+
activeAdapter = adapters[0];
|
|
474
519
|
|
|
475
520
|
const db = new Database(adapters[0]);
|
|
476
521
|
db._poolSize = pool;
|
|
477
522
|
db.pool = adapters;
|
|
478
523
|
db.poolIndex = 0;
|
|
479
524
|
db.adapter = null; // Don't use single-adapter path
|
|
480
|
-
db.adapterFactory = () => createAdapterFromUrl(url, username, password);
|
|
525
|
+
db.adapterFactory = async () => wrapWithCache(await createAdapterFromUrl(url, username, password), { sharedCache });
|
|
481
526
|
db.dbType = parsed.type;
|
|
482
527
|
return db;
|
|
483
528
|
}
|
|
484
529
|
|
|
485
|
-
// Single-connection mode —
|
|
530
|
+
// Single-connection mode — wrap once and share the SAME wrapped adapter
|
|
531
|
+
// between getAdapter() (ORM reads) and the Database wrapper (db.fetch()),
|
|
532
|
+
// so both hit one cache + one set of counters.
|
|
486
533
|
const adapter = await createAdapterFromUrl(url, username, password);
|
|
487
|
-
setAdapter(adapter);
|
|
488
|
-
const db = new Database(
|
|
534
|
+
const wrapped = setAdapter(adapter);
|
|
535
|
+
const db = new Database(wrapped);
|
|
489
536
|
db.dbType = parsed.type;
|
|
490
537
|
return db;
|
|
491
538
|
}
|
|
@@ -780,21 +827,42 @@ export class Database {
|
|
|
780
827
|
return this.lastError ?? null;
|
|
781
828
|
}
|
|
782
829
|
|
|
783
|
-
/**
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
830
|
+
/**
|
|
831
|
+
* Return query cache statistics from the REAL cache backing this connection.
|
|
832
|
+
*
|
|
833
|
+
* The bound adapter is a CachedDatabaseAdapter (caching is ON by default —
|
|
834
|
+
* request-scoped — and additionally persistent when TINA4_DB_CACHE=true), so
|
|
835
|
+
* we read the live counters + size + mode from it. Mirrors Python's
|
|
836
|
+
* `Database.cache_stats()`: `{ enabled, mode, hits, misses, size, ttl }`.
|
|
837
|
+
*/
|
|
838
|
+
cacheStats(): { enabled: boolean; mode: "persistent" | "request" | "off"; hits: number; misses: number; size: number; ttl: number } {
|
|
839
|
+
const adapter = this.getNextAdapter();
|
|
840
|
+
if (adapter instanceof CachedDatabaseAdapter) {
|
|
841
|
+
return adapter.cacheStats();
|
|
842
|
+
}
|
|
843
|
+
// Adapter isn't cache-wrapped (shouldn't happen via initDatabase/create) —
|
|
844
|
+
// report a disabled cache truthfully rather than lying about size.
|
|
845
|
+
return { enabled: false, mode: "off", hits: 0, misses: 0, size: 0, ttl: 0 };
|
|
790
846
|
}
|
|
791
847
|
|
|
792
|
-
/**
|
|
848
|
+
/** Flush the query cache and reset counters (mirrors Python `cache_clear()`). */
|
|
793
849
|
cacheClear(): void {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
850
|
+
const adapter = this.getNextAdapter();
|
|
851
|
+
if (adapter instanceof CachedDatabaseAdapter) {
|
|
852
|
+
adapter.cacheClear();
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Clear the request-scoped cache at the START of an HTTP request on this
|
|
858
|
+
* connection (no-op in persistent mode). Mirrors Python's
|
|
859
|
+
* `Database.cache_new_request()`.
|
|
860
|
+
*/
|
|
861
|
+
cacheNewRequest(): void {
|
|
862
|
+
const adapter = this.getNextAdapter();
|
|
863
|
+
if (adapter instanceof CachedDatabaseAdapter) {
|
|
864
|
+
adapter.cacheNewRequest();
|
|
865
|
+
}
|
|
798
866
|
}
|
|
799
867
|
|
|
800
868
|
/** Get the last auto-increment id. */
|
|
@@ -1088,6 +1156,18 @@ export namespace Database {
|
|
|
1088
1156
|
password: opts.password,
|
|
1089
1157
|
});
|
|
1090
1158
|
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Clear the request-scoped query cache on every live connection.
|
|
1162
|
+
*
|
|
1163
|
+
* Static convenience mirroring Python's `Database.reset_request_caches()`
|
|
1164
|
+
* classmethod. The request dispatcher calls this at the start of each HTTP
|
|
1165
|
+
* request so request-scoped caching never serves rows across requests.
|
|
1166
|
+
* Persistent-mode connections (TINA4_DB_CACHE=true) are left alone.
|
|
1167
|
+
*/
|
|
1168
|
+
export function resetRequestCaches(): void {
|
|
1169
|
+
CachedDatabaseAdapter.resetRequestCaches();
|
|
1170
|
+
}
|
|
1091
1171
|
}
|
|
1092
1172
|
|
|
1093
1173
|
export async function initDatabase(config?: DatabaseConfig): Promise<Database> {
|
|
@@ -1106,8 +1186,7 @@ export async function initDatabase(config?: DatabaseConfig): Promise<Database> {
|
|
|
1106
1186
|
return Database.create(url, resolvedUser, resolvedPassword, pool);
|
|
1107
1187
|
}
|
|
1108
1188
|
const adapter = await createAdapterFromUrl(url, resolvedUser, resolvedPassword);
|
|
1109
|
-
setAdapter(adapter);
|
|
1110
|
-
return new Database(adapter);
|
|
1189
|
+
return new Database(setAdapter(adapter));
|
|
1111
1190
|
}
|
|
1112
1191
|
|
|
1113
1192
|
// Legacy config path — normalize "sqlserver" to "mssql"
|
|
@@ -1132,8 +1211,7 @@ export async function initDatabase(config?: DatabaseConfig): Promise<Database> {
|
|
|
1132
1211
|
case "sqlite": {
|
|
1133
1212
|
const { SQLiteAdapter } = await import("./adapters/sqlite.js");
|
|
1134
1213
|
const adapter = new SQLiteAdapter(config?.path ?? "./data/tina4.db");
|
|
1135
|
-
setAdapter(adapter);
|
|
1136
|
-
return new Database(adapter);
|
|
1214
|
+
return new Database(setAdapter(adapter));
|
|
1137
1215
|
}
|
|
1138
1216
|
case "postgres": {
|
|
1139
1217
|
const { PostgresAdapter } = await import("./adapters/postgres.js");
|
|
@@ -1145,8 +1223,7 @@ export async function initDatabase(config?: DatabaseConfig): Promise<Database> {
|
|
|
1145
1223
|
database: config?.database,
|
|
1146
1224
|
});
|
|
1147
1225
|
await adapter.connect();
|
|
1148
|
-
setAdapter(adapter);
|
|
1149
|
-
return new Database(adapter);
|
|
1226
|
+
return new Database(setAdapter(adapter));
|
|
1150
1227
|
}
|
|
1151
1228
|
case "mysql": {
|
|
1152
1229
|
const { MysqlAdapter } = await import("./adapters/mysql.js");
|
|
@@ -1158,8 +1235,7 @@ export async function initDatabase(config?: DatabaseConfig): Promise<Database> {
|
|
|
1158
1235
|
database: config?.database,
|
|
1159
1236
|
});
|
|
1160
1237
|
await adapter.connect();
|
|
1161
|
-
setAdapter(adapter);
|
|
1162
|
-
return new Database(adapter);
|
|
1238
|
+
return new Database(setAdapter(adapter));
|
|
1163
1239
|
}
|
|
1164
1240
|
case "mssql": {
|
|
1165
1241
|
const { MssqlAdapter } = await import("./adapters/mssql.js");
|
|
@@ -1171,8 +1247,7 @@ export async function initDatabase(config?: DatabaseConfig): Promise<Database> {
|
|
|
1171
1247
|
database: config?.database,
|
|
1172
1248
|
});
|
|
1173
1249
|
await adapter.connect();
|
|
1174
|
-
setAdapter(adapter);
|
|
1175
|
-
return new Database(adapter);
|
|
1250
|
+
return new Database(setAdapter(adapter));
|
|
1176
1251
|
}
|
|
1177
1252
|
case "firebird": {
|
|
1178
1253
|
const { FirebirdAdapter } = await import("./adapters/firebird.js");
|
|
@@ -1184,8 +1259,7 @@ export async function initDatabase(config?: DatabaseConfig): Promise<Database> {
|
|
|
1184
1259
|
database: config?.database,
|
|
1185
1260
|
});
|
|
1186
1261
|
await adapter.connect();
|
|
1187
|
-
setAdapter(adapter);
|
|
1188
|
-
return new Database(adapter);
|
|
1262
|
+
return new Database(setAdapter(adapter));
|
|
1189
1263
|
}
|
|
1190
1264
|
case "mongodb": {
|
|
1191
1265
|
const { MongodbAdapter } = await import("./adapters/mongodb.js");
|
|
@@ -1198,16 +1272,14 @@ export async function initDatabase(config?: DatabaseConfig): Promise<Database> {
|
|
|
1198
1272
|
const connectionString = `mongodb://${creds}${host}:${port}/${database}`;
|
|
1199
1273
|
const adapter = new MongodbAdapter(connectionString);
|
|
1200
1274
|
await adapter.connect();
|
|
1201
|
-
setAdapter(adapter);
|
|
1202
|
-
return new Database(adapter);
|
|
1275
|
+
return new Database(setAdapter(adapter));
|
|
1203
1276
|
}
|
|
1204
1277
|
case "odbc": {
|
|
1205
1278
|
const { OdbcAdapter } = await import("./adapters/odbc.js");
|
|
1206
1279
|
const connStr = config?.connectionString ?? config?.url?.replace(/^odbc:\/\/\//, "") ?? "";
|
|
1207
1280
|
const adapter = new OdbcAdapter({ connectionString: connStr });
|
|
1208
1281
|
await adapter.connect();
|
|
1209
|
-
setAdapter(adapter);
|
|
1210
|
-
return new Database(adapter);
|
|
1282
|
+
return new Database(setAdapter(adapter));
|
|
1211
1283
|
}
|
|
1212
1284
|
default:
|
|
1213
1285
|
throw new Error(`Unknown database type: ${type}`);
|
|
@@ -14,7 +14,7 @@ export { FetchResult } from "./types.js";
|
|
|
14
14
|
|
|
15
15
|
export { DatabaseResult } from "./databaseResult.js";
|
|
16
16
|
export type { ColumnInfoResult } from "./databaseResult.js";
|
|
17
|
-
export { Database, initDatabase, getAdapter, setAdapter, bindDatabase, createAdapterFromUrl, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter, resolveDbPool, stripTrailingSemicolons } from "./database.js";
|
|
17
|
+
export { Database, initDatabase, getAdapter, setAdapter, bindDatabase, createAdapterFromUrl, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter, resolveDbPool, stripTrailingSemicolons, wrapWithCache, resetRequestCaches } from "./database.js";
|
|
18
18
|
export {
|
|
19
19
|
adapterFetch, adapterQuery, adapterFetchOne, adapterExecute,
|
|
20
20
|
adapterStartTransaction, adapterCommit, adapterRollback,
|
|
@@ -49,6 +49,7 @@ export { BaseModel, snakeToCamel, camelToSnake } from "./baseModel.js";
|
|
|
49
49
|
export { QueryBuilder } from "./queryBuilder.js";
|
|
50
50
|
export { SQLTranslator, QueryCache } from "./sqlTranslation.js";
|
|
51
51
|
export { CachedDatabaseAdapter } from "./cachedDatabase.js";
|
|
52
|
+
export type { CachedAdapterOptions } from "./cachedDatabase.js";
|
|
52
53
|
export { FakeData } from "./fakeData.js";
|
|
53
54
|
export { seedTable, seedOrm } from "./seeder.js";
|
|
54
55
|
|