tina4-nodejs 3.13.81 → 3.13.83

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.
@@ -0,0 +1,104 @@
1
+ /**
2
+ * One MQTT 3.1.1 application message as delivered by the broker.
3
+ *
4
+ * Shaped like the Queue's job (its unit of work): it carries the payload plus
5
+ * the delivery metadata a consumer needs, and it knows how to acknowledge itself
6
+ * back to the client it came from. Mirrors tina4_python.mqtt.MqttMessage,
7
+ * Tina4::MqttMessage (Ruby), and Tina4\MqttMessage (PHP).
8
+ *
9
+ * The two flags matter for correctness, not decoration:
10
+ *
11
+ * retained — the broker replayed the topic's last known value to us because
12
+ * we subscribed AFTER it was published. It is current state, not a
13
+ * fresh event.
14
+ * duplicate — the DUP flag. The broker is REDELIVERING a QoS 1 message it never
15
+ * saw acknowledged. QoS 1 is at-least-once, so a duplicate is
16
+ * guaranteed eventually; a consumer that treats a DUP delivery as a
17
+ * new sample double-counts energy or mileage. Key the ingest on
18
+ * (deviceId, deviceTimestamp) and it is harmless.
19
+ *
20
+ * The payload is a Buffer of the raw bytes; text() decodes it for JSON/string
21
+ * payloads.
22
+ */
23
+
24
+ /** The slice of an Mqtt client an MqttMessage needs to acknowledge itself. */
25
+ export interface MqttAcknowledger {
26
+ acknowledge(packetId: number): Promise<boolean>;
27
+ }
28
+
29
+ export class MqttMessage {
30
+ private acknowledgedFlag = false;
31
+
32
+ constructor(
33
+ public readonly topic: string,
34
+ public readonly payload: Buffer,
35
+ public readonly qos: number = 0,
36
+ public readonly retained: boolean = false,
37
+ public readonly duplicate: boolean = false,
38
+ public readonly packetId: number | null = null,
39
+ private readonly client: MqttAcknowledger | null = null,
40
+ ) {}
41
+
42
+ /** True when the broker replayed this as the topic's retained (last known) value. */
43
+ isRetained(): boolean {
44
+ return this.retained;
45
+ }
46
+
47
+ /**
48
+ * True when the broker set the DUP flag — a REDELIVERY of a QoS 1 message we
49
+ * never acknowledged, not a new sample.
50
+ */
51
+ isDuplicate(): boolean {
52
+ return this.duplicate;
53
+ }
54
+
55
+ /**
56
+ * PUBACK a QoS 1 delivery so the broker stops redelivering it.
57
+ *
58
+ * A QoS 0 message needs no acknowledgement, and a second call is a no-op, so
59
+ * this is always safe to call once processing succeeded. Returns true only
60
+ * when a PUBACK was actually sent.
61
+ */
62
+ async acknowledge(): Promise<boolean> {
63
+ if (this.acknowledgedFlag || this.qos === 0 || this.packetId === null || this.client === null) {
64
+ return false;
65
+ }
66
+ await this.client.acknowledge(this.packetId);
67
+ this.acknowledgedFlag = true;
68
+ return true;
69
+ }
70
+
71
+ /** True once this message has been acknowledged back to the broker. */
72
+ isAcknowledged(): boolean {
73
+ return this.acknowledgedFlag;
74
+ }
75
+
76
+ /** The payload decoded as text (for JSON / string payloads). */
77
+ text(encoding: BufferEncoding = "utf-8"): string {
78
+ return this.payload.toString(encoding);
79
+ }
80
+
81
+ /** The message as a plain object. */
82
+ toObject(): {
83
+ topic: string;
84
+ payload: Buffer;
85
+ qos: number;
86
+ retained: boolean;
87
+ duplicate: boolean;
88
+ packetId: number | null;
89
+ } {
90
+ return {
91
+ topic: this.topic,
92
+ payload: this.payload,
93
+ qos: this.qos,
94
+ retained: this.retained,
95
+ duplicate: this.duplicate,
96
+ packetId: this.packetId,
97
+ };
98
+ }
99
+
100
+ /** String form is the payload text (parity with Python __str__ / Ruby to_s). */
101
+ toString(): string {
102
+ return this.text();
103
+ }
104
+ }
@@ -168,9 +168,56 @@ function resolveImports(content: string, paths: string[], imported: Set<string>)
168
168
 
