tina4-nodejs 3.13.18 → 3.13.20
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 +48 -12
- package/package.json +1 -1
- package/packages/core/src/response.ts +37 -1
- package/packages/core/src/server.ts +21 -1
- package/packages/orm/src/baseModel.ts +15 -1
- package/packages/orm/src/database.ts +59 -6
- package/packages/orm/src/index.ts +1 -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.20)
|
|
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.20 — 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
|
|
|
@@ -105,7 +105,7 @@ The HTTP foundation. Handles request/response lifecycle, route matching, middlew
|
|
|
105
105
|
- `router.ts` — Pattern matching with `{id}` dynamic params and `{...slug}` catch-all
|
|
106
106
|
- `routeDiscovery.ts` — Scans `src/routes/` recursively, maps files to endpoints (converts `[id]` dirs to `{id}` URL patterns)
|
|
107
107
|
- `request.ts` — Wraps `IncomingMessage`, adds `.params`, `.query`, `.body`
|
|
108
|
-
- `response.ts` — Wraps `ServerResponse`, adds `.json()`, `.html()`, `.status()`, `.send()`, `.redirect()`
|
|
108
|
+
- `response.ts` — Wraps `ServerResponse`, adds `.json()`, `.html()`, `.status()`, `.send()`, `.redirect()`. `res.json(...)` / `response(...)` auto-serialize an ORM model (→ JSON object), an array of models, or a `DatabaseResult` (→ JSON array) — no manual `toDict()`/`toJson()`. Plain objects, arrays and strings behave exactly as before (purely additive).
|
|
109
109
|
- `middleware.ts` — Chain runner, built-in CORS and request logger
|
|
110
110
|
- `static.ts` — Serves files from `public/` with MIME type detection
|
|
111
111
|
- `types.ts` — All shared type definitions (`Tina4Request`, `Tina4Response`, `RouteHandler`, etc.)
|
|
@@ -243,6 +243,8 @@ response.xml(content, status?): Tina4Response
|
|
|
243
243
|
response.stream(source: AsyncIterable<string | Buffer>, contentType?: string): Promise<Tina4Response> // SSE/streaming
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
+
`res.json(model)`, `res.json(arrayOfModels)`, and `res.json(db.fetch(...))` auto-serialize to JSON — a single model becomes a JSON object, an array of models or a `DatabaseResult` becomes a JSON array. No manual `toDict()`/`toJson()` needed.
|
|
247
|
+
|
|
246
248
|
### Queue
|
|
247
249
|
|
|
248
250
|
```typescript
|
|
@@ -553,7 +555,7 @@ r.group("/api/v1", (g) => {
|
|
|
553
555
|
Full Database API. The same instance covers all five drivers (sqlite, postgres, mysql, mssql, firebird) — pick the driver via `TINA4_DATABASE_URL` or pass a `DatabaseConfig` to `initDatabase()`.
|
|
554
556
|
|
|
555
557
|
```typescript
|
|
556
|
-
import { initDatabase, Database, DatabaseResult } from "@tina4/orm";
|
|
558
|
+
import { initDatabase, bindDatabase, createAdapterFromUrl, Database, DatabaseResult } from "@tina4/orm";
|
|
557
559
|
|
|
558
560
|
const db = await initDatabase({ url: "sqlite:///app.db" });
|
|
559
561
|
// Connection pooling: pass `pool: 4` for round-robin connections.
|
|
@@ -595,6 +597,30 @@ db.cacheClear(): void
|
|
|
595
597
|
db.pool
|
|
596
598
|
```
|
|
597
599
|
|
|
600
|
+
### Binding adapters: `bindDatabase` / `createAdapterFromUrl`
|
|
601
|
+
|
|
602
|
+
There are three ways models get an adapter, in increasing order of explicitness:
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
import { initDatabase, bindDatabase, createAdapterFromUrl } from "@tina4/orm";
|
|
606
|
+
|
|
607
|
+
// (a) .env auto-default (unchanged) — initDatabase() auto-binds the default at boot.
|
|
608
|
+
// Most apps need nothing more than TINA4_DATABASE_URL in .env.
|
|
609
|
+
const db = await initDatabase({ url: "sqlite:///app.db" });
|
|
610
|
+
|
|
611
|
+
// (b) Set or override the default explicitly with bindDatabase(adapter).
|
|
612
|
+
bindDatabase(adapter);
|
|
613
|
+
|
|
614
|
+
// (c) Register a NAMED / secondary connection and point a model at it.
|
|
615
|
+
bindDatabase(await createAdapterFromUrl("postgres://localhost:5432/analytics"), "analytics");
|
|
616
|
+
// then a model selects it:
|
|
617
|
+
// class Visit extends BaseModel { static _db = "analytics"; }
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
- `bindDatabase(adapter, name?)` — public binder. With no `name` it sets/overrides the **default** connection; with a `name` it registers a **named** connection. `initDatabase()` (auto-binds the `.env` default) and the internal `setAdapter()` are unchanged — `bindDatabase` is additive and non-breaking.
|
|
621
|
+
- `createAdapterFromUrl(url, user?, pass?)` — now exported. Builds a `DatabaseAdapter` from a connection URL (and optional credentials), ready to pass to `bindDatabase`.
|
|
622
|
+
- A model selects a named connection via `static _db = "analytics"`. A mistyped/missing named connection (e.g. `static _db = "typo"`) now **throws** a clear error instead of silently falling back to the default.
|
|
623
|
+
|
|
598
624
|
**`tina4_sequences` table** — Auto-created by `getNextId()` on first use for SQLite, MySQL, and MSSQL. Stores the current sequence value per table. Do not modify this table manually.
|
|
599
625
|
|
|
600
626
|
## Module: ORM (`packages/orm/src/baseModel.ts`)
|
|
@@ -602,7 +628,7 @@ db.pool
|
|
|
602
628
|
Active-Record base class. Models live in `src/models/` and are auto-discovered. Use `static fields` (not decorators) — same convention across all four frameworks.
|
|
603
629
|
|
|
604
630
|
```typescript
|
|
605
|
-
import { BaseModel, initDatabase,
|
|
631
|
+
import { BaseModel, initDatabase, bindDatabase, createAdapterFromUrl } from "@tina4/orm";
|
|
606
632
|
|
|
607
633
|
export default class User extends BaseModel {
|
|
608
634
|
static tableName = "users";
|
|
@@ -612,10 +638,15 @@ export default class User extends BaseModel {
|
|
|
612
638
|
author_id: { type: "foreignKey" as const, references: "Author" }, // auto-wires belongsTo + hasMany
|
|
613
639
|
};
|
|
614
640
|
static softDelete = true; // optional — toggles is_deleted column
|
|
641
|
+
// static _db = "analytics"; // optional — bind this model to a named connection
|
|
615
642
|
}
|
|
616
643
|
|
|
644
|
+
// Constructor accepts an object OR a JSON object string. Passing an array throws TypeError.
|
|
645
|
+
const user = new User({ email: "alice@example.com" });
|
|
646
|
+
const user2 = new User('{"email":"bob@example.com"}'); // JSON object string -> one record
|
|
647
|
+
// new User([{ ... }]); // throws TypeError — map over the list to build many records
|
|
648
|
+
|
|
617
649
|
// Instance methods (chainable where it makes sense)
|
|
618
|
-
const user = new User({ email: "alice@example.com" });
|
|
619
650
|
user.save(); // returns this on success, false on failure
|
|
620
651
|
user.delete(); // soft-delete if enabled, otherwise hard
|
|
621
652
|
user.forceDelete(); // bypasses soft-delete
|
|
@@ -645,11 +676,16 @@ User.createTable();
|
|
|
645
676
|
User.query(): QueryBuilder;
|
|
646
677
|
BaseModel.registerModel(name, class); // for foreignKey name resolution
|
|
647
678
|
|
|
648
|
-
// Models bind to the active adapter, not a Database wrapper.
|
|
649
|
-
//
|
|
650
|
-
await initDatabase({ url: "sqlite:///app.db" }); // sets the
|
|
651
|
-
//
|
|
652
|
-
|
|
679
|
+
// Models bind to the active adapter, not a Database wrapper. There are three ways:
|
|
680
|
+
// (a) .env auto-default (unchanged) — initDatabase() auto-binds the default at boot:
|
|
681
|
+
await initDatabase({ url: "sqlite:///app.db" }); // sets the default adapter for all models
|
|
682
|
+
// (b) set/override the default explicitly:
|
|
683
|
+
bindDatabase(adapter);
|
|
684
|
+
// (c) register a NAMED/secondary connection, then point a model at it with `static _db`:
|
|
685
|
+
bindDatabase(await createAdapterFromUrl("postgres://localhost:5432/analytics"), "analytics");
|
|
686
|
+
// class Visit extends BaseModel { static _db = "analytics"; }
|
|
687
|
+
// A mistyped/missing named connection (e.g. static _db = "typo") now throws instead of
|
|
688
|
+
// silently falling back to the default. (initDatabase / the internal setAdapter are unchanged.)
|
|
653
689
|
```
|
|
654
690
|
|
|
655
691
|
**Soft delete:** set `static softDelete = true`. Adds an `is_deleted` INTEGER column (0/1). `delete()` flips the flag, `forceDelete()` removes the row, `restore()` clears it.
|
|
@@ -1062,7 +1098,7 @@ When adding new features, add a corresponding `test/<feature>.test.ts` file.
|
|
|
1062
1098
|
## v3 Features Summary
|
|
1063
1099
|
|
|
1064
1100
|
- **45 built-in features**, zero third-party dependencies
|
|
1065
|
-
- **3,
|
|
1101
|
+
- **3,684 tests** passing across all modules
|
|
1066
1102
|
- **Race-safe `getNextId()`** with atomic sequence table (`tina4_sequences`) for SQLite/MySQL/MSSQL; PostgreSQL auto-creates sequences
|
|
1067
1103
|
- **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)
|
|
1068
1104
|
- **Production server auto-detect**: `npx tina4nodejs serve --production` auto-uses cluster mode
|
package/package.json
CHANGED
|
@@ -81,6 +81,38 @@ export function setFrond(engine: InstanceType<any>): void {
|
|
|
81
81
|
* return response.json(data, 201); // Method
|
|
82
82
|
* return response.redirect("/login"); // Special
|
|
83
83
|
*/
|
|
84
|
+
/**
|
|
85
|
+
* Normalise domain objects into JSON-serialisable values so handlers can
|
|
86
|
+
* `return response(model)` / `res.json(model)` without calling .toDict() by hand:
|
|
87
|
+
*
|
|
88
|
+
* return response(user); // ORM model -> object
|
|
89
|
+
* return response(await User.all()); // model[] -> object[]
|
|
90
|
+
* return response(await db.fetch(sql));// DatabaseResult -> object[]
|
|
91
|
+
*
|
|
92
|
+
* Duck-typed (no @tina4/orm import — avoids a package cycle): a callable
|
|
93
|
+
* `toDict` marks a model; a `records` array plus a `toArray` method marks a
|
|
94
|
+
* query result. Plain objects / arrays / scalars pass through unchanged.
|
|
95
|
+
*/
|
|
96
|
+
function toJsonable(data: unknown): unknown {
|
|
97
|
+
if (data === null || typeof data !== "object" || Buffer.isBuffer(data)) {
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
const obj = data as Record<string, unknown>;
|
|
101
|
+
// Query result (DatabaseResult-like): records array + toArray method.
|
|
102
|
+
if (Array.isArray(obj.records) && typeof obj.toArray === "function") {
|
|
103
|
+
return obj.records;
|
|
104
|
+
}
|
|
105
|
+
// ORM model: callable toDict().
|
|
106
|
+
if (typeof obj.toDict === "function") {
|
|
107
|
+
return (obj.toDict as () => unknown)();
|
|
108
|
+
}
|
|
109
|
+
// Collections: normalise each element (array of models -> array of objects).
|
|
110
|
+
if (Array.isArray(data)) {
|
|
111
|
+
return data.map((item) => toJsonable(item));
|
|
112
|
+
}
|
|
113
|
+
return data;
|
|
114
|
+
}
|
|
115
|
+
|
|
84
116
|
export function createResponse(res: ServerResponse): Tina4Response {
|
|
85
117
|
|
|
86
118
|
// ── Guard: prevent writing after headers are sent ──
|
|
@@ -95,6 +127,10 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
95
127
|
const response = function (data?: unknown, statusCode?: number, contentType?: string): Tina4Response {
|
|
96
128
|
if (res.headersSent) return response;
|
|
97
129
|
|
|
130
|
+
// Normalise ORM models / collections / query results so handlers can
|
|
131
|
+
// `return response(model)` without serialising by hand.
|
|
132
|
+
data = toJsonable(data);
|
|
133
|
+
|
|
98
134
|
if (statusCode !== undefined) {
|
|
99
135
|
res.statusCode = statusCode;
|
|
100
136
|
}
|
|
@@ -143,7 +179,7 @@ export function createResponse(res: ServerResponse): Tina4Response {
|
|
|
143
179
|
if (res.headersSent) return response;
|
|
144
180
|
if (status !== undefined) res.statusCode = status;
|
|
145
181
|
safeSetHeader("Content-Type", "application/json");
|
|
146
|
-
safeEnd(JSON.stringify(data));
|
|
182
|
+
safeEnd(JSON.stringify(toJsonable(data)));
|
|
147
183
|
return response;
|
|
148
184
|
};
|
|
149
185
|
|
|
@@ -12,7 +12,7 @@ import { validToken, getPayload, refreshToken } from "./auth.js";
|
|
|
12
12
|
import { discoverRoutes } from "./routeDiscovery.js";
|
|
13
13
|
import { createRequest } from "./request.js";
|
|
14
14
|
import { createResponse, setDefaultTemplatesDir } from "./response.js";
|
|
15
|
-
import { MiddlewareChain, cors, requestLogger } from "./middleware.js";
|
|
15
|
+
import { MiddlewareChain, MiddlewareRunner, cors, requestLogger } from "./middleware.js";
|
|
16
16
|
import { tryServeStatic } from "./static.js";
|
|
17
17
|
import { loadEnv, isTruthy } from "./dotenv.js";
|
|
18
18
|
import { createHealthRoutes } from "./health.js";
|
|
@@ -1108,6 +1108,19 @@ ${reset}
|
|
|
1108
1108
|
req.params = match.params;
|
|
1109
1109
|
matchedPattern = match.pattern;
|
|
1110
1110
|
|
|
1111
|
+
// Global class-based middleware registered via Router.use(...) /
|
|
1112
|
+
// MiddlewareRunner.use(...) — run the beforeX hooks before the handler.
|
|
1113
|
+
// beforeX may set response headers (they persist through the handler's
|
|
1114
|
+
// write), mutate the request, or short-circuit on a >= 400 status.
|
|
1115
|
+
// (Parity with Python/PHP/Ruby, whose Router.use class middleware runs.)
|
|
1116
|
+
const globalMiddleware = [
|
|
1117
|
+
...new Set([...Router.getClassMiddlewares(), ...MiddlewareRunner.getGlobal()]),
|
|
1118
|
+
];
|
|
1119
|
+
if (globalMiddleware.length > 0) {
|
|
1120
|
+
const [, , proceed] = MiddlewareRunner.runBefore(globalMiddleware, req, res);
|
|
1121
|
+
if (!proceed || res.raw.writableEnded) return;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1111
1124
|
// Run per-route middlewares if any
|
|
1112
1125
|
if (match.middlewares && match.middlewares.length > 0) {
|
|
1113
1126
|
const proceed = await runRouteMiddlewares(match.middlewares, req, res);
|
|
@@ -1198,6 +1211,13 @@ ${reset}
|
|
|
1198
1211
|
await res.render(match.template, result as Record<string, unknown>);
|
|
1199
1212
|
}
|
|
1200
1213
|
|
|
1214
|
+
// Global class-based middleware afterX hooks (logging / post-processing).
|
|
1215
|
+
// Header mutations here are no-ops once the response is flushed (Node
|
|
1216
|
+
// sends headers with the body) — set response headers in beforeX.
|
|
1217
|
+
if (globalMiddleware.length > 0) {
|
|
1218
|
+
MiddlewareRunner.runAfter(globalMiddleware, req, res);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1201
1221
|
if (!res.raw.writableEnded) {
|
|
1202
1222
|
res.raw.end();
|
|
1203
1223
|
}
|
|
@@ -95,7 +95,21 @@ export class BaseModel {
|
|
|
95
95
|
/** Relationship cache for lazy loading */
|
|
96
96
|
private _relCache: Record<string, unknown> = {};
|
|
97
97
|
|
|
98
|
-
constructor(data?: Record<string, unknown>) {
|
|
98
|
+
constructor(data?: Record<string, unknown> | string) {
|
|
99
|
+
// Accept a JSON object string (parity with Python/PHP/Ruby):
|
|
100
|
+
// new Widget('{"id":1,"name":"alpha"}')
|
|
101
|
+
if (typeof data === "string") {
|
|
102
|
+
data = JSON.parse(data) as Record<string, unknown>;
|
|
103
|
+
}
|
|
104
|
+
// A single model is one record — reject an array with a clear message
|
|
105
|
+
// (previously an array silently produced an empty model).
|
|
106
|
+
if (Array.isArray(data)) {
|
|
107
|
+
throw new TypeError(
|
|
108
|
+
`${(this.constructor as typeof BaseModel).name} expects an object, keyword data, ` +
|
|
109
|
+
`or a JSON object string for one record — got an array. ` +
|
|
110
|
+
`Map over the list to build many records.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
99
113
|
if (data) {
|
|
100
114
|
const ModelClass = this.constructor as typeof BaseModel;
|
|
101
115
|
// If autoMap is on, auto-generate fieldMapping from camelCase fields
|
|
@@ -132,6 +132,39 @@ export function setAdapter(adapter: DatabaseAdapter): void {
|
|
|
132
132
|
activeAdapter = adapter;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Public, user-facing API to bind a database connection.
|
|
137
|
+
*
|
|
138
|
+
* - No `name` → registers `adapter` as the global default connection
|
|
139
|
+
* (what `getAdapter()` returns and what every model resolves to unless it
|
|
140
|
+
* declares `static _db`). This is the manual equivalent of the auto-binding
|
|
141
|
+
* that `initDatabase()` performs from `.env`/`TINA4_DATABASE_URL`.
|
|
142
|
+
* - With `name` → registers `adapter` in the named registry. A model with
|
|
143
|
+
* `static _db = name` resolves to it via `getNamedAdapter(name)`.
|
|
144
|
+
*
|
|
145
|
+
* Mirrors the Python master `bind_database(db, name=None)`.
|
|
146
|
+
*
|
|
147
|
+
* import { bindDatabase, createAdapterFromUrl } from "@tina4/orm";
|
|
148
|
+
*
|
|
149
|
+
* // Default connection
|
|
150
|
+
* bindDatabase(adapter);
|
|
151
|
+
*
|
|
152
|
+
* // Named secondary connection built from a URL (kept synchronous —
|
|
153
|
+
* // build the adapter first, then bind it)
|
|
154
|
+
* bindDatabase(await createAdapterFromUrl(url, user, pass), "analytics");
|
|
155
|
+
*
|
|
156
|
+
* `bindDatabase` itself is synchronous: it takes an already-constructed
|
|
157
|
+
* adapter. Use `createAdapterFromUrl()` to build a named secondary adapter
|
|
158
|
+
* from a URL without making it the default.
|
|
159
|
+
*/
|
|
160
|
+
export function bindDatabase(adapter: DatabaseAdapter, name?: string): void {
|
|
161
|
+
if (name === undefined) {
|
|
162
|
+
setAdapter(adapter);
|
|
163
|
+
} else {
|
|
164
|
+
namedAdapters.set(name, adapter);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
135
168
|
export function getAdapter(): DatabaseAdapter {
|
|
136
169
|
if (!activeAdapter) {
|
|
137
170
|
throw new Error("No database adapter configured. Call setAdapter() first.");
|
|
@@ -148,13 +181,24 @@ export function setNamedAdapter(name: string, adapter: DatabaseAdapter): void {
|
|
|
148
181
|
}
|
|
149
182
|
|
|
150
183
|
/**
|
|
151
|
-
* Get a named adapter
|
|
184
|
+
* Get a named adapter previously registered via `bindDatabase(adapter, name)`
|
|
185
|
+
* (or the lower-level `setNamedAdapter(name, adapter)`).
|
|
186
|
+
*
|
|
187
|
+
* Throws a clear error if the name isn't registered — a model that declares
|
|
188
|
+
* `static _db = "name"` resolves through here, so a missing name means the
|
|
189
|
+
* connection was never bound. The message tells the developer exactly how to
|
|
190
|
+
* fix it rather than silently falling back to the default connection (which
|
|
191
|
+
* would hide the mistake and write to the wrong database).
|
|
152
192
|
*/
|
|
153
193
|
export function getNamedAdapter(name: string): DatabaseAdapter {
|
|
154
194
|
const adapter = namedAdapters.get(name);
|
|
155
195
|
if (adapter) return adapter;
|
|
156
|
-
|
|
157
|
-
|
|
196
|
+
throw new Error(
|
|
197
|
+
`No database adapter registered under the name "${name}". ` +
|
|
198
|
+
`Call bindDatabase(adapter, "${name}") before using a model with ` +
|
|
199
|
+
`static _db = "${name}" (build a secondary adapter with ` +
|
|
200
|
+
`createAdapterFromUrl(url, user, pass) if you need one from a URL).`,
|
|
201
|
+
);
|
|
158
202
|
}
|
|
159
203
|
|
|
160
204
|
export function closeDatabase(): void {
|
|
@@ -908,10 +952,19 @@ export class Database {
|
|
|
908
952
|
}
|
|
909
953
|
|
|
910
954
|
/**
|
|
911
|
-
*
|
|
912
|
-
*
|
|
955
|
+
* Build a connected `DatabaseAdapter` from a connection URL.
|
|
956
|
+
*
|
|
957
|
+
* Used internally by `initDatabase()` and `Database.create()`, and exported so
|
|
958
|
+
* users can construct a NAMED secondary adapter without making it the default:
|
|
959
|
+
*
|
|
960
|
+
* bindDatabase(await createAdapterFromUrl(url, user, pass), "analytics");
|
|
961
|
+
*
|
|
962
|
+
* Unlike `initDatabase()`, this does NOT call `setAdapter()` — it returns a
|
|
963
|
+
* standalone adapter that the caller decides what to do with. For async engines
|
|
964
|
+
* (Postgres/MySQL/MSSQL/Firebird/Mongo) the returned adapter is already
|
|
965
|
+
* connected; SQLite connects lazily.
|
|
913
966
|
*/
|
|
914
|
-
async function createAdapterFromUrl(url: string, username?: string, password?: string): Promise<DatabaseAdapter> {
|
|
967
|
+
export async function createAdapterFromUrl(url: string, username?: string, password?: string): Promise<DatabaseAdapter> {
|
|
915
968
|
const parsed = parseDatabaseUrl(url, username, password);
|
|
916
969
|
|
|
917
970
|
switch (parsed.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, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter, resolveDbPool, stripTrailingSemicolons } from "./database.js";
|
|
17
|
+
export { Database, initDatabase, getAdapter, setAdapter, bindDatabase, createAdapterFromUrl, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter, resolveDbPool, stripTrailingSemicolons } from "./database.js";
|
|
18
18
|
export {
|
|
19
19
|
adapterFetch, adapterQuery, adapterFetchOne, adapterExecute,
|
|
20
20
|
adapterStartTransaction, adapterCommit, adapterRollback,
|