@fragno-dev/db 0.1.13 → 0.1.15

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 (178) hide show
  1. package/.turbo/turbo-build.log +179 -132
  2. package/CHANGELOG.md +30 -0
  3. package/dist/adapters/adapters.d.ts +27 -1
  4. package/dist/adapters/adapters.d.ts.map +1 -1
  5. package/dist/adapters/adapters.js.map +1 -1
  6. package/dist/adapters/drizzle/drizzle-adapter.d.ts +5 -1
  7. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  8. package/dist/adapters/drizzle/drizzle-adapter.js +15 -3
  9. package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-query.js +7 -5
  11. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  12. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -1
  13. package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -1
  14. package/dist/adapters/drizzle/drizzle-uow-compiler.js +76 -44
  15. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  16. package/dist/adapters/drizzle/drizzle-uow-decoder.js +23 -16
  17. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  18. package/dist/adapters/drizzle/drizzle-uow-executor.js +18 -7
  19. package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
  20. package/dist/adapters/drizzle/generate.d.ts +4 -1
  21. package/dist/adapters/drizzle/generate.d.ts.map +1 -1
  22. package/dist/adapters/drizzle/generate.js +11 -18
  23. package/dist/adapters/drizzle/generate.js.map +1 -1
  24. package/dist/adapters/drizzle/shared.d.ts +14 -1
  25. package/dist/adapters/drizzle/shared.d.ts.map +1 -0
  26. package/dist/adapters/kysely/kysely-adapter.d.ts +5 -1
  27. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  28. package/dist/adapters/kysely/kysely-adapter.js +14 -3
  29. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  30. package/dist/adapters/kysely/kysely-query-builder.js +1 -1
  31. package/dist/adapters/kysely/kysely-query-compiler.js +3 -2
  32. package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
  33. package/dist/adapters/kysely/kysely-query.d.ts +1 -0
  34. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
  35. package/dist/adapters/kysely/kysely-query.js +28 -19
  36. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  37. package/dist/adapters/kysely/kysely-shared.d.ts +14 -0
  38. package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -0
  39. package/dist/adapters/kysely/kysely-shared.js +16 -1
  40. package/dist/adapters/kysely/kysely-shared.js.map +1 -1
  41. package/dist/adapters/kysely/kysely-uow-compiler.js +68 -16
  42. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  43. package/dist/adapters/kysely/kysely-uow-executor.js +8 -4
  44. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
  45. package/dist/adapters/kysely/migration/execute-base.js +1 -1
  46. package/dist/adapters/kysely/migration/execute-base.js.map +1 -1
  47. package/dist/db-fragment-definition-builder.d.ts +152 -0
  48. package/dist/db-fragment-definition-builder.d.ts.map +1 -0
  49. package/dist/db-fragment-definition-builder.js +137 -0
  50. package/dist/db-fragment-definition-builder.js.map +1 -0
  51. package/dist/fragments/internal-fragment.d.ts +19 -0
  52. package/dist/fragments/internal-fragment.d.ts.map +1 -0
  53. package/dist/fragments/internal-fragment.js +39 -0
  54. package/dist/fragments/internal-fragment.js.map +1 -0
  55. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  56. package/dist/migration-engine/generation-engine.js +35 -15
  57. package/dist/migration-engine/generation-engine.js.map +1 -1
  58. package/dist/mod.d.ts +8 -18
  59. package/dist/mod.d.ts.map +1 -1
  60. package/dist/mod.js +7 -34
  61. package/dist/mod.js.map +1 -1
  62. package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js +165 -0
  63. package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js.map +1 -0
  64. package/dist/packages/fragno/dist/api/bind-services.js +20 -0
  65. package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
  66. package/dist/packages/fragno/dist/api/error.js +48 -0
  67. package/dist/packages/fragno/dist/api/error.js.map +1 -0
  68. package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
  69. package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
  70. package/dist/packages/fragno/dist/api/fragment-instantiator.js +487 -0
  71. package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
  72. package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
  73. package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
  74. package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
  75. package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
  76. package/dist/packages/fragno/dist/api/internal/route.js +10 -0
  77. package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
  78. package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
  79. package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
  80. package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
  81. package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
  82. package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
  83. package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
  84. package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
  85. package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
  86. package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
  87. package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
  88. package/dist/packages/fragno/dist/api/route.js +17 -0
  89. package/dist/packages/fragno/dist/api/route.js.map +1 -0
  90. package/dist/packages/fragno/dist/internal/symbols.js +10 -0
  91. package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
  92. package/dist/query/cursor.d.ts +10 -2
  93. package/dist/query/cursor.d.ts.map +1 -1
  94. package/dist/query/cursor.js +11 -4
  95. package/dist/query/cursor.js.map +1 -1
  96. package/dist/query/execute-unit-of-work.d.ts +123 -0
  97. package/dist/query/execute-unit-of-work.d.ts.map +1 -0
  98. package/dist/query/execute-unit-of-work.js +184 -0
  99. package/dist/query/execute-unit-of-work.js.map +1 -0
  100. package/dist/query/query.d.ts +3 -3
  101. package/dist/query/query.d.ts.map +1 -1
  102. package/dist/query/result-transform.js +4 -2
  103. package/dist/query/result-transform.js.map +1 -1
  104. package/dist/query/retry-policy.d.ts +88 -0
  105. package/dist/query/retry-policy.d.ts.map +1 -0
  106. package/dist/query/retry-policy.js +61 -0
  107. package/dist/query/retry-policy.js.map +1 -0
  108. package/dist/query/unit-of-work.d.ts +171 -32
  109. package/dist/query/unit-of-work.d.ts.map +1 -1
  110. package/dist/query/unit-of-work.js +530 -133
  111. package/dist/query/unit-of-work.js.map +1 -1
  112. package/dist/schema/serialize.js +12 -7
  113. package/dist/schema/serialize.js.map +1 -1
  114. package/dist/with-database.d.ts +28 -0
  115. package/dist/with-database.d.ts.map +1 -0
  116. package/dist/with-database.js +34 -0
  117. package/dist/with-database.js.map +1 -0
  118. package/package.json +10 -3
  119. package/src/adapters/adapters.ts +30 -0
  120. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +86 -17
  121. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +291 -7
  122. package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
  123. package/src/adapters/drizzle/drizzle-adapter.ts +35 -7
  124. package/src/adapters/drizzle/drizzle-query.ts +25 -15
  125. package/src/adapters/drizzle/drizzle-uow-compiler-mysql.test.ts +1442 -0
  126. package/src/adapters/drizzle/drizzle-uow-compiler-sqlite.test.ts +1414 -0
  127. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +78 -61
  128. package/src/adapters/drizzle/drizzle-uow-compiler.ts +123 -42
  129. package/src/adapters/drizzle/drizzle-uow-decoder.ts +34 -27
  130. package/src/adapters/drizzle/drizzle-uow-executor.ts +41 -8
  131. package/src/adapters/drizzle/generate.test.ts +102 -269
  132. package/src/adapters/drizzle/generate.ts +12 -30
  133. package/src/adapters/drizzle/test-utils.ts +36 -5
  134. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +66 -22
  135. package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
  136. package/src/adapters/kysely/kysely-adapter.ts +25 -2
  137. package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
  138. package/src/adapters/kysely/kysely-query.ts +57 -37
  139. package/src/adapters/kysely/kysely-shared.ts +34 -0
  140. package/src/adapters/kysely/kysely-uow-compiler.test.ts +62 -74
  141. package/src/adapters/kysely/kysely-uow-compiler.ts +92 -24
  142. package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
  143. package/src/adapters/kysely/kysely-uow-joins.test.ts +33 -50
  144. package/src/adapters/kysely/migration/execute-base.ts +1 -1
  145. package/src/db-fragment-definition-builder.test.ts +887 -0
  146. package/src/db-fragment-definition-builder.ts +506 -0
  147. package/src/db-fragment-instantiator.test.ts +467 -0
  148. package/src/db-fragment-integration.test.ts +408 -0
  149. package/src/fragments/internal-fragment.test.ts +160 -0
  150. package/src/fragments/internal-fragment.ts +85 -0
  151. package/src/migration-engine/generation-engine.test.ts +58 -15
  152. package/src/migration-engine/generation-engine.ts +78 -25
  153. package/src/mod.ts +35 -43
  154. package/src/query/cursor.test.ts +119 -0
  155. package/src/query/cursor.ts +17 -4
  156. package/src/query/execute-unit-of-work.test.ts +1310 -0
  157. package/src/query/execute-unit-of-work.ts +463 -0
  158. package/src/query/query.ts +4 -4
  159. package/src/query/result-transform.test.ts +129 -0
  160. package/src/query/result-transform.ts +4 -1
  161. package/src/query/retry-policy.test.ts +217 -0
  162. package/src/query/retry-policy.ts +141 -0
  163. package/src/query/unit-of-work-coordinator.test.ts +833 -0
  164. package/src/query/unit-of-work-types.test.ts +15 -2
  165. package/src/query/unit-of-work.test.ts +878 -200
  166. package/src/query/unit-of-work.ts +963 -321
  167. package/src/schema/serialize.ts +22 -11
  168. package/src/with-database.ts +140 -0
  169. package/tsdown.config.ts +1 -0
  170. package/dist/fragment.d.ts +0 -54
  171. package/dist/fragment.d.ts.map +0 -1
  172. package/dist/fragment.js +0 -92
  173. package/dist/fragment.js.map +0 -1
  174. package/dist/shared/settings-schema.js +0 -36
  175. package/dist/shared/settings-schema.js.map +0 -1
  176. package/src/fragment.test.ts +0 -341
  177. package/src/fragment.ts +0 -198
  178. package/src/shared/settings-schema.ts +0 -61
