@zerotal/orm 1.15.1 → 1.15.2
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/CHANGELOG.md +19 -0
- package/package.json +3 -3
- package/src/db/DB.ts +4 -3
- package/src/db/QueryBuilder.ts +2 -2
- package/src/db/templateStrings.ts +19 -0
- package/src/index.ts +1 -0
- package/src/provider/DatabaseProvider.ts +4 -13
- package/src/schema/MigrationRunner.ts +5 -8
- package/src/schema/Schema.ts +3 -2
- package/src/schema/SchemaInspector.ts +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,25 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.15.2] — 2026-10-23
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **The ORM runs on Bun 1.4.4.** Every statement it assembled from a runtime string — the
|
|
16
|
+
string form of `DB.raw()`, the query builder, schema and migration DDL, the `SAVEPOINT`
|
|
17
|
+
a nested transaction issues, the validator's `unique` and `exists` rules — reached Bun's
|
|
18
|
+
`sql` function as a template object built with `Object.assign(parts, { raw: parts })`,
|
|
19
|
+
whose `raw` is enumerable. Bun 1.4.4 refuses that ("has an enumerable 'raw' property, so
|
|
20
|
+
it is not a template object"), so on that version and later none of those statements ran:
|
|
21
|
+
the package's own suite failed 288 tests and then hung. Earlier Bun accepted it, which is
|
|
22
|
+
why nothing noticed until the CI job that runs on the latest Bun did.
|
|
23
|
+
|
|
24
|
+
All of them now take the object from one helper, `_templateStrings()`, which defines
|
|
25
|
+
`raw` the way a real template object has it and freezes the result. `@zerotal/notifications`
|
|
26
|
+
and `@zerotal/testing` had built the same object by hand and use it too. One test runs the
|
|
27
|
+
ORM's paths through a connection that rejects an enumerable `raw`; another fails if any
|
|
28
|
+
package's source builds one again.
|
|
29
|
+
|
|
11
30
|
## [1.15.1] — 2026-09-13
|
|
12
31
|
|
|
13
32
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/orm",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"maturity": "stable",
|
|
6
6
|
"private": false,
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@zerotal/core": "1.15.
|
|
35
|
-
"@zerotal/validator": "1.15.
|
|
34
|
+
"@zerotal/core": "1.15.2",
|
|
35
|
+
"@zerotal/validator": "1.15.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "^5.8.0"
|
package/src/db/DB.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
allowNPlusOne as _allowNPlusOne,
|
|
18
18
|
type NPlusOneOptions,
|
|
19
19
|
} from "./NPlusOneDetector.ts";
|
|
20
|
+
import { _templateStrings } from "./templateStrings.ts";
|
|
20
21
|
|
|
21
22
|
// Test-override escape hatch — set by _setDbConnection() in test files.
|
|
22
23
|
// Production code resolves the connection from the container instead.
|
|
@@ -140,7 +141,7 @@ let _savepointCounter = 0;
|
|
|
140
141
|
|
|
141
142
|
/** Run a raw, parameter-less statement on a specific connection (SAVEPOINT, etc.). */
|
|
142
143
|
function _runRaw(conn: SQLInstance, sql: string): Promise<unknown> {
|
|
143
|
-
const arr =
|
|
144
|
+
const arr = _templateStrings([sql]);
|
|
144
145
|
return conn(arr);
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -160,7 +161,7 @@ export function _rawStatement(sql: string): Promise<unknown> {
|
|
|
160
161
|
/** Run a parameterised statement (`?` placeholders) on a specific connection. */
|
|
161
162
|
function _runParams(conn: SQLInstance, sql: string, params: unknown[]): Promise<unknown> {
|
|
162
163
|
const parts = sql.split("?");
|
|
163
|
-
const tpl =
|
|
164
|
+
const tpl = _templateStrings(parts);
|
|
164
165
|
return conn(tpl, ...params);
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -246,7 +247,7 @@ export const DB = {
|
|
|
246
247
|
if (typeof sql === "string") {
|
|
247
248
|
const bindings: unknown[] = Array.isArray(rest[0]) ? (rest[0] as unknown[]) : rest;
|
|
248
249
|
const parts = sql.split("?");
|
|
249
|
-
const tpl =
|
|
250
|
+
const tpl = _templateStrings(parts);
|
|
250
251
|
const rows = await conn<T>(tpl, ...bindings);
|
|
251
252
|
FrameworkEvents.emit(
|
|
252
253
|
new QueryExecuted(
|
package/src/db/QueryBuilder.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
SimplePaginateResult,
|
|
21
21
|
} from "./types.ts";
|
|
22
22
|
import { withPaginationHelpers, withSimplePaginationHelpers } from "./types.ts";
|
|
23
|
+
import { _templateStrings } from "./templateStrings.ts";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* The comparison operators accepted in a value-comparison slot. Exported so
|
|
@@ -204,8 +205,7 @@ function _getCachedTemplate(strings: string[]): TemplateStringsArray {
|
|
|
204
205
|
const key = strings.join("\x00");
|
|
205
206
|
let tpl = _tplCache.get(key);
|
|
206
207
|
if (!tpl) {
|
|
207
|
-
|
|
208
|
-
tpl = Object.assign(arr, { raw: arr }) as TemplateStringsArray;
|
|
208
|
+
tpl = _templateStrings(strings);
|
|
209
209
|
if (_tplCache.size >= _TPL_CACHE_MAX) {
|
|
210
210
|
// FIFO eviction: drop the oldest interned shape.
|
|
211
211
|
const oldest = _tplCache.keys().next().value;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the `strings` argument for a Bun SQL tagged-template call from plain strings.
|
|
3
|
+
*
|
|
4
|
+
* Bun's `sql` function wants what the language hands a tag: a frozen array whose `raw`
|
|
5
|
+
* is a property you cannot see when you enumerate it. `Object.assign(parts, { raw: parts })`
|
|
6
|
+
* makes the property enumerable, and Bun 1.4.4 refuses that with "has an enumerable 'raw'
|
|
7
|
+
* property, so it is not a template object" — every statement built from a runtime-assembled
|
|
8
|
+
* string failed, which is nearly everything the ORM sends. `defineProperty` leaves `raw`
|
|
9
|
+
* non-enumerable, as a real template object has it.
|
|
10
|
+
*
|
|
11
|
+
* Copies its input, so the caller's array is neither frozen nor given a `raw`.
|
|
12
|
+
*
|
|
13
|
+
* @internal Shared by the ORM and by the packages that issue SQL of their own.
|
|
14
|
+
*/
|
|
15
|
+
export function _templateStrings(parts: readonly string[]): TemplateStringsArray {
|
|
16
|
+
const strings = [...parts];
|
|
17
|
+
Object.defineProperty(strings, "raw", { value: strings });
|
|
18
|
+
return Object.freeze(strings) as TemplateStringsArray;
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -97,6 +97,7 @@ export type { InsertPayload, UpdatePayload } from "./model/payload.ts";
|
|
|
97
97
|
export { ModelQueryBuilder, _globalScopeRegistry } from "./model/ModelQueryBuilder.ts";
|
|
98
98
|
export type { GlobalScopeCallback, RelationConstraint } from "./model/ModelQueryBuilder.ts";
|
|
99
99
|
export { DB } from "./db/DB.ts";
|
|
100
|
+
export { _templateStrings } from "./db/templateStrings.ts";
|
|
100
101
|
export {
|
|
101
102
|
_getConnection,
|
|
102
103
|
_setDbConnection,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
RUN_MIGRATIONS_PATH,
|
|
26
26
|
_mintDiagnosisToken,
|
|
27
27
|
} from "../diagnostics/runMigrationsEndpoint.ts";
|
|
28
|
+
import { _templateStrings } from "../db/templateStrings.ts";
|
|
28
29
|
|
|
29
30
|
// Extend the core container registry so 'db' is a typed binding.
|
|
30
31
|
declare module "@zerotal/core" {
|
|
@@ -171,27 +172,17 @@ export class DatabaseProvider extends ServiceProvider {
|
|
|
171
172
|
let rows: unknown[];
|
|
172
173
|
if (ignoreId !== undefined) {
|
|
173
174
|
const strs = [`SELECT 1 FROM ${table} WHERE ${column} = `, ` AND id != `, ` LIMIT 1`];
|
|
174
|
-
rows = await conn(
|
|
175
|
-
Object.assign(strs, { raw: strs }) as unknown as TemplateStringsArray,
|
|
176
|
-
value,
|
|
177
|
-
ignoreId,
|
|
178
|
-
);
|
|
175
|
+
rows = await conn(_templateStrings(strs), value, ignoreId);
|
|
179
176
|
} else {
|
|
180
177
|
const strs = [`SELECT 1 FROM ${table} WHERE ${column} = `, ` LIMIT 1`];
|
|
181
|
-
rows = await conn(
|
|
182
|
-
Object.assign(strs, { raw: strs }) as unknown as TemplateStringsArray,
|
|
183
|
-
value,
|
|
184
|
-
);
|
|
178
|
+
rows = await conn(_templateStrings(strs), value);
|
|
185
179
|
}
|
|
186
180
|
return rows.length === 0; // true = unique (no duplicate found)
|
|
187
181
|
}
|
|
188
182
|
|
|
189
183
|
// exists
|
|
190
184
|
const strs = [`SELECT 1 FROM ${table} WHERE ${column} = `, ` LIMIT 1`];
|
|
191
|
-
const rows = await conn(
|
|
192
|
-
Object.assign(strs, { raw: strs }) as unknown as TemplateStringsArray,
|
|
193
|
-
value,
|
|
194
|
-
);
|
|
185
|
+
const rows = await conn(_templateStrings(strs), value);
|
|
195
186
|
return rows.length > 0; // true = exists (row found)
|
|
196
187
|
});
|
|
197
188
|
} catch {
|
|
@@ -8,6 +8,7 @@ import { MigrationRan } from "../events.ts";
|
|
|
8
8
|
import { dialectFor } from "../db/QueryBuilder.ts";
|
|
9
9
|
import { getDialect } from "../db/dialects/index.ts";
|
|
10
10
|
import { TransactionContext } from "../db/TransactionContext.ts";
|
|
11
|
+
import { _templateStrings } from "../db/templateStrings.ts";
|
|
11
12
|
|
|
12
13
|
// ── Public types ──────────────────────────────────────────────────────────────
|
|
13
14
|
|
|
@@ -375,9 +376,7 @@ export class MigrationRunner {
|
|
|
375
376
|
/** Execute a DDL string with no parameters. */
|
|
376
377
|
private async _ddl(sql: string): Promise<void> {
|
|
377
378
|
const strings = [sql];
|
|
378
|
-
const tpl =
|
|
379
|
-
raw: strings,
|
|
380
|
-
}) as TemplateStringsArray;
|
|
379
|
+
const tpl = _templateStrings(strings);
|
|
381
380
|
await this._active()(tpl);
|
|
382
381
|
}
|
|
383
382
|
|
|
@@ -389,22 +388,20 @@ export class MigrationRunner {
|
|
|
389
388
|
/** SELECT with exactly one bound parameter. */
|
|
390
389
|
private async _selectParam<T>(sql: string, value: unknown): Promise<T[]> {
|
|
391
390
|
const parts = sql.split("?");
|
|
392
|
-
const tpl =
|
|
391
|
+
const tpl = _templateStrings(parts);
|
|
393
392
|
return this._active()<T>(tpl, value);
|
|
394
393
|
}
|
|
395
394
|
|
|
396
395
|
/** DML with N bound parameters. */
|
|
397
396
|
private async _exec(sql: string, ...values: unknown[]): Promise<void> {
|
|
398
397
|
const parts = sql.split("?");
|
|
399
|
-
const tpl =
|
|
398
|
+
const tpl = _templateStrings(parts);
|
|
400
399
|
await this._active()(tpl, ...values);
|
|
401
400
|
}
|
|
402
401
|
|
|
403
402
|
private async _exec0<T>(sql: string): Promise<T[]> {
|
|
404
403
|
const strings = [sql];
|
|
405
|
-
const tpl =
|
|
406
|
-
raw: strings,
|
|
407
|
-
}) as TemplateStringsArray;
|
|
404
|
+
const tpl = _templateStrings(strings);
|
|
408
405
|
return this._active()<T>(tpl);
|
|
409
406
|
}
|
|
410
407
|
|
package/src/schema/Schema.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { _getScopedDbConnection } from "../db/DB.ts";
|
|
|
2
2
|
import { _getDialect } from "../model/BaseModel.ts";
|
|
3
3
|
import { getDialect } from "../db/dialects/index.ts";
|
|
4
4
|
import { Blueprint } from "./Blueprint.ts";
|
|
5
|
+
import { _templateStrings } from "../db/templateStrings.ts";
|
|
5
6
|
|
|
6
7
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
7
8
|
|
|
@@ -16,7 +17,7 @@ async function ddl(sql: string): Promise<void> {
|
|
|
16
17
|
// migration runner's `begin()` wrapper decorative.
|
|
17
18
|
const conn = _getScopedDbConnection();
|
|
18
19
|
const strings = [sql];
|
|
19
|
-
const tpl =
|
|
20
|
+
const tpl = _templateStrings(strings);
|
|
20
21
|
await conn(tpl);
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -29,7 +30,7 @@ async function query<T = Record<string, unknown>>(sql: string, params: unknown[]
|
|
|
29
30
|
// the tables that transaction has created, which a pooled connection cannot.
|
|
30
31
|
const conn = _getScopedDbConnection();
|
|
31
32
|
const parts = sql.split("?");
|
|
32
|
-
const tpl =
|
|
33
|
+
const tpl = _templateStrings(parts);
|
|
33
34
|
return conn<T>(tpl, ...params);
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _getDbConnection } from "../db/DB.ts";
|
|
2
2
|
import { _getDialect } from "../model/BaseModel.ts";
|
|
3
|
+
import { _templateStrings } from "../db/templateStrings.ts";
|
|
3
4
|
|
|
4
5
|
// ── DB column descriptor (normalised across dialects) ─────────────────────────
|
|
5
6
|
|
|
@@ -23,7 +24,7 @@ export interface LiveTable {
|
|
|
23
24
|
async function queryRaw<T>(sql: string): Promise<T[]> {
|
|
24
25
|
const conn = _getDbConnection();
|
|
25
26
|
const strings = [sql];
|
|
26
|
-
const tpl =
|
|
27
|
+
const tpl = _templateStrings(strings);
|
|
27
28
|
return conn<T>(tpl);
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -31,7 +32,7 @@ async function queryRaw<T>(sql: string): Promise<T[]> {
|
|
|
31
32
|
async function queryParam<T>(sql: string, value: unknown): Promise<T[]> {
|
|
32
33
|
const conn = _getDbConnection();
|
|
33
34
|
const parts = sql.split("?");
|
|
34
|
-
const tpl =
|
|
35
|
+
const tpl = _templateStrings(parts);
|
|
35
36
|
return conn<T>(tpl, value);
|
|
36
37
|
}
|
|
37
38
|
|