@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.
Files changed (234) hide show
  1. package/README.md +8 -0
  2. package/db.darwin-arm64.node +0 -0
  3. package/db.darwin-x64.node +0 -0
  4. package/db.linux-arm64-gnu.node +0 -0
  5. package/db.linux-x64-gnu.node +0 -0
  6. package/db.win32-x64-msvc.node +0 -0
  7. package/dist/AtlasProvider.d.ts +60 -7
  8. package/dist/AtlasProvider.d.ts.map +1 -1
  9. package/dist/AtlasProvider.js +204 -38
  10. package/dist/AtlasProvider.js.map +1 -1
  11. package/dist/BaseEntity.d.ts +17 -0
  12. package/dist/BaseEntity.d.ts.map +1 -1
  13. package/dist/BaseEntity.js.map +1 -1
  14. package/dist/BaseModel.d.ts +63 -27
  15. package/dist/BaseModel.d.ts.map +1 -1
  16. package/dist/BaseModel.js +108 -51
  17. package/dist/BaseModel.js.map +1 -1
  18. package/dist/BaseRepository.d.ts +11 -1
  19. package/dist/BaseRepository.d.ts.map +1 -1
  20. package/dist/BaseRepository.js +95 -20
  21. package/dist/BaseRepository.js.map +1 -1
  22. package/dist/ConnectionManager.d.ts +106 -0
  23. package/dist/ConnectionManager.d.ts.map +1 -0
  24. package/dist/ConnectionManager.js +228 -0
  25. package/dist/ConnectionManager.js.map +1 -0
  26. package/dist/ModelQuery.d.ts +256 -43
  27. package/dist/ModelQuery.d.ts.map +1 -1
  28. package/dist/ModelQuery.js +899 -231
  29. package/dist/ModelQuery.js.map +1 -1
  30. package/dist/Transaction.d.ts +54 -1
  31. package/dist/Transaction.d.ts.map +1 -1
  32. package/dist/Transaction.js +121 -59
  33. package/dist/Transaction.js.map +1 -1
  34. package/dist/adapters/NapiDbAdapter.d.ts +11 -0
  35. package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
  36. package/dist/adapters/NapiDbAdapter.js +35 -7
  37. package/dist/adapters/NapiDbAdapter.js.map +1 -1
  38. package/dist/console/contract.d.ts +62 -0
  39. package/dist/console/contract.d.ts.map +1 -0
  40. package/dist/console/contract.js +38 -0
  41. package/dist/console/contract.js.map +1 -0
  42. package/dist/console/factoryCommands.d.ts +23 -0
  43. package/dist/console/factoryCommands.d.ts.map +1 -0
  44. package/dist/console/factoryCommands.js +62 -0
  45. package/dist/console/factoryCommands.js.map +1 -0
  46. package/dist/console/migrationCommands.d.ts +38 -8
  47. package/dist/console/migrationCommands.d.ts.map +1 -1
  48. package/dist/console/migrationCommands.js +157 -50
  49. package/dist/console/migrationCommands.js.map +1 -1
  50. package/dist/console/schemaCheckCommand.d.ts +11 -17
  51. package/dist/console/schemaCheckCommand.d.ts.map +1 -1
  52. package/dist/console/schemaCheckCommand.js +21 -15
  53. package/dist/console/schemaCheckCommand.js.map +1 -1
  54. package/dist/console/schemaDumpCommand.d.ts +30 -0
  55. package/dist/console/schemaDumpCommand.d.ts.map +1 -0
  56. package/dist/console/schemaDumpCommand.js +69 -0
  57. package/dist/console/schemaDumpCommand.js.map +1 -0
  58. package/dist/console/schemaGenerateCommand.d.ts +100 -0
  59. package/dist/console/schemaGenerateCommand.d.ts.map +1 -0
  60. package/dist/console/schemaGenerateCommand.js +246 -0
  61. package/dist/console/schemaGenerateCommand.js.map +1 -0
  62. package/dist/console/seederCommands.d.ts +46 -0
  63. package/dist/console/seederCommands.d.ts.map +1 -0
  64. package/dist/console/seederCommands.js +136 -0
  65. package/dist/console/seederCommands.js.map +1 -0
  66. package/dist/decorators/entity.d.ts +13 -2
  67. package/dist/decorators/entity.d.ts.map +1 -1
  68. package/dist/decorators/entity.js +10 -0
  69. package/dist/decorators/entity.js.map +1 -1
  70. package/dist/decorators/hooks.d.ts +3 -3
  71. package/dist/decorators/hooks.d.ts.map +1 -1
  72. package/dist/decorators/hooks.js.map +1 -1
  73. package/dist/events.d.ts +5 -0
  74. package/dist/events.d.ts.map +1 -1
  75. package/dist/events.js.map +1 -1
  76. package/dist/factories.d.ts +8 -0
  77. package/dist/factories.d.ts.map +1 -0
  78. package/dist/factories.js +8 -0
  79. package/dist/factories.js.map +1 -0
  80. package/dist/index.d.ts +11 -5
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +12 -4
  83. package/dist/index.js.map +1 -1
  84. package/dist/lucid-schema.d.ts +15 -0
  85. package/dist/lucid-schema.d.ts.map +1 -0
  86. package/dist/lucid-schema.js +16 -0
  87. package/dist/lucid-schema.js.map +1 -0
  88. package/dist/orm.d.ts +14 -0
  89. package/dist/orm.d.ts.map +1 -0
  90. package/dist/orm.js +14 -0
  91. package/dist/orm.js.map +1 -0
  92. package/dist/query/DatabaseQueryBuilder.d.ts +557 -0
  93. package/dist/query/DatabaseQueryBuilder.d.ts.map +1 -0
  94. package/dist/query/DatabaseQueryBuilder.js +1798 -0
  95. package/dist/query/DatabaseQueryBuilder.js.map +1 -0
  96. package/dist/query/DmlBuilder.d.ts +62 -0
  97. package/dist/query/DmlBuilder.d.ts.map +1 -0
  98. package/dist/query/DmlBuilder.js +84 -0
  99. package/dist/query/DmlBuilder.js.map +1 -0
  100. package/dist/query/QueryBuilder.d.ts +5 -0
  101. package/dist/query/QueryBuilder.d.ts.map +1 -1
  102. package/dist/query/QueryBuilder.js +7 -0
  103. package/dist/query/QueryBuilder.js.map +1 -1
  104. package/dist/query/RawQueryBuilder.d.ts +47 -0
  105. package/dist/query/RawQueryBuilder.d.ts.map +1 -0
  106. package/dist/query/RawQueryBuilder.js +138 -0
  107. package/dist/query/RawQueryBuilder.js.map +1 -0
  108. package/dist/query/interpolate.d.ts +45 -0
  109. package/dist/query/interpolate.d.ts.map +1 -0
  110. package/dist/query/interpolate.js +51 -0
  111. package/dist/query/interpolate.js.map +1 -0
  112. package/dist/query/native.d.ts +8 -0
  113. package/dist/query/native.d.ts.map +1 -1
  114. package/dist/query/native.js +14 -0
  115. package/dist/query/native.js.map +1 -1
  116. package/dist/query/operators.d.ts +4 -0
  117. package/dist/query/operators.d.ts.map +1 -0
  118. package/dist/query/operators.js +27 -0
  119. package/dist/query/operators.js.map +1 -0
  120. package/dist/schema/Migration.d.ts +31 -7
  121. package/dist/schema/Migration.d.ts.map +1 -1
  122. package/dist/schema/Migration.js +34 -7
  123. package/dist/schema/Migration.js.map +1 -1
  124. package/dist/schema/MigrationRunner.d.ts +56 -6
  125. package/dist/schema/MigrationRunner.d.ts.map +1 -1
  126. package/dist/schema/MigrationRunner.js +382 -112
  127. package/dist/schema/MigrationRunner.js.map +1 -1
  128. package/dist/schema/Schema.d.ts +59 -2
  129. package/dist/schema/Schema.d.ts.map +1 -1
  130. package/dist/schema/Schema.js +143 -9
  131. package/dist/schema/Schema.js.map +1 -1
  132. package/dist/schema/SchemaDumper.d.ts +121 -0
  133. package/dist/schema/SchemaDumper.d.ts.map +1 -0
  134. package/dist/schema/SchemaDumper.js +365 -0
  135. package/dist/schema/SchemaDumper.js.map +1 -0
  136. package/dist/schema/Seeder.d.ts +13 -0
  137. package/dist/schema/Seeder.d.ts.map +1 -1
  138. package/dist/schema/Seeder.js +28 -5
  139. package/dist/schema/Seeder.js.map +1 -1
  140. package/dist/schema/TableBuilder.d.ts +27 -8
  141. package/dist/schema/TableBuilder.d.ts.map +1 -1
  142. package/dist/schema/TableBuilder.js +50 -12
  143. package/dist/schema/TableBuilder.js.map +1 -1
  144. package/dist/schema/catalog.d.ts +25 -1
  145. package/dist/schema/catalog.d.ts.map +1 -1
  146. package/dist/schema/catalog.js +69 -22
  147. package/dist/schema/catalog.js.map +1 -1
  148. package/dist/schema/introspect.d.ts +1 -1
  149. package/dist/schema/introspect.d.ts.map +1 -1
  150. package/dist/schema/introspect.js +26 -11
  151. package/dist/schema/introspect.js.map +1 -1
  152. package/dist/seeders.d.ts +7 -0
  153. package/dist/seeders.d.ts.map +1 -0
  154. package/dist/seeders.js +7 -0
  155. package/dist/seeders.js.map +1 -0
  156. package/dist/services/db.d.ts +120 -9
  157. package/dist/services/db.d.ts.map +1 -1
  158. package/dist/services/db.js +182 -26
  159. package/dist/services/db.js.map +1 -1
  160. package/dist/testing/DatabaseCleanup.d.ts +29 -6
  161. package/dist/testing/DatabaseCleanup.d.ts.map +1 -1
  162. package/dist/testing/DatabaseCleanup.js +44 -17
  163. package/dist/testing/DatabaseCleanup.js.map +1 -1
  164. package/dist/testing/DbAssertions.d.ts +48 -0
  165. package/dist/testing/DbAssertions.d.ts.map +1 -0
  166. package/dist/testing/DbAssertions.js +80 -0
  167. package/dist/testing/DbAssertions.js.map +1 -0
  168. package/dist/testing/Factory.d.ts +111 -25
  169. package/dist/testing/Factory.d.ts.map +1 -1
  170. package/dist/testing/Factory.js +331 -56
  171. package/dist/testing/Factory.js.map +1 -1
  172. package/dist/testing/TestUtils.d.ts +49 -0
  173. package/dist/testing/TestUtils.d.ts.map +1 -0
  174. package/dist/testing/TestUtils.js +79 -0
  175. package/dist/testing/TestUtils.js.map +1 -0
  176. package/dist/testing/index.d.ts +3 -1
  177. package/dist/testing/index.d.ts.map +1 -1
  178. package/dist/testing/index.js +3 -1
  179. package/dist/testing/index.js.map +1 -1
  180. package/dist/types/relations.d.ts +31 -0
  181. package/dist/types/relations.d.ts.map +1 -0
  182. package/dist/types/relations.js +20 -0
  183. package/dist/types/relations.js.map +1 -0
  184. package/index.darwin-arm64.node +0 -0
  185. package/index.darwin-x64.node +0 -0
  186. package/index.linux-arm64-gnu.node +0 -0
  187. package/index.linux-x64-gnu.node +0 -0
  188. package/index.win32-x64-msvc.node +0 -0
  189. package/package.json +30 -2
  190. package/src/AtlasProvider.ts +297 -51
  191. package/src/BaseEntity.ts +29 -0
  192. package/src/BaseModel.ts +174 -37
  193. package/src/BaseRepository.ts +136 -17
  194. package/src/ConnectionManager.ts +298 -0
  195. package/src/ModelQuery.ts +1309 -260
  196. package/src/Transaction.ts +194 -60
  197. package/src/adapters/NapiDbAdapter.ts +72 -14
  198. package/src/console/contract.ts +96 -0
  199. package/src/console/factoryCommands.ts +73 -0
  200. package/src/console/migrationCommands.ts +253 -63
  201. package/src/console/schemaCheckCommand.ts +26 -22
  202. package/src/console/schemaDumpCommand.ts +96 -0
  203. package/src/console/schemaGenerateCommand.ts +380 -0
  204. package/src/console/seederCommands.ts +174 -0
  205. package/src/decorators/entity.ts +18 -2
  206. package/src/decorators/hooks.ts +5 -3
  207. package/src/events.ts +5 -0
  208. package/src/factories.ts +10 -0
  209. package/src/index.ts +50 -3
  210. package/src/lucid-schema.ts +22 -0
  211. package/src/orm.ts +13 -0
  212. package/src/query/DatabaseQueryBuilder.ts +2724 -0
  213. package/src/query/DmlBuilder.ts +131 -0
  214. package/src/query/QueryBuilder.ts +8 -0
  215. package/src/query/RawQueryBuilder.ts +173 -0
  216. package/src/query/interpolate.ts +73 -0
  217. package/src/query/native.ts +29 -0
  218. package/src/query/operators.ts +27 -0
  219. package/src/schema/Migration.ts +36 -7
  220. package/src/schema/MigrationRunner.ts +483 -119
  221. package/src/schema/Schema.ts +168 -19
  222. package/src/schema/SchemaDumper.ts +518 -0
  223. package/src/schema/Seeder.ts +47 -10
  224. package/src/schema/TableBuilder.ts +48 -11
  225. package/src/schema/catalog.ts +94 -24
  226. package/src/schema/introspect.ts +26 -9
  227. package/src/seeders.ts +16 -0
  228. package/src/services/db.ts +343 -31
  229. package/src/testing/DatabaseCleanup.ts +73 -21
  230. package/src/testing/DbAssertions.ts +127 -0
  231. package/src/testing/Factory.ts +559 -79
  232. package/src/testing/TestUtils.ts +123 -0
  233. package/src/testing/index.ts +12 -1
  234. 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
+ }
@@ -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
+ }
@@ -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 execute
56
- * (Adonis Lucid `this.defer`). Typically used to seed data into a table the
57
- * same migration just created. The callback gets a live connection handle.
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
- * Note: deferred callbacks run after the schema statements have committed, so
60
- * they are outside the schema transaction (atlas builds schema as a batch);
61
- * the migration is only recorded as applied once every deferred callback
62
- * succeeds. Make deferred work idempotent.
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;