@@ -14,6 +14,7 @@ import {
14
14
  type TableNameMapper,
15
15
  parseDrizzle,
16
16
  type DBType,
17
+ createTableNameMapper,
17
18
  } from "./shared";
18
19
  import { encodeValues, ReferenceSubquery } from "../../query/result-transform";
19
20
  import { serialize } from "../../schema/serialize";
@@ -33,30 +34,42 @@ export type DrizzleCompiledQuery = {
33
34
  * This compiler translates UOW operations into Drizzle query functions
34
35
  * that can be executed as a batch/transaction.
35
36
  *
36
- * @param schema - The database schema
37
37
  * @param pool - Connection pool for acquiring database connections
38
38
  * @param provider - SQL provider (sqlite, mysql, postgresql)
39
- * @param mapper - Optional table name mapper for namespace prefixing
39
+ * @param mapper - Optional table name mapper for namespace prefixing (fallback for operations without explicit namespace)
40
40
  * @returns A UOWCompiler instance for Drizzle
41
41
  */
42
- export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
43
- schema: TSchema,
42
+ export function createDrizzleUOWCompiler(
44
43
  pool: ConnectionPool<DBType>,
45
44
  provider: "sqlite" | "mysql" | "postgresql",
46
45
  mapper?: TableNameMapper,
47
- ): UOWCompiler<TSchema, DrizzleCompiledQuery> {
46
+ ): UOWCompiler<DrizzleCompiledQuery> {
48
47
  // Get db synchronously for compilation (doesn't execute, just builds SQL)
49
48
  // TODO: We don't even need a Drizzle instance with a db client attached here. `drizzle({ schema })` is enough.
50
49
  const dbRaw = pool.getDatabaseSync();
51
50
  const [db, drizzleTables] = parseDrizzle(dbRaw);
52
51
 
52
+ /**
53
+ * Get the mapper for a specific operation
54
+ * Uses operation's namespace if provided, otherwise falls back to the default mapper
55
+ */
56
+ function getMapperForOperation(namespace: string | undefined): TableNameMapper | undefined {
57
+ if (namespace) {
58
+ return createTableNameMapper(namespace);
59
+ }
60
+ return mapper;
61
+ }
62
+
53
63
  /**
54
64
  * Convert a Fragno table to a Drizzle table
55
65
  * @throws Error if table is not found in Drizzle schema
56
66
  */
57
- function toDrizzleTable(table: AnyTable): TableType {
58
- // Map logical table name to physical table name using the mapper
59
- const physicalTableName = mapper ? mapper.toPhysical(table.ormName) : table.ormName;
67
+ function toDrizzleTable(table: AnyTable, namespace: string | undefined): TableType {
68
+ // Get the mapper for this operation's namespace
69
+ const opMapper = getMapperForOperation(namespace);
70
+
71
+ // Map logical table name to physical table name using the operation-specific mapper
72
+ const physicalTableName = opMapper ? opMapper.toPhysical(table.ormName) : table.ormName;
60
73
  const out = drizzleTables[physicalTableName];
61
74
  if (out) {
62
75
  return out;
@@ -71,13 +84,17 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
71
84
  * Convert a Fragno column to a Drizzle column
72
85
  * @throws Error if column is not found in Drizzle table
73
86
  */
74
- function toDrizzleColumn(col: AnyColumn): ColumnType {
87
+ function toDrizzleColumn(
88
+ schema: AnySchema,
89
+ namespace: string | undefined,
90
+ col: AnyColumn,
91
+ ): ColumnType {
75
92
  const fragnoTable = schema.tables[col.tableName];
76
93
  if (!fragnoTable) {
77
94
  throw new Error(`[Drizzle] Unknown table ${col.tableName} for column ${col.ormName}.`);
78
95
  }
79
96
 
80
- const table = toDrizzleTable(fragnoTable);
97
+ const table = toDrizzleTable(fragnoTable, namespace);
81
98
  const out = table[col.ormName];
82
99
  if (out) {
83
100
  return out;
@@ -89,13 +106,17 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
89
106
  /**
90
107
  * Build a WHERE clause from a condition using Drizzle's query builder
91
108
  */
92
- function buildWhere(condition: Condition): Drizzle.SQL | undefined {
109
+ function buildWhere(
110
+ schema: AnySchema,
111
+ namespace: string | undefined,
112
+ condition: Condition,
113
+ ): Drizzle.SQL | undefined {
93
114
  if (condition.type === "compare") {
94
- const left = toDrizzleColumn(condition.a);
115
+ const left = toDrizzleColumn(schema, namespace, condition.a);
95
116
  const op = condition.operator;
96
117
  let right = condition.b;
97
118
  if (right instanceof Column) {
98
- right = toDrizzleColumn(right);
119
+ right = toDrizzleColumn(schema, namespace, right);
99
120
  } else {
100
121
  // Handle string references - convert external ID to internal ID via subquery
101
122
  if (condition.a.role === "reference" && typeof right === "string") {
@@ -181,11 +202,11 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
181
202
  }
182
203
 
183
204
  if (condition.type === "and") {
184
- return Drizzle.and(...condition.items.map((item) => buildWhere(item)));
205
+ return Drizzle.and(...condition.items.map((item) => buildWhere(schema, namespace, item)));
185
206
  }
186
207
 
187
208
  if (condition.type === "not") {
188
- const result = buildWhere(condition.item);
209
+ const result = buildWhere(schema, namespace, condition.item);
189
210
  if (!result) {
190
211
  return;
191
212
  }
@@ -193,7 +214,7 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
193
214
  return Drizzle.not(result);
194
215
  }
195
216
 
196
- return Drizzle.or(...condition.items.map((item) => buildWhere(item)));
217
+ return Drizzle.or(...condition.items.map((item) => buildWhere(schema, namespace, item)));
197
218
  }
198
219
 
199
220
  /**
@@ -229,7 +250,7 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
229
250
  * Get table from schema by name
230
251
  * @throws Error if table is not found in schema
231
252
  */
232
- function getTable(name: unknown): AnyTable {
253
+ function getTable(schema: AnySchema, name: unknown): AnyTable {
233
254
  const table = schema.tables[name as string];
234
255
  if (!table) {
235
256
  throw new Error(`Invalid table name ${name}.`);
@@ -260,6 +281,8 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
260
281
  * Process joins recursively to support nested joins with orderBy and limit
261
282
  */
262
283
  function processJoins(
284
+ schema: AnySchema,
285
+ namespace: string | undefined,
263
286
  joins: CompiledJoin[],
264
287
  ): Record<string, Drizzle.DBQueryConfig<"many", boolean>> {
265
288
  const result: Record<string, Drizzle.DBQueryConfig<"many", boolean>> = {};
@@ -286,7 +309,7 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
286
309
  let joinOrderBy: Drizzle.SQL[] | undefined;
287
310
  if (options.orderBy && options.orderBy.length > 0) {
288
311
  joinOrderBy = options.orderBy.map(([col, direction]) => {
289
- const drizzleCol = toDrizzleColumn(col);
312
+ const drizzleCol = toDrizzleColumn(schema, namespace, col);
290
313
  return direction === "asc" ? Drizzle.asc(drizzleCol) : Drizzle.desc(drizzleCol);
291
314
  });
292
315
  }
@@ -294,7 +317,7 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
294
317
  // Build WHERE clause for this join if provided
295
318
  let joinWhere: Drizzle.SQL | undefined;
296
319
  if (options.where) {
297
- joinWhere = buildWhere(options.where);
320
+ joinWhere = buildWhere(schema, namespace, options.where);
298
321
  }
299
322
 
300
323
  // Build the join config
@@ -307,7 +330,7 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
307
330
 
308
331
  // Recursively process nested joins
309
332
  if (options.join && options.join.length > 0) {
310
- joinConfig.with = processJoins(options.join);
333
+ joinConfig.with = processJoins(schema, namespace, options.join);
311
334
  }
312
335
 
313
336
  result[joinName] = joinConfig;
@@ -317,7 +340,8 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
317
340
  }
318
341
 
319
342
  return {
320
- compileRetrievalOperation(op: RetrievalOperation<TSchema>): DrizzleCompiledQuery | null {
343
+ compileRetrievalOperation(op: RetrievalOperation<AnySchema>): DrizzleCompiledQuery | null {
344
+ const schema = op.schema;
321
345
  switch (op.type) {
322
346
  case "count": {
323
347
  // Build WHERE clause
@@ -329,11 +353,11 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
329
353
  return null;
330
354
  }
331
355
  if (condition !== true) {
332
- whereClause = buildWhere(condition);
356
+ whereClause = buildWhere(schema, op.namespace, condition);
333
357
  }
334
358
  }
335
359
 
336
- const drizzleTable = toDrizzleTable(op.table);
360
+ const drizzleTable = toDrizzleTable(op.table, op.namespace);
337
361
  const query = db.select({ count: Drizzle.count() }).from(drizzleTable);
338
362
 
339
363
  const compiledQuery = whereClause ? query.where(whereClause).toSQL() : query.toSQL();
@@ -377,7 +401,7 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
377
401
  let orderBy: Drizzle.SQL[] | undefined;
378
402
  if (indexColumns.length > 0) {
379
403
  orderBy = indexColumns.map((col) => {
380
- const drizzleCol = toDrizzleColumn(col);
404
+ const drizzleCol = toDrizzleColumn(schema, op.namespace, col);
381
405
  return orderDirection === "asc" ? Drizzle.asc(drizzleCol) : Drizzle.desc(drizzleCol);
382
406
  });
383
407
  }
@@ -413,7 +437,7 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
413
437
  return null;
414
438
  }
415
439
  if (condition !== true) {
416
- const clause = buildWhere(condition);
440
+ const clause = buildWhere(schema, op.namespace, condition);
417
441
  if (clause) {
418
442
  whereClauses.push(clause);
419
443
  }
@@ -436,12 +460,12 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
436
460
 
437
461
  if (indexColumns.length === 1) {
438
462
  // Simple single-column case
439
- const col = toDrizzleColumn(indexColumns[0]!);
463
+ const col = toDrizzleColumn(schema, op.namespace, indexColumns[0]!);
440
464
  const val = serializedValues[indexColumns[0]!.ormName];
441
465
  whereClauses.push(useGreaterThan ? Drizzle.gt(col, val) : Drizzle.lt(col, val));
442
466
  } else {
443
467
  // Multi-column tuple comparison using SQL
444
- const drizzleCols = indexColumns.map((c) => toDrizzleColumn(c));
468
+ const drizzleCols = indexColumns.map((c) => toDrizzleColumn(schema, op.namespace, c));
445
469
  const vals = indexColumns.map((c) => serializedValues[c.ormName]);
446
470
  const operator = useGreaterThan ? ">" : "<";
447
471
  // Safe cast: building a SQL comparison expression for cursor pagination
@@ -459,9 +483,13 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
459
483
 
460
484
  const whereClause = whereClauses.length > 0 ? Drizzle.and(...whereClauses) : undefined;
461
485
 
486
+ // For cursor pagination, fetch one extra item to determine if there's a next page
487
+ // Only apply this when using the high-level findWithCursor() API (op.withCursor === true)
488
+ const effectiveLimit = pageSize && op.withCursor ? pageSize + 1 : pageSize;
489
+
462
490
  const queryConfig: Drizzle.DBQueryConfig<"many", boolean> = {
463
491
  columns,
464
- limit: pageSize,
492
+ limit: effectiveLimit,
465
493
  where: whereClause,
466
494
  orderBy,
467
495
  with: {},
@@ -469,39 +497,54 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
469
497
 
470
498
  // Process joins recursively to support nested joins
471
499
  if (joins) {
472
- queryConfig.with = processJoins(joins);
500
+ queryConfig.with = processJoins(schema, op.namespace, joins);
473
501
  }
474
502
 
475
- const physicalTableName = mapper ? mapper.toPhysical(op.table.ormName) : op.table.ormName;
476
- const compiledQuery = db.query[physicalTableName].findMany(queryConfig).toSQL();
503
+ // For multi-schema support: get the mapper for the operation's namespace
504
+ const opMapper = getMapperForOperation(op.namespace);
505
+ const physicalTableName = opMapper
506
+ ? opMapper.toPhysical(op.table.ormName)
507
+ : op.table.ormName;
508
+ const tableQuery = db.query[physicalTableName];
509
+
510
+ if (!tableQuery) {
511
+ throw new Error(
512
+ `[Drizzle] Table ${op.table.ormName} (physical: ${physicalTableName}) not found in db.query. ` +
513
+ `Available tables: ${Object.keys(db.query).join(", ")}`,
514
+ );
515
+ }
516
+
517
+ const compiledQuery = tableQuery.findMany(queryConfig).toSQL();
477
518
  return compiledQuery;
478
519
  }
479
520
  }
480
521
  },
481
522
 
482
523
  compileMutationOperation(
483
- op: MutationOperation<TSchema>,
524
+ op: MutationOperation<AnySchema>,
484
525
  ): CompiledMutation<DrizzleCompiledQuery> | null {
526
+ const schema = op.schema;
485
527
  switch (op.type) {
486
528
  case "create": {
487
- const table = getTable(op.table);
488
- const drizzleTable = toDrizzleTable(table);
529
+ const table = getTable(schema, op.table);
530
+ const drizzleTable = toDrizzleTable(table, op.namespace);
489
531
  // encodeValues now handles runtime defaults automatically
490
- const encodedValues = encodeValues(op.values, table, true, provider);
532
+ const encodedValues = encodeValues(op.values, table, true, provider, true);
491
533
  const values = processReferenceSubqueries(encodedValues);
492
534
 
493
535
  const compiledQuery = db.insert(drizzleTable).values(values).toSQL();
494
536
  return {
495
537
  query: compiledQuery,
496
538
  expectedAffectedRows: null, // creates don't need affected row checks
539
+ expectedReturnedRows: null,
497
540
  };
498
541
  }
499
542
 
500
543
  case "update": {
501
- const table = getTable(op.table);
544
+ const table = getTable(schema, op.table);
502
545
  const idColumn = table.getIdColumn();
503
546
  const versionColumn = table.getVersionColumn();
504
- const drizzleTable = toDrizzleTable(table);
547
+ const drizzleTable = toDrizzleTable(table, op.namespace);
505
548
 
506
549
  const externalId = typeof op.id === "string" ? op.id : op.id.externalId;
507
550
  const versionToCheck = getVersionToCheck(op.id, op.checkVersion);
@@ -523,8 +566,9 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
523
566
  return null;
524
567
  }
525
568
 
526
- const whereClause = condition === true ? undefined : buildWhere(condition);
527
- const encodedSetValues = encodeValues(op.set, table, false, provider);
569
+ const whereClause =
570
+ condition === true ? undefined : buildWhere(schema, op.namespace, condition);
571
+ const encodedSetValues = encodeValues(op.set, table, false, provider, true);
528
572
  const setValues = processReferenceSubqueries(encodedSetValues);
529
573
 
530
574
  // Automatically increment _version for optimistic concurrency control
@@ -537,14 +581,15 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
537
581
  return {
538
582
  query: compiledQuery,
539
583
  expectedAffectedRows: op.checkVersion ? 1 : null,
584
+ expectedReturnedRows: null,
540
585
  };
541
586
  }
542
587
 
543
588
  case "delete": {
544
- const table = getTable(op.table);
589
+ const table = getTable(schema, op.table);
545
590
  const idColumn = table.getIdColumn();
546
591
  const versionColumn = table.getVersionColumn();
547
- const drizzleTable = toDrizzleTable(table);
592
+ const drizzleTable = toDrizzleTable(table, op.namespace);
548
593
 
549
594
  if (!op.id) {
550
595
  throw new Error(
@@ -582,12 +627,48 @@ export function createDrizzleUOWCompiler<TSchema extends AnySchema>(
582
627
  return null;
583
628
  }
584
629
 
585
- const whereClause = condition === true ? undefined : buildWhere(condition);
630
+ const whereClause =
631
+ condition === true ? undefined : buildWhere(schema, op.namespace, condition);
586
632
 
587
633
  const compiledQuery = db.delete(drizzleTable).where(whereClause).toSQL();
588
634
  return {
589
635
  query: compiledQuery,
590
636
  expectedAffectedRows: op.checkVersion ? 1 : null,
637
+ expectedReturnedRows: null,
638
+ };
639
+ }
640
+
641
+ case "check": {
642
+ const table = getTable(schema, op.table);
643
+ const idColumn = table.getIdColumn();
644
+ const versionColumn = table.getVersionColumn();
645
+ const drizzleTable = toDrizzleTable(table, op.namespace);
646
+
647
+ const externalId = op.id.externalId;
648
+ const version = op.id.version;
649
+
650
+ // Build WHERE clause that filters by ID and version
651
+ const condition = buildCondition(table.columns, (eb) =>
652
+ eb.and(eb(idColumn.ormName, "=", externalId), eb(versionColumn.ormName, "=", version)),
653
+ );
654
+
655
+ if (typeof condition === "boolean") {
656
+ throw new Error("Condition is a boolean, but should be a condition object.");
657
+ }
658
+
659
+ // Build a SELECT query to check if the row exists with the correct version
660
+ // Use sql`1` to select a constant with an alias
661
+ const compiledQuery = db
662
+ .select({ exists: Drizzle.sql<number>`1`.as("exists") })
663
+ .from(drizzleTable)
664
+ .where(buildWhere(schema, op.namespace, condition))
665
+ .limit(1)
666
+ .toSQL();
667
+
668
+ return {
669
+ query: compiledQuery,
670
+ expectedAffectedRows: null,
671
+ expectedReturnedRows: 1, // Check that exactly 1 row was returned
591
672
  };
592
673
  }
593
674
  }
@@ -143,17 +143,14 @@ function transformJoinArraysToObjects(
143
143
  return transformedRow;
144
144
  }
145
145
 
146
- export function createDrizzleUOWDecoder<TSchema extends AnySchema>(
147
- _schema: TSchema,
148
- provider: SQLProvider,
149
- ): UOWDecoder<TSchema, DrizzleResult> {
146
+ export function createDrizzleUOWDecoder(provider: SQLProvider): UOWDecoder<DrizzleResult> {
150
147
  return (rawResults, ops) => {
151
148
  if (rawResults.length !== ops.length) {
152
149
  throw new Error("rawResults and ops must have the same length");
153
150
  }
154
151
 
155
152
  return rawResults.map((result, index) => {
156
- const op = ops[index] as RetrievalOperation<TSchema>;
153
+ const op = ops[index] as RetrievalOperation<AnySchema>;
157
154
  if (!op) {
158
155
  throw new Error("op must be defined");
159
156
  }
@@ -182,35 +179,45 @@ export function createDrizzleUOWDecoder<TSchema extends AnySchema>(
182
179
  // If cursor generation is requested, wrap in CursorResult
183
180
  if (op.withCursor) {
184
181
  let cursor: Cursor | undefined;
185
-
186
- // Generate cursor from last item if results exist
187
- if (decodedRows.length > 0 && op.options.orderByIndex && op.options.pageSize) {
188
- const lastItem = decodedRows[decodedRows.length - 1];
189
- const indexName = op.options.orderByIndex.indexName;
190
-
191
- // Get index columns
192
- let indexColumns;
193
- if (indexName === "_primary") {
194
- indexColumns = [op.table.getIdColumn()];
195
- } else {
196
- const index = op.table.indexes[indexName];
197
- if (index) {
198
- indexColumns = index.columns;
182
+ let hasNextPage = false;
183
+ let items = decodedRows;
184
+
185
+ // Check if there are more results (we fetched pageSize + 1)
186
+ if (op.options.pageSize && decodedRows.length > op.options.pageSize) {
187
+ hasNextPage = true;
188
+ // Trim to requested pageSize
189
+ items = decodedRows.slice(0, op.options.pageSize);
190
+
191
+ // Generate cursor from the last item we're returning
192
+ if (op.options.orderByIndex) {
193
+ const lastItem = items[items.length - 1];
194
+ const indexName = op.options.orderByIndex.indexName;
195
+
196
+ // Get index columns
197
+ let indexColumns;
198
+ if (indexName === "_primary") {
199
+ indexColumns = [op.table.getIdColumn()];
200
+ } else {
201
+ const index = op.table.indexes[indexName];
202
+ if (index) {
203
+ indexColumns = index.columns;
204
+ }
199
205
  }
200
- }
201
206
 
202
- if (indexColumns && lastItem) {
203
- cursor = createCursorFromRecord(lastItem as Record<string, unknown>, indexColumns, {
204
- indexName: op.options.orderByIndex.indexName,
205
- orderDirection: op.options.orderByIndex.direction,
206
- pageSize: op.options.pageSize,
207
- });
207
+ if (indexColumns && lastItem) {
208
+ cursor = createCursorFromRecord(lastItem as Record<string, unknown>, indexColumns, {
209
+ indexName: op.options.orderByIndex.indexName,
210
+ orderDirection: op.options.orderByIndex.direction,
211
+ pageSize: op.options.pageSize,
212
+ });
213
+ }
208
214
  }
209
215
  }
210
216
 
211
217
  const cursorResult: CursorResult<unknown> = {
212
- items: decodedRows,
218
+ items,
213
219
  cursor,
220
+ hasNextPage,
214
221
  };
215
222
  return cursorResult;
216
223
  }
@@ -75,7 +75,6 @@ function toSQL(query: DrizzleCompiledQuery, provider: "sqlite" | "mysql" | "post
75
75
 
76
76
  const queryChunks =
77
77
  provider === "sqlite" ? sqliteToSQL(sqlString, params) : postgresToSQL(sqlString, params);
78
-
79
78
  return new SQL(queryChunks);
80
79
  }
81
80
 
@@ -160,7 +159,24 @@ function extractCreatedInternalId(result: unknown): bigint | null {
160
159
  return null;
161
160
  }
162
161
 
162
+ function validateReturnedRows(result: unknown, expected: number): void {
163
+ // For SELECT queries, check the number of rows returned
164
+ // Result can be an array (sync) or an object with rows property (async)
165
+ let rowCount = 0;
166
+ if (Array.isArray(result)) {
167
+ rowCount = result.length;
168
+ } else if (result && typeof result === "object" && "rows" in result) {
169
+ const rows = (result as { rows: unknown[] }).rows;
170
+ rowCount = Array.isArray(rows) ? rows.length : 0;
171
+ }
172
+
173
+ if (rowCount !== expected) {
174
+ throw new Error(`Version conflict: expected ${expected} rows returned, but got ${rowCount}`);
175
+ }
176
+ }
177
+
163
178
  function validateAffectedRows(result: unknown, expected: number): void {
179
+ // For UPDATE/DELETE queries, check affected rows
164
180
  const actual = getAffectedRows(result);
165
181
  if (actual !== expected) {
166
182
  throw new Error(`Version conflict: expected ${expected} rows affected, but got ${actual}`);
@@ -235,21 +251,32 @@ export async function executeDrizzleMutationPhase(
235
251
  db,
236
252
  provider,
237
253
  (syncDb) => {
238
- for (const { query, expectedAffectedRows } of mutationBatch) {
254
+ for (const { query, expectedAffectedRows, expectedReturnedRows } of mutationBatch) {
239
255
  const sqlObj = toSQL(query, provider);
256
+
240
257
  // Type assertion needed due to drizzle-orm version mismatch in dependencies
241
- const result = syncDb.run(sqlObj as never);
258
+ const result =
259
+ expectedReturnedRows !== null
260
+ ? syncDb.all(sqlObj as never)
261
+ : syncDb.run(sqlObj as never);
242
262
 
243
- if (expectedAffectedRows === null) {
263
+ if (expectedAffectedRows === null && expectedReturnedRows === null) {
244
264
  createdInternalIds.push(extractCreatedInternalId(result));
245
- } else {
265
+ }
266
+
267
+ if (expectedReturnedRows !== null) {
268
+ validateReturnedRows(result, expectedReturnedRows);
269
+ }
270
+
271
+ if (expectedAffectedRows !== null) {
246
272
  validateAffectedRows(result, expectedAffectedRows);
247
273
  }
248
274
  }
249
275
  },
250
276
  async (tx) => {
251
- for (const { query, expectedAffectedRows } of mutationBatch) {
277
+ for (const { query, expectedAffectedRows, expectedReturnedRows } of mutationBatch) {
252
278
  const sqlObj = toSQL(query, provider);
279
+
253
280
  // Fallback to run when execute is not available (e.g., libsql)
254
281
  const executeMethod = tx.execute ?? tx.run;
255
282
  if (!executeMethod) {
@@ -257,9 +284,15 @@ export async function executeDrizzleMutationPhase(
257
284
  }
258
285
  const result = await executeMethod.call(tx, sqlObj);
259
286
 
260
- if (expectedAffectedRows === null) {
287
+ if (expectedAffectedRows === null && expectedReturnedRows === null) {
261
288
  createdInternalIds.push(extractCreatedInternalId(result));
262
- } else {
289
+ }
290
+
291
+ if (expectedReturnedRows !== null) {
292
+ validateReturnedRows(result, expectedReturnedRows);
293
+ }
294
+
295
+ if (expectedAffectedRows !== null) {
263
296
  validateAffectedRows(result, expectedAffectedRows);
264
297
  }
265
298
  }