tina4-nodejs 3.13.1 → 3.13.3
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 +2 -2
- package/package.json +1 -1
- package/packages/core/src/ai.ts +4 -4
- package/packages/core/src/env.ts +113 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/logger.ts +68 -1
- package/packages/core/src/scss.ts +60 -20
- package/packages/core/src/sessionHandlers/databaseHandler.ts +2 -2
- package/packages/orm/src/baseModel.ts +3 -3
- package/packages/orm/src/database.ts +4 -4
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.3)
|
|
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.3 — 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
|
|
package/package.json
CHANGED
package/packages/core/src/ai.ts
CHANGED
|
@@ -424,7 +424,7 @@ npx tina4nodejs routes # List routes
|
|
|
424
424
|
## Database
|
|
425
425
|
|
|
426
426
|
Default: SQLite via \`node:sqlite\`. Adapters for PostgreSQL, MySQL, MSSQL, Firebird.
|
|
427
|
-
Set \`
|
|
427
|
+
Set \`TINA4_DATABASE_URL\` in \`.env\` (e.g. \`postgres://localhost:5432/mydb\`).
|
|
428
428
|
|
|
429
429
|
## Auth
|
|
430
430
|
|
|
@@ -485,7 +485,7 @@ npx tina4nodejs routes # List routes
|
|
|
485
485
|
## Database
|
|
486
486
|
|
|
487
487
|
Default: SQLite via \`node:sqlite\`. Adapters for PostgreSQL, MySQL, MSSQL, Firebird.
|
|
488
|
-
Set \`
|
|
488
|
+
Set \`TINA4_DATABASE_URL\` in \`.env\`.
|
|
489
489
|
`;
|
|
490
490
|
}
|
|
491
491
|
|
|
@@ -593,7 +593,7 @@ npx tina4nodejs routes # List routes
|
|
|
593
593
|
## Database
|
|
594
594
|
|
|
595
595
|
Default: SQLite via \`node:sqlite\`. Adapters for PostgreSQL, MySQL, MSSQL, Firebird.
|
|
596
|
-
Set \`
|
|
596
|
+
Set \`TINA4_DATABASE_URL\` in \`.env\` (e.g. \`sqlite:///path/to/db.sqlite\`, \`postgres://localhost:5432/mydb\`).
|
|
597
597
|
|
|
598
598
|
## Auth
|
|
599
599
|
|
|
@@ -693,7 +693,7 @@ npx tina4nodejs routes # List routes
|
|
|
693
693
|
## Database
|
|
694
694
|
|
|
695
695
|
Default: SQLite via \`node:sqlite\`. Adapters for PostgreSQL, MySQL, MSSQL, Firebird.
|
|
696
|
-
Set \`
|
|
696
|
+
Set \`TINA4_DATABASE_URL\` in \`.env\`.
|
|
697
697
|
|
|
698
698
|
## Auth
|
|
699
699
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed environment-variable helpers — zero-deps.
|
|
3
|
+
*
|
|
4
|
+
* Reading env vars by hand gets old fast: every boolean flag becomes a
|
|
5
|
+
* `(process.env.X ?? "false").toLowerCase() === "true" || ...` incantation,
|
|
6
|
+
* every numeric tuning knob needs a parseInt + isNaN guard. `Env` centralises
|
|
7
|
+
* that. Same API across all four Tina4 frameworks (`Tina4\Env` in PHP,
|
|
8
|
+
* `Tina4::Env` in Ruby, `Env` in Python).
|
|
9
|
+
*
|
|
10
|
+
* import { Env } from "@tina4/core";
|
|
11
|
+
*
|
|
12
|
+
* const debug = Env.bool("TINA4_DEBUG"); // default false
|
|
13
|
+
* const workers = Env.int("WORKERS", 4);
|
|
14
|
+
* const rate = Env.float("RATE_LIMIT", 10.0);
|
|
15
|
+
* const region = Env.str("AWS_REGION", "us-east-1");
|
|
16
|
+
*
|
|
17
|
+
* Values are accepted case-insensitively after `.trim().toLowerCase()`. Truthy:
|
|
18
|
+
* `1 / true / on / yes / y / t`. Falsy: `0 / false / off / no / n / f / ""`.
|
|
19
|
+
* Anything else returns `default`. Unparseable ints/floats log a warning via
|
|
20
|
+
* `Log` (when available) and fall back to `default` — never throw.
|
|
21
|
+
*/
|
|
22
|
+
const TRUTHY = new Set(["1", "true", "on", "yes", "y", "t"]);
|
|
23
|
+
const FALSY = new Set(["0", "false", "off", "no", "n", "f", ""]);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Emit a warning via Log without creating a circular import at module load.
|
|
27
|
+
* Log itself depends on env parsing, so we resolve it lazily and swallow any
|
|
28
|
+
* "not yet wired" errors (very early bootstrap, ESM cycle, etc.).
|
|
29
|
+
*/
|
|
30
|
+
function logWarning(message: string): void {
|
|
31
|
+
try {
|
|
32
|
+
// Lazy import to avoid the env → logger → env cycle.
|
|
33
|
+
// Top-level await isn't usable here (sync API), so we fire-and-forget.
|
|
34
|
+
import("./logger.js")
|
|
35
|
+
.then((mod) => {
|
|
36
|
+
try {
|
|
37
|
+
mod.Log.warn(message);
|
|
38
|
+
} catch {
|
|
39
|
+
/* Log not ready — skip */
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
.catch(() => {
|
|
43
|
+
/* Module not yet loadable — skip */
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
/* Defensive — never let logging break the caller */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Typed environment-variable helpers.
|
|
52
|
+
*
|
|
53
|
+
* All methods are static so callers can write `Env.bool(...)` without
|
|
54
|
+
* instantiating anything — matching the Python/PHP/Ruby ports.
|
|
55
|
+
*/
|
|
56
|
+
export class Env {
|
|
57
|
+
/**
|
|
58
|
+
* Read `name` and coerce to bool.
|
|
59
|
+
*
|
|
60
|
+
* Truthy values (case-insensitive after trim): `1`, `true`, `on`, `yes`,
|
|
61
|
+
* `y`, `t`. Falsy: `0`, `false`, `off`, `no`, `n`, `f`, empty string.
|
|
62
|
+
* Anything else returns the `defaultValue` — never throws.
|
|
63
|
+
*/
|
|
64
|
+
static bool(name: string, defaultValue = false): boolean {
|
|
65
|
+
const raw = process.env[name];
|
|
66
|
+
if (raw === undefined) return defaultValue;
|
|
67
|
+
const token = raw.trim().toLowerCase();
|
|
68
|
+
if (TRUTHY.has(token)) return true;
|
|
69
|
+
if (FALSY.has(token)) return false;
|
|
70
|
+
return defaultValue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Read `name` and coerce to int. Returns `defaultValue` on parse failure. */
|
|
74
|
+
static int(name: string, defaultValue = 0): number {
|
|
75
|
+
const raw = process.env[name];
|
|
76
|
+
if (raw === undefined) return defaultValue;
|
|
77
|
+
const parsed = Number.parseInt(raw.trim(), 10);
|
|
78
|
+
if (Number.isNaN(parsed)) {
|
|
79
|
+
logWarning(
|
|
80
|
+
`Env.int(${JSON.stringify(name)}): could not parse ${JSON.stringify(raw)} as int — using default ${defaultValue}`,
|
|
81
|
+
);
|
|
82
|
+
return defaultValue;
|
|
83
|
+
}
|
|
84
|
+
return parsed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Read `name` and coerce to float. Returns `defaultValue` on parse failure. */
|
|
88
|
+
static float(name: string, defaultValue = 0.0): number {
|
|
89
|
+
const raw = process.env[name];
|
|
90
|
+
if (raw === undefined) return defaultValue;
|
|
91
|
+
const parsed = Number.parseFloat(raw.trim());
|
|
92
|
+
if (Number.isNaN(parsed)) {
|
|
93
|
+
logWarning(
|
|
94
|
+
`Env.float(${JSON.stringify(name)}): could not parse ${JSON.stringify(raw)} as float — using default ${defaultValue}`,
|
|
95
|
+
);
|
|
96
|
+
return defaultValue;
|
|
97
|
+
}
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Read `name` as a string. Returns `defaultValue` if unset.
|
|
103
|
+
*
|
|
104
|
+
* Whitespace is preserved — this is a pass-through for the raw env value.
|
|
105
|
+
* `Env.str("PATH")` is exactly `process.env.PATH ?? ""` with a more
|
|
106
|
+
* discoverable name.
|
|
107
|
+
*/
|
|
108
|
+
static str(name: string, defaultValue = ""): string {
|
|
109
|
+
const raw = process.env[name];
|
|
110
|
+
if (raw === undefined) return defaultValue;
|
|
111
|
+
return raw;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -24,6 +24,7 @@ export { createRequest } from "./request.js";
|
|
|
24
24
|
export { createResponse, errorResponse, setDefaultTemplatesDir, getFrond, setFrond, getFrameworkFrond } from "./response.js";
|
|
25
25
|
export { tryServeStatic } from "./static.js";
|
|
26
26
|
export { loadEnv, getEnv, requireEnv, hasEnv, allEnv, resetEnv, isTruthy } from "./dotenv.js";
|
|
27
|
+
export { Env } from "./env.js";
|
|
27
28
|
export { Log } from "./logger.js";
|
|
28
29
|
export { createHealthRoute, createHealthRoutes, healthPath } from "./health.js";
|
|
29
30
|
export { rateLimiter } from "./rateLimiter.js";
|
|
@@ -28,9 +28,67 @@ interface LogEntry {
|
|
|
28
28
|
level: LogLevel;
|
|
29
29
|
message: string;
|
|
30
30
|
request_id?: string;
|
|
31
|
+
function?: string;
|
|
31
32
|
context?: unknown;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Log frames we walk past when looking for the real caller of a Log.* call.
|
|
37
|
+
* Mirrors Python's `_OWN_FRAMES`. Anything matching is treated as internal.
|
|
38
|
+
*/
|
|
39
|
+
const OWN_FRAMES = new Set<string>([
|
|
40
|
+
"log", "Log.log",
|
|
41
|
+
"callerName", "Log.callerName",
|
|
42
|
+
"info", "debug", "warning", "warn", "error", "critical",
|
|
43
|
+
"Log.info", "Log.debug", "Log.warning", "Log.warn", "Log.error", "Log.critical",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
/** V8 stack-trace markers that mean "no real function name". */
|
|
47
|
+
const ANON_NAMES = new Set<string>(["", "anonymous", "<anonymous>"]);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Return the function name that called Log.{info,debug,warning,error}.
|
|
51
|
+
*
|
|
52
|
+
* Active only when `TINA4_LOG_FUNC=true` — captures `new Error().stack`,
|
|
53
|
+
* walks past Log's own frames (info / warn / error / debug / log /
|
|
54
|
+
* callerName) and returns the first user function name. Anonymous frames
|
|
55
|
+
* (`anonymous`, `<anonymous>`, bare file paths) are filtered out as noise.
|
|
56
|
+
* Returns undefined on any error — never throws. Parity feature #41 across
|
|
57
|
+
* all four Tina4 frameworks.
|
|
58
|
+
*/
|
|
59
|
+
function callerName(): string | undefined {
|
|
60
|
+
// Read the env directly (not via Env.bool) to keep the logger ↔ env cycle
|
|
61
|
+
// one-way: env helpers depend on Log, not the other way around.
|
|
62
|
+
const raw = (process.env.TINA4_LOG_FUNC ?? "").trim().toLowerCase();
|
|
63
|
+
if (raw !== "1" && raw !== "true" && raw !== "on" && raw !== "yes" && raw !== "y" && raw !== "t") {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const stack = new Error().stack;
|
|
68
|
+
if (!stack) return undefined;
|
|
69
|
+
const lines = stack.split("\n");
|
|
70
|
+
// Skip line 0 ("Error") and walk frames. Cap at 32 to defend against
|
|
71
|
+
// pathological recursion / wrapper stacks.
|
|
72
|
+
for (let i = 1; i < lines.length && i < 32; i++) {
|
|
73
|
+
const line = lines[i];
|
|
74
|
+
// V8 frame: " at functionName (file:line:col)"
|
|
75
|
+
// or " at file:line:col" (anonymous)
|
|
76
|
+
// or " at async functionName (...)"
|
|
77
|
+
const m = line.match(/^\s+at\s+(?:async\s+)?([^\s(]+)\s*\(/);
|
|
78
|
+
if (!m) continue;
|
|
79
|
+
const name = m[1];
|
|
80
|
+
// Strip the leading `Object.` / class prefix some V8s emit, then check.
|
|
81
|
+
const bare = name.includes(".") ? name.split(".").pop()! : name;
|
|
82
|
+
if (OWN_FRAMES.has(name) || OWN_FRAMES.has(bare)) continue;
|
|
83
|
+
if (ANON_NAMES.has(bare)) continue;
|
|
84
|
+
return bare;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
34
92
|
/** ANSI color codes for terminal output */
|
|
35
93
|
const COLORS: Record<LogLevel, string> = {
|
|
36
94
|
DEBUG: "\x1b[36m", // cyan
|
|
@@ -308,6 +366,14 @@ export class Log {
|
|
|
308
366
|
entry.request_id = Log.requestId;
|
|
309
367
|
}
|
|
310
368
|
|
|
369
|
+
// Caller-name injection — opt-in via TINA4_LOG_FUNC=true. Off by default
|
|
370
|
+
// so existing log output stays byte-identical for users who haven't asked
|
|
371
|
+
// for it. Parity feature across all four Tina4 frameworks.
|
|
372
|
+
const fnName = callerName();
|
|
373
|
+
if (fnName) {
|
|
374
|
+
entry.function = fnName;
|
|
375
|
+
}
|
|
376
|
+
|
|
311
377
|
if (data !== undefined) {
|
|
312
378
|
entry.context = data;
|
|
313
379
|
}
|
|
@@ -315,8 +381,9 @@ export class Log {
|
|
|
315
381
|
// Build human-readable line
|
|
316
382
|
const paddedLevel = level.padEnd(8);
|
|
317
383
|
const reqPart = Log.requestId ? ` [${Log.requestId}]` : "";
|
|
384
|
+
const fnPart = fnName ? ` [${fnName}]` : "";
|
|
318
385
|
const dataPart = data !== undefined ? ` ${JSON.stringify(data)}` : "";
|
|
319
|
-
const humanLine = `${entry.timestamp} [${paddedLevel}]${reqPart} ${message}${dataPart}`;
|
|
386
|
+
const humanLine = `${entry.timestamp} [${paddedLevel}]${reqPart}${fnPart} ${message}${dataPart}`;
|
|
320
387
|
|
|
321
388
|
// Build the file-format line based on TINA4_LOG_FORMAT
|
|
322
389
|
const fileLine = cfg.format === "json" ? JSON.stringify(entry) : humanLine;
|
|
@@ -251,30 +251,70 @@ function resolveIncludes(
|
|
|
251
251
|
// ── Math Evaluation ──────────────────────────────────────────────
|
|
252
252
|
|
|
253
253
|
function evalMath(scss: string): string {
|
|
254
|
-
|
|
254
|
+
// Mixed-unit arithmetic is left verbatim — that is exactly what CSS calc()
|
|
255
|
+
// is for, and folding it silently produces invalid output (see tina4-nodejs#1).
|
|
256
|
+
// Math inside calc(...) is preserved untouched on the same principle: the
|
|
257
|
+
// author asked the browser to compute it.
|
|
258
|
+
//
|
|
259
|
+
// Rules for folding:
|
|
260
|
+
// * Both operands unitless → fold
|
|
261
|
+
// * Same unit on both operands → fold, keep unit
|
|
262
|
+
// * One operand unitless for * or / → fold, keep the other unit
|
|
263
|
+
// * Anything else (mixed units on +/-, etc) → leave verbatim
|
|
264
|
+
|
|
265
|
+
// Step 1 — mask calc(...) ranges so the math regex cannot eat into them.
|
|
266
|
+
const placeholders: string[] = [];
|
|
267
|
+
const masked = scss.replace(/calc\([^()]*\)/g, (m) => {
|
|
268
|
+
placeholders.push(m);
|
|
269
|
+
return `\x00CALC${placeholders.length - 1}\x00`;
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Step 2 — run the math fold on what remains.
|
|
273
|
+
const folded = masked.replace(
|
|
255
274
|
/([\d.]+)([a-z%]*)\s*([+\-*/])\s*([\d.]+)([a-z%]*)/g,
|
|
256
|
-
(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return _m;
|
|
275
|
+
(full, n1: string, u1: string, op: string, n2: string, u2: string) => {
|
|
276
|
+
const num1 = parseFloat(n1);
|
|
277
|
+
const num2 = parseFloat(n2);
|
|
278
|
+
if (Number.isNaN(num1) || Number.isNaN(num2)) return full;
|
|
279
|
+
|
|
280
|
+
const unit1 = u1 || "";
|
|
281
|
+
const unit2 = u2 || "";
|
|
282
|
+
|
|
283
|
+
// Decide result unit; bail if units are incompatible.
|
|
284
|
+
let unit: string;
|
|
285
|
+
if (unit1 === unit2) {
|
|
286
|
+
unit = unit1;
|
|
287
|
+
} else if ((op === "*" || op === "/") && unit1 === "") {
|
|
288
|
+
unit = unit2;
|
|
289
|
+
} else if ((op === "*" || op === "/") && unit2 === "") {
|
|
290
|
+
unit = unit1;
|
|
291
|
+
} else {
|
|
292
|
+
return full;
|
|
275
293
|
}
|
|
294
|
+
|
|
295
|
+
let result: number;
|
|
296
|
+
switch (op) {
|
|
297
|
+
case "+": result = num1 + num2; break;
|
|
298
|
+
case "-": result = num1 - num2; break;
|
|
299
|
+
case "*": result = num1 * num2; break;
|
|
300
|
+
case "/":
|
|
301
|
+
if (num2 === 0) return full;
|
|
302
|
+
result = num1 / num2;
|
|
303
|
+
break;
|
|
304
|
+
default: return full;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (result === Math.floor(result)) {
|
|
308
|
+
return `${Math.floor(result)}${unit}`;
|
|
309
|
+
}
|
|
310
|
+
return `${result.toFixed(2)}${unit}`;
|
|
276
311
|
}
|
|
277
312
|
);
|
|
313
|
+
|
|
314
|
+
// Step 3 — restore the calc() ranges verbatim.
|
|
315
|
+
return folded.replace(/\x00CALC(\d+)\x00/g, (_m, idx: string) => {
|
|
316
|
+
return placeholders[parseInt(idx, 10)];
|
|
317
|
+
});
|
|
278
318
|
}
|
|
279
319
|
|
|
280
320
|
// ── Nesting Flattener ────────────────────────────────────────────
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Stores sessions in a `tina4_session` table with JSON data and expiry.
|
|
6
6
|
*
|
|
7
7
|
* Configure via environment variables:
|
|
8
|
-
*
|
|
8
|
+
* TINA4_DATABASE_URL (default: "sqlite:///data/tina4_sessions.db")
|
|
9
9
|
*
|
|
10
10
|
* The handler dynamically imports `better-sqlite3` and throws a clear
|
|
11
11
|
* error if the package is not installed.
|
|
@@ -20,7 +20,7 @@ interface SessionData {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export interface DatabaseSessionConfig {
|
|
23
|
-
/** SQLite database file path (default: extracted from
|
|
23
|
+
/** SQLite database file path (default: extracted from TINA4_DATABASE_URL or "data/tina4_sessions.db") */
|
|
24
24
|
dbPath?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -206,7 +206,7 @@ export class BaseModel {
|
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
208
|
* Get the database adapter for this model.
|
|
209
|
-
* If no adapter is registered, attempts auto-discovery from
|
|
209
|
+
* If no adapter is registered, attempts auto-discovery from TINA4_DATABASE_URL.
|
|
210
210
|
* SQLite URLs are initialised synchronously. Other engines require initDatabase()
|
|
211
211
|
* to be called before first use.
|
|
212
212
|
*/
|
|
@@ -229,12 +229,12 @@ export class BaseModel {
|
|
|
229
229
|
return adapter;
|
|
230
230
|
}
|
|
231
231
|
throw new Error(
|
|
232
|
-
`
|
|
232
|
+
`TINA4_DATABASE_URL is set to a non-SQLite engine ("${parsed.type}"). ` +
|
|
233
233
|
`Call await initDatabase() at startup before using ORM models.`,
|
|
234
234
|
);
|
|
235
235
|
}
|
|
236
236
|
throw new Error(
|
|
237
|
-
"No database adapter configured. Call initDatabase() or set
|
|
237
|
+
"No database adapter configured. Call initDatabase() or set TINA4_DATABASE_URL in .env.",
|
|
238
238
|
);
|
|
239
239
|
}
|
|
240
240
|
}
|
|
@@ -60,7 +60,7 @@ export interface DatabaseConfig {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Parsed result from a
|
|
63
|
+
* Parsed result from a TINA4_DATABASE_URL connection string.
|
|
64
64
|
*/
|
|
65
65
|
export interface ParsedDatabaseUrl {
|
|
66
66
|
type: "sqlite" | "postgres" | "mysql" | "mssql" | "firebird" | "mongodb" | "odbc";
|
|
@@ -75,7 +75,7 @@ export interface ParsedDatabaseUrl {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* Parse a
|
|
78
|
+
* Parse a TINA4_DATABASE_URL connection string into its components.
|
|
79
79
|
*
|
|
80
80
|
* Supported formats:
|
|
81
81
|
* sqlite:///path/to/db.sqlite
|
|
@@ -325,10 +325,10 @@ export class Database {
|
|
|
325
325
|
|
|
326
326
|
/**
|
|
327
327
|
* Create a Database from an environment variable.
|
|
328
|
-
* @param envKey - Name of the env var holding the connection URL. Defaults to "
|
|
328
|
+
* @param envKey - Name of the env var holding the connection URL. Defaults to "TINA4_DATABASE_URL".
|
|
329
329
|
* @param pool - Number of pooled connections (0 = single, N>0 = round-robin)
|
|
330
330
|
*/
|
|
331
|
-
static async fromEnv(envKey = "
|
|
331
|
+
static async fromEnv(envKey = "TINA4_DATABASE_URL", pool: number = 0): Promise<Database> {
|
|
332
332
|
const url = process.env[envKey];
|
|
333
333
|
if (!url) {
|
|
334
334
|
throw new Error(`Environment variable "${envKey}" is not set.`);
|