@c9up/atlas 0.2.0 → 0.2.1
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/README.md +8 -0
- package/db.darwin-arm64.node +0 -0
- package/db.darwin-x64.node +0 -0
- package/db.linux-arm64-gnu.node +0 -0
- package/db.linux-x64-gnu.node +0 -0
- package/db.win32-x64-msvc.node +0 -0
- package/dist/AtlasProvider.d.ts +60 -7
- package/dist/AtlasProvider.d.ts.map +1 -1
- package/dist/AtlasProvider.js +204 -38
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/BaseEntity.d.ts +17 -0
- package/dist/BaseEntity.d.ts.map +1 -1
- package/dist/BaseEntity.js.map +1 -1
- package/dist/BaseModel.d.ts +63 -27
- package/dist/BaseModel.d.ts.map +1 -1
- package/dist/BaseModel.js +108 -51
- package/dist/BaseModel.js.map +1 -1
- package/dist/BaseRepository.d.ts +11 -1
- package/dist/BaseRepository.d.ts.map +1 -1
- package/dist/BaseRepository.js +95 -20
- package/dist/BaseRepository.js.map +1 -1
- package/dist/ConnectionManager.d.ts +106 -0
- package/dist/ConnectionManager.d.ts.map +1 -0
- package/dist/ConnectionManager.js +228 -0
- package/dist/ConnectionManager.js.map +1 -0
- package/dist/ModelQuery.d.ts +256 -43
- package/dist/ModelQuery.d.ts.map +1 -1
- package/dist/ModelQuery.js +899 -231
- package/dist/ModelQuery.js.map +1 -1
- package/dist/Transaction.d.ts +54 -1
- package/dist/Transaction.d.ts.map +1 -1
- package/dist/Transaction.js +121 -59
- package/dist/Transaction.js.map +1 -1
- package/dist/adapters/NapiDbAdapter.d.ts +11 -0
- package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
- package/dist/adapters/NapiDbAdapter.js +35 -7
- package/dist/adapters/NapiDbAdapter.js.map +1 -1
- package/dist/console/contract.d.ts +62 -0
- package/dist/console/contract.d.ts.map +1 -0
- package/dist/console/contract.js +38 -0
- package/dist/console/contract.js.map +1 -0
- package/dist/console/factoryCommands.d.ts +23 -0
- package/dist/console/factoryCommands.d.ts.map +1 -0
- package/dist/console/factoryCommands.js +62 -0
- package/dist/console/factoryCommands.js.map +1 -0
- package/dist/console/migrationCommands.d.ts +38 -8
- package/dist/console/migrationCommands.d.ts.map +1 -1
- package/dist/console/migrationCommands.js +157 -50
- package/dist/console/migrationCommands.js.map +1 -1
- package/dist/console/schemaCheckCommand.d.ts +11 -17
- package/dist/console/schemaCheckCommand.d.ts.map +1 -1
- package/dist/console/schemaCheckCommand.js +21 -15
- package/dist/console/schemaCheckCommand.js.map +1 -1
- package/dist/console/schemaDumpCommand.d.ts +30 -0
- package/dist/console/schemaDumpCommand.d.ts.map +1 -0
- package/dist/console/schemaDumpCommand.js +69 -0
- package/dist/console/schemaDumpCommand.js.map +1 -0
- package/dist/console/schemaGenerateCommand.d.ts +100 -0
- package/dist/console/schemaGenerateCommand.d.ts.map +1 -0
- package/dist/console/schemaGenerateCommand.js +246 -0
- package/dist/console/schemaGenerateCommand.js.map +1 -0
- package/dist/console/seederCommands.d.ts +46 -0
- package/dist/console/seederCommands.d.ts.map +1 -0
- package/dist/console/seederCommands.js +136 -0
- package/dist/console/seederCommands.js.map +1 -0
- package/dist/decorators/entity.d.ts +13 -2
- package/dist/decorators/entity.d.ts.map +1 -1
- package/dist/decorators/entity.js +10 -0
- package/dist/decorators/entity.js.map +1 -1
- package/dist/decorators/hooks.d.ts +3 -3
- package/dist/decorators/hooks.d.ts.map +1 -1
- package/dist/decorators/hooks.js.map +1 -1
- package/dist/events.d.ts +5 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/factories.d.ts +8 -0
- package/dist/factories.d.ts.map +1 -0
- package/dist/factories.js +8 -0
- package/dist/factories.js.map +1 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/lucid-schema.d.ts +15 -0
- package/dist/lucid-schema.d.ts.map +1 -0
- package/dist/lucid-schema.js +16 -0
- package/dist/lucid-schema.js.map +1 -0
- package/dist/orm.d.ts +14 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/orm.js +14 -0
- package/dist/orm.js.map +1 -0
- package/dist/query/DatabaseQueryBuilder.d.ts +557 -0
- package/dist/query/DatabaseQueryBuilder.d.ts.map +1 -0
- package/dist/query/DatabaseQueryBuilder.js +1798 -0
- package/dist/query/DatabaseQueryBuilder.js.map +1 -0
- package/dist/query/DmlBuilder.d.ts +62 -0
- package/dist/query/DmlBuilder.d.ts.map +1 -0
- package/dist/query/DmlBuilder.js +84 -0
- package/dist/query/DmlBuilder.js.map +1 -0
- package/dist/query/QueryBuilder.d.ts +5 -0
- package/dist/query/QueryBuilder.d.ts.map +1 -1
- package/dist/query/QueryBuilder.js +7 -0
- package/dist/query/QueryBuilder.js.map +1 -1
- package/dist/query/RawQueryBuilder.d.ts +47 -0
- package/dist/query/RawQueryBuilder.d.ts.map +1 -0
- package/dist/query/RawQueryBuilder.js +138 -0
- package/dist/query/RawQueryBuilder.js.map +1 -0
- package/dist/query/interpolate.d.ts +45 -0
- package/dist/query/interpolate.d.ts.map +1 -0
- package/dist/query/interpolate.js +51 -0
- package/dist/query/interpolate.js.map +1 -0
- package/dist/query/native.d.ts +8 -0
- package/dist/query/native.d.ts.map +1 -1
- package/dist/query/native.js +14 -0
- package/dist/query/native.js.map +1 -1
- package/dist/query/operators.d.ts +4 -0
- package/dist/query/operators.d.ts.map +1 -0
- package/dist/query/operators.js +27 -0
- package/dist/query/operators.js.map +1 -0
- package/dist/schema/Migration.d.ts +31 -7
- package/dist/schema/Migration.d.ts.map +1 -1
- package/dist/schema/Migration.js +34 -7
- package/dist/schema/Migration.js.map +1 -1
- package/dist/schema/MigrationRunner.d.ts +56 -6
- package/dist/schema/MigrationRunner.d.ts.map +1 -1
- package/dist/schema/MigrationRunner.js +382 -112
- package/dist/schema/MigrationRunner.js.map +1 -1
- package/dist/schema/Schema.d.ts +59 -2
- package/dist/schema/Schema.d.ts.map +1 -1
- package/dist/schema/Schema.js +143 -9
- package/dist/schema/Schema.js.map +1 -1
- package/dist/schema/SchemaDumper.d.ts +121 -0
- package/dist/schema/SchemaDumper.d.ts.map +1 -0
- package/dist/schema/SchemaDumper.js +365 -0
- package/dist/schema/SchemaDumper.js.map +1 -0
- package/dist/schema/Seeder.d.ts +13 -0
- package/dist/schema/Seeder.d.ts.map +1 -1
- package/dist/schema/Seeder.js +28 -5
- package/dist/schema/Seeder.js.map +1 -1
- package/dist/schema/TableBuilder.d.ts +27 -8
- package/dist/schema/TableBuilder.d.ts.map +1 -1
- package/dist/schema/TableBuilder.js +50 -12
- package/dist/schema/TableBuilder.js.map +1 -1
- package/dist/schema/catalog.d.ts +25 -1
- package/dist/schema/catalog.d.ts.map +1 -1
- package/dist/schema/catalog.js +69 -22
- package/dist/schema/catalog.js.map +1 -1
- package/dist/schema/introspect.d.ts +1 -1
- package/dist/schema/introspect.d.ts.map +1 -1
- package/dist/schema/introspect.js +26 -11
- package/dist/schema/introspect.js.map +1 -1
- package/dist/seeders.d.ts +7 -0
- package/dist/seeders.d.ts.map +1 -0
- package/dist/seeders.js +7 -0
- package/dist/seeders.js.map +1 -0
- package/dist/services/db.d.ts +120 -9
- package/dist/services/db.d.ts.map +1 -1
- package/dist/services/db.js +182 -26
- package/dist/services/db.js.map +1 -1
- package/dist/testing/DatabaseCleanup.d.ts +29 -6
- package/dist/testing/DatabaseCleanup.d.ts.map +1 -1
- package/dist/testing/DatabaseCleanup.js +44 -17
- package/dist/testing/DatabaseCleanup.js.map +1 -1
- package/dist/testing/DbAssertions.d.ts +48 -0
- package/dist/testing/DbAssertions.d.ts.map +1 -0
- package/dist/testing/DbAssertions.js +80 -0
- package/dist/testing/DbAssertions.js.map +1 -0
- package/dist/testing/Factory.d.ts +111 -25
- package/dist/testing/Factory.d.ts.map +1 -1
- package/dist/testing/Factory.js +331 -56
- package/dist/testing/Factory.js.map +1 -1
- package/dist/testing/TestUtils.d.ts +49 -0
- package/dist/testing/TestUtils.d.ts.map +1 -0
- package/dist/testing/TestUtils.js +79 -0
- package/dist/testing/TestUtils.js.map +1 -0
- package/dist/testing/index.d.ts +3 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types/relations.d.ts +31 -0
- package/dist/types/relations.d.ts.map +1 -0
- package/dist/types/relations.js +20 -0
- package/dist/types/relations.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +30 -2
- package/src/AtlasProvider.ts +297 -51
- package/src/BaseEntity.ts +29 -0
- package/src/BaseModel.ts +174 -37
- package/src/BaseRepository.ts +136 -17
- package/src/ConnectionManager.ts +298 -0
- package/src/ModelQuery.ts +1309 -260
- package/src/Transaction.ts +194 -60
- package/src/adapters/NapiDbAdapter.ts +72 -14
- package/src/console/contract.ts +96 -0
- package/src/console/factoryCommands.ts +73 -0
- package/src/console/migrationCommands.ts +253 -63
- package/src/console/schemaCheckCommand.ts +26 -22
- package/src/console/schemaDumpCommand.ts +96 -0
- package/src/console/schemaGenerateCommand.ts +380 -0
- package/src/console/seederCommands.ts +174 -0
- package/src/decorators/entity.ts +18 -2
- package/src/decorators/hooks.ts +5 -3
- package/src/events.ts +5 -0
- package/src/factories.ts +10 -0
- package/src/index.ts +50 -3
- package/src/lucid-schema.ts +22 -0
- package/src/orm.ts +13 -0
- package/src/query/DatabaseQueryBuilder.ts +2724 -0
- package/src/query/DmlBuilder.ts +131 -0
- package/src/query/QueryBuilder.ts +8 -0
- package/src/query/RawQueryBuilder.ts +173 -0
- package/src/query/interpolate.ts +73 -0
- package/src/query/native.ts +29 -0
- package/src/query/operators.ts +27 -0
- package/src/schema/Migration.ts +36 -7
- package/src/schema/MigrationRunner.ts +483 -119
- package/src/schema/Schema.ts +168 -19
- package/src/schema/SchemaDumper.ts +518 -0
- package/src/schema/Seeder.ts +47 -10
- package/src/schema/TableBuilder.ts +48 -11
- package/src/schema/catalog.ts +94 -24
- package/src/schema/introspect.ts +26 -9
- package/src/seeders.ts +16 -0
- package/src/services/db.ts +343 -31
- package/src/testing/DatabaseCleanup.ts +73 -21
- package/src/testing/DbAssertions.ts +127 -0
- package/src/testing/Factory.ts +559 -79
- package/src/testing/TestUtils.ts +123 -0
- package/src/testing/index.ts +12 -1
- package/src/types/relations.ts +43 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy, chainable, inspectable DML builder — Lucid `insert()`/`update()`/
|
|
3
|
+
* `delete()` return a builder that only runs on `await`/`.exec()`. This lets the
|
|
4
|
+
* Lucid-documented order work:
|
|
5
|
+
*
|
|
6
|
+
* db.table('users').insert(data).onConflict('email').merge()
|
|
7
|
+
* db.from('users').where(...).update(data).returning('id')
|
|
8
|
+
* db.table('users').insert(data).toSQL()
|
|
9
|
+
* db.from('users').where(...).delete().timeout(1000)
|
|
10
|
+
*
|
|
11
|
+
* The clause methods delegate back to the owning {@link DatabaseQueryBuilder}
|
|
12
|
+
* (via `hooks`), so the reverse (atlas) order `onConflict(...).insert(...)` keeps
|
|
13
|
+
* working too — both mutate the same state, read when the query finally compiles.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
type CompiledStatement,
|
|
18
|
+
compiledStatement,
|
|
19
|
+
interpolateQuery,
|
|
20
|
+
} from "./interpolate.js";
|
|
21
|
+
|
|
22
|
+
/** The chainable DML clauses the builder forwards to its owner. */
|
|
23
|
+
export interface DmlChainHooks {
|
|
24
|
+
onConflict(...columns: Array<string | string[]>): void;
|
|
25
|
+
merge(...args: Array<string | string[] | Record<string, unknown>>): void;
|
|
26
|
+
ignore(): void;
|
|
27
|
+
returning(...columns: Array<string | string[]>): void;
|
|
28
|
+
timeout(ms?: number, options?: { cancel?: boolean }): void;
|
|
29
|
+
comment(text: string): void;
|
|
30
|
+
debug(enabled?: boolean): void;
|
|
31
|
+
reporterData(data: Record<string, unknown>): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class DmlBuilder<R> implements PromiseLike<R> {
|
|
35
|
+
readonly #run: () => Promise<R>;
|
|
36
|
+
readonly #compile: () => {
|
|
37
|
+
sql: string;
|
|
38
|
+
bindings: unknown[];
|
|
39
|
+
params: unknown[];
|
|
40
|
+
};
|
|
41
|
+
readonly #hooks: DmlChainHooks;
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
run: () => Promise<R>,
|
|
45
|
+
compile: () => { sql: string; bindings: unknown[]; params: unknown[] },
|
|
46
|
+
hooks: DmlChainHooks,
|
|
47
|
+
) {
|
|
48
|
+
this.#run = run;
|
|
49
|
+
this.#compile = compile;
|
|
50
|
+
this.#hooks = hooks;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The compiled statement without executing (Lucid `toSQL`; `.toNative()` for native form). */
|
|
54
|
+
toSQL(): CompiledStatement {
|
|
55
|
+
const c = this.#compile();
|
|
56
|
+
return compiledStatement(c.sql, c.params);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** SQL with bindings substituted as literals, for inspection (Lucid `toQuery`). */
|
|
60
|
+
toQuery(): string {
|
|
61
|
+
const c = this.#compile();
|
|
62
|
+
return interpolateQuery(c.sql, c.params);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Conflict target for an upsert (Lucid `insert(...).onConflict(...)`). */
|
|
66
|
+
onConflict(...columns: Array<string | string[]>): this {
|
|
67
|
+
this.#hooks.onConflict(...columns);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** On conflict, UPDATE columns / custom values (Lucid `merge`). */
|
|
72
|
+
merge(...args: Array<string | string[] | Record<string, unknown>>): this {
|
|
73
|
+
this.#hooks.merge(...args);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** On conflict, do nothing (Lucid `ignore`). */
|
|
78
|
+
ignore(): this {
|
|
79
|
+
this.#hooks.ignore();
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Columns to return (Lucid `insert(...).returning(...)`). */
|
|
84
|
+
returning(...columns: Array<string | string[]>): this {
|
|
85
|
+
this.#hooks.returning(...columns);
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Caller-facing statement timeout (Lucid `delete().timeout(ms, { cancel })`). */
|
|
90
|
+
timeout(ms?: number, options?: { cancel?: boolean }): this {
|
|
91
|
+
this.#hooks.timeout(ms, options);
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Prefix a `/* … */` SQL comment (Lucid `comment`). */
|
|
96
|
+
comment(text: string): this {
|
|
97
|
+
this.#hooks.comment(text);
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Log the compiled SQL + bindings on the next run (Lucid `debug`). */
|
|
102
|
+
debug(enabled = true): this {
|
|
103
|
+
this.#hooks.debug(enabled);
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Attach metadata to this statement's `db:query` event (Lucid `reporterData`). */
|
|
108
|
+
reporterData(data: Record<string, unknown>): this {
|
|
109
|
+
this.#hooks.reporterData(data);
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Run the statement and resolve to its result. */
|
|
114
|
+
exec(): Promise<R> {
|
|
115
|
+
return this.#run();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// biome-ignore lint/suspicious/noThenProperty: Lucid DML builders are awaitable by design — `await db.table(t).insert(...)` must resolve to the result; the thenable is the intended public API.
|
|
119
|
+
then<TResult1 = R, TResult2 = never>(
|
|
120
|
+
onfulfilled?:
|
|
121
|
+
| ((value: R) => TResult1 | PromiseLike<TResult1>)
|
|
122
|
+
| undefined
|
|
123
|
+
| null,
|
|
124
|
+
onrejected?:
|
|
125
|
+
| ((reason: unknown) => TResult2 | PromiseLike<TResult2>)
|
|
126
|
+
| undefined
|
|
127
|
+
| null,
|
|
128
|
+
): PromiseLike<TResult1 | TResult2> {
|
|
129
|
+
return this.#run().then(onfulfilled, onrejected);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -83,6 +83,14 @@ export class RawSql {
|
|
|
83
83
|
this.params = params;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Wrap the fragment with a prefix/suffix — e.g. parentheses (Lucid/Knex raw
|
|
88
|
+
* `.wrap('(', ')')`). Immutable: returns a new `RawSql` with the same params.
|
|
89
|
+
*/
|
|
90
|
+
wrap(before: string, after: string): RawSql {
|
|
91
|
+
return new RawSql(`${before}${this.sql}${after}`, this.params);
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
/** Tagged template literal for raw SQL with automatic parameterization. */
|
|
87
95
|
static sql(strings: TemplateStringsArray, ...values: unknown[]): RawSql {
|
|
88
96
|
let sql = "";
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { QueryExecutor } from "./DatabaseQueryBuilder.js";
|
|
2
|
+
import type { AtlasDialect } from "./native.js";
|
|
3
|
+
|
|
4
|
+
/** Dialect-quote a possibly dotted identifier (`users.id` → `"users"."id"`). */
|
|
5
|
+
function quoteIdent(ident: string, dialect: AtlasDialect): string {
|
|
6
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(ident)) {
|
|
7
|
+
throw new Error(`Invalid identifier binding '${ident}'.`);
|
|
8
|
+
}
|
|
9
|
+
const q = dialect === "mysql" ? "`" : '"';
|
|
10
|
+
return ident
|
|
11
|
+
.split(".")
|
|
12
|
+
.map((s) => `${q}${s}${q}`)
|
|
13
|
+
.join(".");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve Lucid/Knex raw bindings into positional `?` SQL + ordered params.
|
|
18
|
+
*
|
|
19
|
+
* Positional (array bindings): `?` binds a value, `??` inlines a quoted
|
|
20
|
+
* identifier. Named (object bindings): `:name` binds a value, `:name:` inlines a
|
|
21
|
+
* quoted identifier. Identifiers are quoted for the dialect; values become `?`
|
|
22
|
+
* placeholders bound in occurrence order.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveRawBindings(
|
|
25
|
+
sql: string,
|
|
26
|
+
bindings: unknown[] | Record<string, unknown>,
|
|
27
|
+
dialect: AtlasDialect,
|
|
28
|
+
): { sql: string; params: unknown[] } {
|
|
29
|
+
const params: unknown[] = [];
|
|
30
|
+
if (Array.isArray(bindings)) {
|
|
31
|
+
let i = 0;
|
|
32
|
+
const out = sql.replace(/\?\?|\?/g, (m) => {
|
|
33
|
+
const v = bindings[i++];
|
|
34
|
+
if (m === "??") return quoteIdent(String(v), dialect);
|
|
35
|
+
params.push(v);
|
|
36
|
+
return "?";
|
|
37
|
+
});
|
|
38
|
+
return { sql: out, params };
|
|
39
|
+
}
|
|
40
|
+
// `::` (Postgres cast) is matched first and passed through untouched, so
|
|
41
|
+
// `:payload::jsonb` reads as the value binding `:payload` + the cast `::jsonb`
|
|
42
|
+
// rather than an `:payload:` identifier. `:name:` only matches when its
|
|
43
|
+
// trailing colon is NOT part of a `::` cast (`(?!:)`).
|
|
44
|
+
const out = sql.replace(
|
|
45
|
+
/::|:(\w+):(?!:)|:(\w+)/g,
|
|
46
|
+
(m: string, ident?: string, name?: string) => {
|
|
47
|
+
if (m === "::") return "::";
|
|
48
|
+
if (ident !== undefined) {
|
|
49
|
+
return quoteIdent(String(bindings[ident]), dialect);
|
|
50
|
+
}
|
|
51
|
+
if (name !== undefined) {
|
|
52
|
+
params.push(bindings[name]);
|
|
53
|
+
return "?";
|
|
54
|
+
}
|
|
55
|
+
return m;
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
return { sql: out, params };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Chainable raw query — Lucid `db.rawQuery(sql, bindings)`. Unlike a raw
|
|
63
|
+
* fragment (`db.raw`), this executes independently and exposes the standard
|
|
64
|
+
* query surface: `toSQL()`, `toQuery()`, `debug()`, `timeout()`,
|
|
65
|
+
* `reporterData()`. Thenable, so it can be awaited directly.
|
|
66
|
+
*/
|
|
67
|
+
export class RawQueryBuilder<T = Record<string, unknown>>
|
|
68
|
+
implements PromiseLike<T[]>
|
|
69
|
+
{
|
|
70
|
+
readonly #exec: QueryExecutor;
|
|
71
|
+
readonly #dialect: AtlasDialect;
|
|
72
|
+
readonly #rawSql: string;
|
|
73
|
+
readonly #rawBindings: unknown[] | Record<string, unknown>;
|
|
74
|
+
#timeoutMs?: number;
|
|
75
|
+
#debugFlag = false;
|
|
76
|
+
#reporter?: Record<string, unknown>;
|
|
77
|
+
|
|
78
|
+
constructor(
|
|
79
|
+
exec: QueryExecutor,
|
|
80
|
+
dialect: AtlasDialect,
|
|
81
|
+
sql: string,
|
|
82
|
+
bindings: unknown[] | Record<string, unknown> = [],
|
|
83
|
+
) {
|
|
84
|
+
this.#exec = exec;
|
|
85
|
+
this.#dialect = dialect;
|
|
86
|
+
this.#rawSql = sql;
|
|
87
|
+
this.#rawBindings = bindings;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** `{ sql, bindings }` with named/identifier bindings resolved (Lucid `toSQL`). */
|
|
91
|
+
toSQL(): { sql: string; bindings: unknown[] } {
|
|
92
|
+
const { sql, params } = resolveRawBindings(
|
|
93
|
+
this.#rawSql,
|
|
94
|
+
this.#rawBindings,
|
|
95
|
+
this.#dialect,
|
|
96
|
+
);
|
|
97
|
+
return { sql, bindings: params };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The SQL with bindings substituted for display/debug (Lucid `toQuery`). */
|
|
101
|
+
toQuery(): string {
|
|
102
|
+
const { sql, bindings } = this.toSQL();
|
|
103
|
+
let i = 0;
|
|
104
|
+
return sql.replace(/\?/g, () => {
|
|
105
|
+
const v = bindings[i++];
|
|
106
|
+
if (v === null || v === undefined) return "NULL";
|
|
107
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
108
|
+
return `'${String(v).replace(/'/g, "''")}'`;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Enable debug logging of the compiled query on execution (Lucid `debug`). */
|
|
113
|
+
debug(value = true): this {
|
|
114
|
+
this.#debugFlag = value;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Caller-facing statement timeout (Lucid `timeout(ms, options)`). Matches
|
|
120
|
+
* Lucid's DEFAULT non-cancelling timeout — the awaiter rejects after `ms`.
|
|
121
|
+
* `{ cancel: true }` server-side cancellation is not wired at this layer.
|
|
122
|
+
*/
|
|
123
|
+
timeout(ms?: number, _options?: { cancel?: boolean }): this {
|
|
124
|
+
this.#timeoutMs = ms;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Attach metadata to the query (Lucid `reporterData`). */
|
|
129
|
+
reporterData(data: Record<string, unknown>): this {
|
|
130
|
+
this.#reporter = data;
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Execute and return the rows. */
|
|
135
|
+
async exec(): Promise<T[]> {
|
|
136
|
+
const { sql, bindings } = this.toSQL();
|
|
137
|
+
// Carry `debug`/`reporterData` in the query meta so the `db:query` observer
|
|
138
|
+
// fires exactly like Lucid — not a bare console.debug.
|
|
139
|
+
const meta: { debug?: boolean; reporterData?: Record<string, unknown> } =
|
|
140
|
+
{};
|
|
141
|
+
if (this.#debugFlag) meta.debug = true;
|
|
142
|
+
if (this.#reporter) meta.reporterData = this.#reporter;
|
|
143
|
+
const work = this.#exec.query<T>(
|
|
144
|
+
sql,
|
|
145
|
+
bindings,
|
|
146
|
+
Object.keys(meta).length > 0 ? meta : undefined,
|
|
147
|
+
);
|
|
148
|
+
return this.#race(work);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// biome-ignore lint/suspicious/noThenProperty: Lucid raw queries are awaitable by design — `await db.rawQuery(...)` must resolve to the rows; the thenable is the intended public API, matching the other builders.
|
|
152
|
+
then<R1 = T[], R2 = never>(
|
|
153
|
+
onfulfilled?: ((value: T[]) => R1 | PromiseLike<R1>) | undefined | null,
|
|
154
|
+
onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | undefined | null,
|
|
155
|
+
): PromiseLike<R1 | R2> {
|
|
156
|
+
return this.exec().then(onfulfilled, onrejected);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Race the query against the configured `.timeout(ms)`. */
|
|
160
|
+
#race(work: Promise<T[]>): Promise<T[]> {
|
|
161
|
+
const ms = this.#timeoutMs;
|
|
162
|
+
if (!ms || ms <= 0) return work;
|
|
163
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
164
|
+
const guard = new Promise<never>((_, reject) => {
|
|
165
|
+
timer = setTimeout(
|
|
166
|
+
() => reject(new Error(`Query timed out after ${ms}ms`)),
|
|
167
|
+
ms,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
work.catch(() => {});
|
|
171
|
+
return Promise.race([work, guard]).finally(() => clearTimeout(timer));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query-inspection helpers shared by every builder's `toQuery()` / `toSQL()`.
|
|
3
|
+
*
|
|
4
|
+
* `toQuery()` returns the SQL with bindings substituted as literals (Adonis
|
|
5
|
+
* Lucid semantics — `where "is_active" = 1`), for ad-hoc inspection ONLY. It is
|
|
6
|
+
* NOT injection-safe; forward `toSQL()` + bindings to logs instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** SQL-literal render of a single binding (inspection only — not injection-safe). */
|
|
10
|
+
export function literalEscape(v: unknown): string {
|
|
11
|
+
if (v === null || v === undefined) return "NULL";
|
|
12
|
+
if (typeof v === "number") return String(v);
|
|
13
|
+
if (typeof v === "boolean") return v ? "1" : "0";
|
|
14
|
+
if (v instanceof Date) return `'${v.toISOString()}'`;
|
|
15
|
+
// Strings — escape single quotes per SQL. NOT hardened against `\'`.
|
|
16
|
+
return `'${String(v).replace(/'/g, "''")}'`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Substitute native placeholders (`?` sequentially, `$N` by 1-based index) with
|
|
21
|
+
* dialect-safe literals — Lucid `toQuery`. `$N` is resolved by its own index so
|
|
22
|
+
* a reused/reordered Postgres placeholder still maps to the right binding.
|
|
23
|
+
*/
|
|
24
|
+
export function interpolateQuery(
|
|
25
|
+
sql: string,
|
|
26
|
+
params: readonly unknown[],
|
|
27
|
+
): string {
|
|
28
|
+
let seq = 0;
|
|
29
|
+
return sql.replace(/\?|\$\d+/g, (tok) =>
|
|
30
|
+
literalEscape(params[tok[0] === "$" ? Number(tok.slice(1)) - 1 : seq++]),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Normalize dialect-native positional placeholders (`$1`, `$2`, … — Postgres) to
|
|
36
|
+
* Knex-style `?`, matching what Lucid's `toSQL().sql` returns for every dialect.
|
|
37
|
+
* MySQL/SQLite already emit `?`, so this is a no-op there. Display concern only:
|
|
38
|
+
* atlas executes with the native statement (see `toNative`).
|
|
39
|
+
*/
|
|
40
|
+
export function toQuestionMarks(sql: string): string {
|
|
41
|
+
return sql.replace(/\$\d+/g, "?");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The compiled statement Lucid's `toSQL()` returns: `{ sql, bindings }` with `?`
|
|
46
|
+
* placeholders (Knex-normalized, same on every dialect), plus a `toNative()` that
|
|
47
|
+
* yields the dialect-native `{ sql, bindings }` (Postgres `$N`) that atlas
|
|
48
|
+
* actually executes. `params` is atlas's historical alias of `bindings` (same
|
|
49
|
+
* array), kept so either name ports.
|
|
50
|
+
*/
|
|
51
|
+
export interface CompiledStatement {
|
|
52
|
+
sql: string;
|
|
53
|
+
bindings: unknown[];
|
|
54
|
+
params: unknown[];
|
|
55
|
+
/** Dialect-native `{ sql, bindings }` (Lucid `toSQL().toNative()`). */
|
|
56
|
+
toNative(): { sql: string; bindings: unknown[] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wrap a NATIVE compiled `sql` + `params` as a Lucid-shaped {@link CompiledStatement}:
|
|
61
|
+
* the public `.sql` is `?`-normalized, `.toNative()` yields the native form.
|
|
62
|
+
*/
|
|
63
|
+
export function compiledStatement(
|
|
64
|
+
nativeSql: string,
|
|
65
|
+
params: unknown[],
|
|
66
|
+
): CompiledStatement {
|
|
67
|
+
return {
|
|
68
|
+
sql: toQuestionMarks(nativeSql),
|
|
69
|
+
bindings: params,
|
|
70
|
+
params,
|
|
71
|
+
toNative: () => ({ sql: nativeSql, bindings: params }),
|
|
72
|
+
};
|
|
73
|
+
}
|
package/src/query/native.ts
CHANGED
|
@@ -40,6 +40,35 @@ try {
|
|
|
40
40
|
|
|
41
41
|
export type AtlasDialect = "sqlite" | "postgres" | "mysql";
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Dialect names accepted by `ifDialect`/`unlessDialect`, including the Lucid/Knex
|
|
45
|
+
* client aliases — so a callback ported from Lucid (`ifDialect('sqlite3', …)`,
|
|
46
|
+
* which Lucid names `sqlite3`/`better-sqlite3`, not `sqlite`) still matches.
|
|
47
|
+
*/
|
|
48
|
+
export type DialectName =
|
|
49
|
+
| AtlasDialect
|
|
50
|
+
| "sqlite3"
|
|
51
|
+
| "better-sqlite3"
|
|
52
|
+
| "postgresql"
|
|
53
|
+
| "pg"
|
|
54
|
+
| "mysql2";
|
|
55
|
+
|
|
56
|
+
const DIALECT_ALIASES: Record<string, AtlasDialect> = {
|
|
57
|
+
sqlite: "sqlite",
|
|
58
|
+
sqlite3: "sqlite",
|
|
59
|
+
"better-sqlite3": "sqlite",
|
|
60
|
+
postgres: "postgres",
|
|
61
|
+
postgresql: "postgres",
|
|
62
|
+
pg: "postgres",
|
|
63
|
+
mysql: "mysql",
|
|
64
|
+
mysql2: "mysql",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Map a Lucid/Knex dialect name to the atlas dialect, or `undefined` if unknown. */
|
|
68
|
+
export function normalizeDialect(name: string): AtlasDialect | undefined {
|
|
69
|
+
return DIALECT_ALIASES[name];
|
|
70
|
+
}
|
|
71
|
+
|
|
43
72
|
/** Module-level default dialect. Set by AtlasProvider at boot. */
|
|
44
73
|
let defaultDialect: AtlasDialect = "sqlite";
|
|
45
74
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** SQL operator helpers shared by the query builders. */
|
|
2
|
+
|
|
3
|
+
const NEGATION: Record<string, string> = {
|
|
4
|
+
"=": "!=",
|
|
5
|
+
"!=": "=",
|
|
6
|
+
"<>": "=",
|
|
7
|
+
"<": ">=",
|
|
8
|
+
">": "<=",
|
|
9
|
+
"<=": ">",
|
|
10
|
+
">=": "<",
|
|
11
|
+
IN: "NOT IN",
|
|
12
|
+
"NOT IN": "IN",
|
|
13
|
+
LIKE: "NOT LIKE",
|
|
14
|
+
"NOT LIKE": "LIKE",
|
|
15
|
+
ILIKE: "NOT ILIKE",
|
|
16
|
+
BETWEEN: "NOT BETWEEN",
|
|
17
|
+
"NOT BETWEEN": "BETWEEN",
|
|
18
|
+
"IS NULL": "IS NOT NULL",
|
|
19
|
+
"IS NOT NULL": "IS NULL",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** The logical negation of a comparison operator (`>=` → `<`, `=` → `!=`, …). */
|
|
23
|
+
export function negateOperator(op: string): string {
|
|
24
|
+
const negated = NEGATION[op.toUpperCase()] ?? NEGATION[op];
|
|
25
|
+
if (!negated) throw new Error(`whereNot: unsupported operator '${op}'`);
|
|
26
|
+
return negated;
|
|
27
|
+
}
|
package/src/schema/Migration.ts
CHANGED
|
@@ -39,7 +39,22 @@ export type DeferredMigrationCallback = (
|
|
|
39
39
|
) => Promise<void> | void;
|
|
40
40
|
|
|
41
41
|
export abstract class Migration {
|
|
42
|
+
/**
|
|
43
|
+
* Adonis Lucid `static disableTransactions`. Set `static disableTransactions =
|
|
44
|
+
* true` on a migration to run it OUTSIDE a transaction — for DDL that cannot
|
|
45
|
+
* run inside one (e.g. Postgres `CREATE INDEX CONCURRENTLY`). Applies to both
|
|
46
|
+
* `up()` and `down()`; the runner then executes the migration non-atomically
|
|
47
|
+
* by design (so `this.defer()` runs loose too). Defaults to `false`.
|
|
48
|
+
*/
|
|
49
|
+
static disableTransactions = false;
|
|
50
|
+
|
|
42
51
|
readonly schema: Schema;
|
|
52
|
+
/**
|
|
53
|
+
* Adonis Lucid `this.dryRun` — `true` while the runner generates SQL without
|
|
54
|
+
* executing it (set by `MigrationRunner.dryRun()`). Read it in `up()`/`down()`
|
|
55
|
+
* to conditionally skip side-effectful branches. Defaults to `false`.
|
|
56
|
+
*/
|
|
57
|
+
dryRun = false;
|
|
43
58
|
readonly #dialect: AtlasDialect;
|
|
44
59
|
#deferred: DeferredMigrationCallback[] = [];
|
|
45
60
|
|
|
@@ -52,19 +67,33 @@ export abstract class Migration {
|
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
/**
|
|
55
|
-
* Register a callback to run AFTER this migration's schema statements
|
|
56
|
-
*
|
|
57
|
-
*
|
|
70
|
+
* Register a callback to run AFTER this migration's schema statements (Adonis
|
|
71
|
+
* Lucid `this.defer`) — typically to seed/backfill data into a table the same
|
|
72
|
+
* migration just created. The callback gets a live connection handle and runs
|
|
73
|
+
* in registration order, interleaved with the schema.
|
|
58
74
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* the
|
|
62
|
-
*
|
|
75
|
+
* ATOMICITY (Lucid parity): by default the schema, the deferred callbacks AND
|
|
76
|
+
* the migration's tracking row all run in ONE transaction — a throwing callback
|
|
77
|
+
* rolls the schema back too, so the migration is all-or-nothing and re-runnable
|
|
78
|
+
* (fully atomic on sqlite/postgres; MySQL auto-commits DDL, so only its tracking
|
|
79
|
+
* row is bound to the callbacks). The one exception is a migration with `static
|
|
80
|
+
* disableTransactions = true`: it opts out of the transaction entirely (for
|
|
81
|
+
* txn-incompatible DDL), so there the deferred work runs non-atomically, by
|
|
82
|
+
* design.
|
|
63
83
|
*/
|
|
64
84
|
defer(callback: DeferredMigrationCallback): void {
|
|
65
85
|
this.#deferred.push(callback);
|
|
66
86
|
}
|
|
67
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @internal Effective `static disableTransactions` of the concrete subclass —
|
|
90
|
+
* read by the runner to decide whether to wrap this migration in a transaction.
|
|
91
|
+
*/
|
|
92
|
+
get transactionsDisabled(): boolean {
|
|
93
|
+
const ctor = this.constructor;
|
|
94
|
+
return "disableTransactions" in ctor && ctor.disableTransactions === true;
|
|
95
|
+
}
|
|
96
|
+
|
|
68
97
|
/** @internal Return and clear the deferred callbacks — called by the runner. */
|
|
69
98
|
consumeDeferred(): DeferredMigrationCallback[] {
|
|
70
99
|
const deferred = this.#deferred;
|