169
169
  // ── Variables ────────────────────────────────────────────────────
170
170
 
171
+ /**
172
+ * Flags that may trail a variable declaration's value. `!default` means "assign
173
+ * only if this variable is not already set" — the flag that makes a variable
174
+ * themeable. `!global` is the scope flag. Both are compiler directives: they are
175
+ * consumed at the declaration and must never reach the CSS, because
176
+ * `padding: 1.5rem !default` is invalid CSS and browsers drop the whole
177
+ * declaration. Sass flag names are case-SENSITIVE (`!DEFAULT` is an error in
178
+ * Dart Sass), so the match is deliberately case-sensitive.
179
+ */
180
+ const VARIABLE_FLAG = /\s*!(default|global)\s*$/;
181
+
182
+ /**
183
+ * Split trailing `!default` / `!global` flags off a variable declaration value.
184
+ * Returns `[valueWithoutFlags, declaresDefault]`.
185
+ *
186
+ * Only ever called on the value of a `$name: value;` declaration, so a literal
187
+ * `!default` anywhere else — inside a quoted string (`content: "x !default y"`)
188
+ * or a function argument — is left untouched, exactly as Dart Sass leaves it. A
189
+ * blanket strip would corrupt real string content, and would silently turn
190
+ * `rgba(#000 !default, 0.1)` (a syntax error in Dart Sass) into valid-looking
191
+ * CSS that Sass would never emit.
192
+ */
193
+ function stripVariableFlags(value: string): [string, boolean] {
194
+ let declaresDefault = false;
195
+ for (;;) {
196
+ const match = VARIABLE_FLAG.exec(value);
197
+ if (match === null) return [value.trim(), declaresDefault];
198
+ if (match[1] === "default") declaresDefault = true;
199
+ value = value.slice(0, match.index);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Extract `$variable: value;` declarations, honouring the `!default` flag.
205
+ *
206
+ * `$x: value !default;` assigns only when `$x` is not already set. That is what
207
+ * makes a variable themeable — a user who writes `$primary: red;` BEFORE
208
+ * importing a partial that declares `$primary: blue !default;` keeps red.
209
+ * Declarations are visited in source order, so "already set" means "set by an
210
+ * earlier declaration or by a preset variable". A value of `null` counts as
211
+ * unset, as in Sass.
212
+ */
171
213
  function extractVariables(scss: string, variables: Record<string, string>): string {
172
214
  return scss.replace(/\$([a-zA-Z_][\w-]*)\s*:\s*([^;]+);/g, (_m, name: string, value: string) => {
173
- let resolved = value.trim();
215
+ const [stripped, declaresDefault] = stripVariableFlags(value.trim());
216
+ // !default must not overwrite a value that is already set.
217
+ if (declaresDefault && (variables[name] ?? "null") !== "null") {
218
+ return "";
219
+ }
220
+ let resolved = stripped;
174
221
  // Resolve variable references within the value
175
222
  for (const [vName, vVal] of Object.entries(variables)) {
176
223
  resolved = resolved.replaceAll(`$${vName}`, vVal);
@@ -34,6 +34,73 @@ const BUILTIN_ERROR_TEMPLATES_DIR = resolve(__dirname, "..", "templates");
34
34
  /** Built-in public directory for framework-bundled static assets. */
35
35
  const BUILTIN_PUBLIC_DIR = resolve(__dirname, "..", "public");
36
36
 
37
+ /**
38
+ * Whether the framework's bundled Swagger UI assets (public/swagger/*) may be
39
+ * served.
40
+ *
41
+ * Static files are resolved BEFORE routes, so the shipped
42
+ * public/swagger/index.html answered `GET /swagger` even when
43
+ * `swaggerEnabled()` was false -- serving the Swagger UI in production and
44
+ * bypassing the documented TINA4_SWAGGER_ENABLED / TINA4_DEBUG gate entirely.
45
+ * The symptom was a 200 on /swagger with a 404 on /swagger/openapi.json (the
46
+ * gated route never registered, the static file still won).
47
+ *
48
+ * Set from `swaggerEnabled()` at boot. Stays false when swagger is disabled OR
49
+ * when the swagger module fails to load -- fail closed, never expose.
50
+ */
51
+ let swaggerAssetsEnabled = false;
52
+
53
+ /** Bundled Swagger UI asset paths that must honour the swagger gate. */
54
+ function isSwaggerAssetPath(pathname: string): boolean {
55
+ return pathname === "/swagger" || pathname.startsWith("/swagger/");
56
+ }
57
+
58
+ /**
59
+ * Build the startup banner's optional surface lines (issue #99).
60
+ *
61
+ * Only advertise a surface that is actually REACHABLE. In production, or with
62
+ * TINA4_DEBUG off, /swagger and /__dev return 404 -- printing them anyway both
63
+ * misleads an operator into believing a dev surface is exposed and sends a
64
+ * developer to a dead link.
65
+ *
66
+ * Kept as a pure function of (port, two booleans) so the contract is unit
67
+ * testable without booting a server and grepping stdout. Parity: Python
68
+ * banner_surface_lines, PHP App::bannerSurfaceLines, Ruby
69
+ * Tina4.banner_surface_lines.
70
+ *
71
+ * @returns [swaggerLine, dashboardLine] -- each empty, or a newline plus the
72
+ * banner row, ready to interpolate.
73
+ */
74
+ export function bannerSurfaceLines(
75
+ port: number,
76
+ opts: { swaggerEnabled: boolean; devAdminEnabled: boolean },
77
+ ): [string, string] {
78
+ return [
79
+ opts.swaggerEnabled ? `\n Swagger: http://localhost:${port}/swagger` : "",
80
+ opts.devAdminEnabled ? `\n Dashboard: http://localhost:${port}/__dev` : "",
81
+ ];
82
+ }
83
+
84
+ /**
85
+ * Whether the startup banner should ADVERTISE /swagger (issue #99).
86
+ *
87
+ * Mirrors `swaggerEnabled()` in packages/swagger/src/ui.ts: an explicit
88
+ * TINA4_SWAGGER_ENABLED wins, otherwise fall back to TINA4_DEBUG.
89
+ *
90
+ * Read from env here rather than importing the swagger package, because the
91
+ * CLUSTER PRIMARY prints its banner before any optional module is loaded (and a
92
+ * dynamic import for one banner line is not worth the boot cost). Keep this in
93
+ * sync with ui.ts -- it is the same two-line contract.
94
+ */
95
+ function swaggerAdvertised(): boolean {
96
+ const TRUTHY = ["true", "1", "yes", "on"];
97
+ const raw = (process.env.TINA4_SWAGGER_ENABLED ?? "").trim().toLowerCase();
98
+ if (raw === "") {
99
+ return TRUTHY.includes((process.env.TINA4_DEBUG ?? "").trim().toLowerCase());
100
+ }
101
+ return TRUTHY.includes(raw);
102
+ }
103
+
37
104
  /**
38
105
  * Apply pending DB migrations on startup — NON-BREAKING.
39
106
  *
@@ -787,6 +854,15 @@ export async function startServer(config?: Tina4Config): Promise<{
787
854
  const logLevel = (process.env.TINA4_LOG_LEVEL ?? "DEBUG").toUpperCase();
788
855
 
789
856
  if (!isBannerSuppressed()) {
857
+ // Only advertise a surface that is actually reachable (issue #99).
858
+ // Cluster mode is the production path: debug is OFF, so /__dev always
859
+ // 404s here and is never advertised; /swagger only when explicitly on.
860
+ // Cluster mode is the production path: debug is OFF, so /__dev never
861
+ // advertises here.
862
+ const [swaggerLine] = bannerSurfaceLines(port, {
863
+ swaggerEnabled: swaggerAdvertised(),
864
+ devAdminEnabled: false,
865
+ });
790
866
  console.log(`${color}
791
867
  ______ _ __ __
792
868
  /_ __/(_)___ ____ _/ // /
@@ -796,9 +872,7 @@ export async function startServer(config?: Tina4Config): Promise<{
796
872
  ${reset}
797
873
  Tina4 Node.js v${TINA4_VERSION} — The Intelligent Native Application 4ramework
798
874
 
799
- Server: http://${displayHost}:${port} (cluster, ${numCPUs} workers)
800
- Swagger: http://localhost:${port}/swagger
801
- Dashboard: http://localhost:${port}/__dev
875
+ Server: http://${displayHost}:${port} (cluster, ${numCPUs} workers)${swaggerLine}
802
876
  Debug: OFF (Log level: ${logLevel})
803
877
  `);
804
878
  }
@@ -1000,7 +1074,10 @@ ${reset}
1000
1074
  // when disabled.
1001
1075
  try {
1002
1076
  const swagger = await import("../../swagger/src/index.js");
1003
- if (!swagger.swaggerEnabled()) {
1077
+ // Single source of truth for BOTH the gated routes and the bundled
1078
+ // public/swagger assets (which static serving would otherwise expose).
1079
+ swaggerAssetsEnabled = swagger.swaggerEnabled();
1080
+ if (!swaggerAssetsEnabled) {
1004
1081
  // Skip the rest of the swagger block when disabled.
1005
1082
  throw new Error("__swagger_disabled__");
1006
1083
  }
@@ -1273,8 +1350,14 @@ ${reset}
1273
1350
  if (existsSync(srcPublicDir) && tryServeStatic(srcPublicDir, req, res)) {
1274
1351
  return;
1275
1352
  }
1276
- if (tryServeStatic(BUILTIN_PUBLIC_DIR, req, res)) {
1277
- return;
1353
+ // Framework-bundled assets. The Swagger UI lives here (public/swagger/),
1354
+ // and static files resolve BEFORE routes -- so this path MUST honour the
1355
+ // swagger gate or /swagger is served in production regardless of
1356
+ // TINA4_SWAGGER_ENABLED / TINA4_DEBUG.
1357
+ if (swaggerAssetsEnabled || !isSwaggerAssetPath(pathname)) {
1358
+ if (tryServeStatic(BUILTIN_PUBLIC_DIR, req, res)) {
1359
+ return;
1360
+ }
1278
1361
  }
1279
1362
 
1280
1363
  // Match route
@@ -1602,6 +1685,14 @@ ${reset}
1602
1685
  : "";
1603
1686
 
1604
1687
  if (!isBannerSuppressed()) {
1688
+ // Only advertise a surface that is actually reachable (issue #99). With
1689
+ // debug off / in production these endpoints 404, and printing a dead URL
1690
+ // both misleads an operator into believing a dev surface is exposed and
1691
+ // sends a developer to a 404.
1692
+ const [swaggerLine, dashboardLine] = bannerSurfaceLines(port, {
1693
+ swaggerEnabled: swaggerAdvertised(),
1694
+ devAdminEnabled: isDebug,
1695
+ });
1605
1696
  console.log(`${color}
1606
1697
  ______ _ __ __
1607
1698
  /_ __/(_)___ ____ _/ // /
@@ -1611,9 +1702,7 @@ ${reset}
1611
1702
  ${reset}
1612
1703
  Tina4 Node.js v${TINA4_VERSION} — The Intelligent Native Application 4ramework
1613
1704
 
1614
- Server: http://${displayHost}:${port} (${serverMode})
1615
- Swagger: http://localhost:${port}/swagger
1616
- Dashboard: http://localhost:${port}/__dev
1705
+ Server: http://${displayHost}:${port} (${serverMode})${swaggerLine}${dashboardLine}
1617
1706
  Debug: ${isDebug ? "ON" : "OFF"} (Log level: ${logLevel})${dualPortLines}
1618
1707
  `);
1619
1708
  }
@@ -18,7 +18,8 @@ import { IncomingMessage, ServerResponse } from "node:http";
18
18
  import { Socket } from "node:net";
19
19
  import { createRequest } from "./request.js";
20
20
  import { createResponse } from "./response.js";
21
- import { defaultRouter, type Router } from "./router.js";
21
+ import { defaultRouter, Router, runRouteMiddlewares } from "./router.js";
22
+ import { MiddlewareRunner } from "./middleware.js";
22
23
  import { enforceRouteAuth } from "./authGate.js";
23
24
 
24
25
  export class TestResponse {
@@ -160,14 +161,73 @@ export class TestClient {
160
161
  const cleanPath = path.includes("?") ? path.split("?")[0] : path;
161
162
 
162
163
  // Match route
163
- const match = this.router.match(method.toUpperCase(), cleanPath);
164
+ const httpMethod = method.toUpperCase();
165
+ const match = this.router.match(httpMethod, cleanPath);
164
166
  if (!match) {
165
- return new TestResponse(404, { "content-type": "application/json" }, '{"error":"Not found"}');
167
+ // D6: dispatch through the REAL front-controller behaviour on a miss —
168
+ // never fabricate a body. This mirrors the live server tail
169
+ // (server.ts dispatch): RFC 9110 conformance first (a path registered
170
+ // under another method answers OPTIONS with 204 + Allow and any other
171
+ // method with 405 + Allow), then the framework's real 404. The old
172
+ // TestClient short-circuited to a hand-invented {"error":"Not found"}
173
+ // that the live server never sends, so a green test proved nothing about
174
+ // production (the same silent-success class as Python's pre-fix client).
175
+ const allowed = this.router.methodsAllowedForPath(cleanPath);
176
+ if (allowed.length > 0) {
177
+ const allowHeader = allowed.join(", ");
178
+ if (httpMethod === "OPTIONS") {
179
+ return new TestResponse(204, { allow: allowHeader, "content-length": "0" }, "");
180
+ }
181
+ const body405 = JSON.stringify({
182
+ error: "Method Not Allowed",
183
+ path: cleanPath,
184
+ method: httpMethod,
185
+ allow: allowed,
186
+ statusCode: 405,
187
+ });
188
+ return new TestResponse(405, { allow: allowHeader, "content-type": "application/json" }, body405);
189
+ }
190
+ // The framework's real 404. The live server renders an HTML error page
191
+ // when a project templatesDir/frondEngine exists and otherwise emits this
192
+ // exact JSON; a server-less TestClient has no project dir, so it produces
193
+ // the JSON fallback the live front controller itself falls back to.
194
+ // tina4: HTML-error-page + filesystem static serving are the only live
195
+ // front-controller steps a server-less client can't reproduce.
196
+ const body404 = JSON.stringify({
197
+ error: "Not Found",
198
+ statusCode: 404,
199
+ message: `No route found for ${httpMethod} ${cleanPath}`,
200
+ });
201
+ return new TestResponse(404, { "content-type": "application/json" }, body404);
166
202
  }
167
203
 
168
204
  // Inject route params
169
205
  req.params = match.params;
170
206
 
207
+ // Global class-based middleware (Router.use / MiddlewareRunner.use), run in
208
+ // the SAME order the live dispatcher uses (server.ts): beforeX hooks before
209
+ // the handler, afterX hooks after — even on a before-short-circuit. Empty
210
+ // when a test registers none, so this is additive/non-breaking.
211
+ const globalMiddleware = [
212
+ ...new Set([...Router.getClassMiddlewares(), ...MiddlewareRunner.getGlobal()]),
213
+ ];
214
+ if (globalMiddleware.length > 0) {
215
+ const [, , proceed] = await MiddlewareRunner.runBefore(globalMiddleware, req, res);
216
+ if (!proceed || res.raw.writableEnded) {
217
+ await MiddlewareRunner.runAfter(globalMiddleware, req, res);
218
+ if (!res.raw.writableEnded) res.raw.end();
219
+ return this._collect(rawRes, chunks, socket);
220
+ }
221
+ }
222
+
223
+ // Per-route middleware, same as the live dispatcher.
224
+ if (match.middlewares && match.middlewares.length > 0) {
225
+ const proceed = await runRouteMiddlewares(match.middlewares, req, res);
226
+ if (!proceed || res.raw.writableEnded) {
227
+ return this._collect(rawRes, chunks, socket);
228
+ }
229
+ }
230
+
171
231
  // Route through the REAL auth gate (parity with the live server). A write to
172
232
  // an auth-required route (secure-by-default POST/PUT/PATCH/DELETE, or any
173
233
  // .secure() route) with no valid token / formToken / session token 401s here
@@ -183,9 +243,17 @@ export class TestClient {
183
243
  // Execute handler (only if auth passed)
184
244
  if (!rejected) {
185
245
  await match.handler(req, res);
246
+ // Global afterX hooks (logging / post-processing), mirroring the live tail.
247
+ if (globalMiddleware.length > 0) {
248
+ await MiddlewareRunner.runAfter(globalMiddleware, req, res);
249
+ }
186
250
  }
187
251
 
188
- // Collect response
252
+ return this._collect(rawRes, chunks, socket);
253
+ }
254
+
255
+ /** Gather the captured status/headers/body into a TestResponse and free the socket. */
256
+ private _collect(rawRes: ServerResponse, chunks: Buffer[], socket: Socket): TestResponse {
189
257
  const responseBody = Buffer.concat(chunks).toString();
190
258
  const responseHeaders: Record<string, string> = {};
191
259
  for (const [name, value] of Object.entries(rawRes.getHeaders())) {
@@ -193,10 +261,7 @@ export class TestClient {
193
261
  responseHeaders[name] = Array.isArray(value) ? value.join(", ") : String(value);
194
262
  }
195
263
  }
196
-
197
- // Clean up the socket
198
264
  socket.destroy();
199
-
200
265
  return new TestResponse(rawRes.statusCode, responseHeaders, responseBody);
201
266
  }
202
267
  }
@@ -5,7 +5,7 @@
5
5
  * URL format: firebird://user:pass@host:port/path/to/database.fdb
6
6
  */
7
7
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
8
- import { SQLTranslator } from "../sqlTranslation.js";
8
+ import { SQLTranslator } from "../sqlTranslator.js";
9
9
  import { createRequire } from "node:module";
10
10
 
11
11
  let firebird: any = null;
@@ -5,7 +5,7 @@
5
5
  * URL format: mssql://user:pass@host:port/database
6
6
  */
7
7
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
8
- import { SQLTranslator } from "../sqlTranslation.js";
8
+ import { SQLTranslator } from "../sqlTranslator.js";
9
9
  import { createRequire } from "node:module";
10
10
 
11
11
  let tedious: any = null;
@@ -5,7 +5,7 @@
5
5
  * URL format: mysql://user:pass@host:port/database
6
6
  */
7
7
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
8
- import { SQLTranslator } from "../sqlTranslation.js";
8
+ import { SQLTranslator } from "../sqlTranslator.js";
9
9
  import { createRequire } from "node:module";
10
10
 
11
11
  let mysql2: any = null;
@@ -5,7 +5,7 @@
5
5
  * URL format: postgresql://user:pass@host:port/database
6
6
  */
7
7
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
8
- import { SQLTranslator } from "../sqlTranslation.js";
8
+ import { SQLTranslator } from "../sqlTranslator.js";
9
9
 
10
10
  import { createRequire } from "node:module";
11
11
 
@@ -2,7 +2,7 @@ import { DatabaseSync } from "node:sqlite";
2
2
  import { mkdirSync } from "node:fs";
3
3
  import { dirname, isAbsolute, join, resolve } from "node:path";
4
4
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
5
- import { SQLTranslator } from "../sqlTranslation.js";
5
+ import { SQLTranslator } from "../sqlTranslator.js";
6
6
 
7
7
  /** A safe-to-interpolate SQL identifier (no quoting/escaping needed). */
8
8
  function isIdentifier(str: string): boolean {
@@ -7,7 +7,7 @@ import {
7
7
  import { validate as validateFields } from "./validation.js";
8
8
  import { QueryBuilder } from "./queryBuilder.js";
9
9
  import { SQLiteAdapter } from "./adapters/sqlite.js";
10
- import { QueryCache } from "./sqlTranslation.js";
10
+ import { QueryCache } from "./sqlTranslator.js";
11
11
  import { Log } from "../../core/src/index.js";
12
12
  import type { DatabaseAdapter, FieldDefinition, RelationshipDefinition } from "./types.js";
13
13
 
@@ -32,7 +32,7 @@
32
32
  * db.cacheStats(); // { enabled, mode, hits, misses, size, ttl }
33
33
  */
34
34
 
35
- import { QueryCache } from "./sqlTranslation.js";
35
+ import { QueryCache } from "./sqlTranslator.js";
36
36
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "./types.js";
37
37
  import type { CacheBackend } from "../../core/src/index.js";
38
38
 
@@ -2,7 +2,7 @@ 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
4
  import { CachedDatabaseAdapter, type CachedAdapterOptions } from "./cachedDatabase.js";
5
- import { QueryCache } from "./sqlTranslation.js";
5
+ import { QueryCache } from "./sqlTranslator.js";
6
6
 
7
7
  /**
8
8
  * v3.13.12 — strip trailing `;` and whitespace from user-supplied SQL
@@ -53,7 +53,7 @@ export { validate } from "./validation.js";
53
53
  export type { ValidationError } from "./validation.js";
54
54
  export { BaseModel, snakeToCamel, camelToSnake } from "./baseModel.js";
55
55
  export { QueryBuilder } from "./queryBuilder.js";
56
- export { SQLTranslator, QueryCache } from "./sqlTranslation.js";
56
+ export { SQLTranslator, QueryCache } from "./sqlTranslator.js";
57
57
  export { CachedDatabaseAdapter } from "./cachedDatabase.js";
58
58
  export type { CachedAdapterOptions } from "./cachedDatabase.js";
59
59
  export { FakeData } from "./fakeData.js";