@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
@@ -1,8 +1,8 @@
1
1
  //#region src/adapters/kysely/kysely-uow-executor.ts
2
2
  function getAffectedRows(result) {
3
- const affectedRows = result.numAffectedRows ?? result.numChangedRows ?? ("affectedRows" in result && (typeof result["affectedRows"] === "number" || typeof result["affectedRows"] === "bigint") ? result["affectedRows"] : void 0);
4
- if (affectedRows === void 0) throw new Error("No affected rows found");
5
- if (affectedRows > Number.MAX_SAFE_INTEGER) throw new Error(`affectedRows BigInt value ${affectedRows.toString()} exceeds JS safe integer range`);
3
+ const affectedRows = result.numAffectedRows ?? result.numChangedRows ?? ("affectedRows" in result && (typeof result["affectedRows"] === "number" || typeof result["affectedRows"] === "bigint") ? result["affectedRows"] : void 0) ?? ("numUpdatedRows" in result && (typeof result["numUpdatedRows"] === "number" || typeof result["numUpdatedRows"] === "bigint") ? result["numUpdatedRows"] : void 0);
4
+ if (affectedRows === void 0) throw new Error(`No affected rows found: ${JSON.stringify(result)}`);
5
+ if (typeof affectedRows === "bigint" && affectedRows > Number.MAX_SAFE_INTEGER) throw new Error(`affectedRows BigInt value ${affectedRows.toString()} exceeds JS safe integer range`);
6
6
  return Number(affectedRows);
7
7
  }
8
8
  /**
@@ -68,10 +68,14 @@ async function executeKyselyMutationPhase(kysely, mutationBatch) {
68
68
  createdInternalIds.push(internalId);
69
69
  } else createdInternalIds.push(null);
70
70
  } else createdInternalIds.push(null);
71
- else {
71
+ else if (compiledMutation.expectedAffectedRows !== null) {
72
72
  const affectedRows = getAffectedRows(result);
73
73
  if (affectedRows !== compiledMutation.expectedAffectedRows) throw new Error(`Version conflict: expected ${compiledMutation.expectedAffectedRows} rows affected, but got ${affectedRows}`);
74
74
  }
75
+ if (compiledMutation.expectedReturnedRows !== null) {
76
+ const rowCount = Array.isArray(result.rows) ? result.rows.length : 0;
77
+ if (rowCount !== compiledMutation.expectedReturnedRows) throw new Error(`Version conflict: expected ${compiledMutation.expectedReturnedRows} rows returned, but got ${rowCount}`);
78
+ }
75
79
  }
76
80
  });
77
81
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"kysely-uow-executor.js","names":["retrievalResults: unknown[]","createdInternalIds: (bigint | null)[]"],"sources":["../../../src/adapters/kysely/kysely-uow-executor.ts"],"sourcesContent":["import type { CompiledQuery, Kysely, QueryResult } from \"kysely\";\nimport type { CompiledMutation, MutationResult } from \"../../query/unit-of-work\";\n\nfunction getAffectedRows(result: QueryResult<unknown>): number {\n const affectedRows =\n result.numAffectedRows ??\n result.numChangedRows ??\n // PGLite returns `affectedRows` instead of `numAffectedRows` or `numChangedRows`\n (\"affectedRows\" in result &&\n (typeof result[\"affectedRows\"] === \"number\" || typeof result[\"affectedRows\"] === \"bigint\")\n ? result[\"affectedRows\"]\n : undefined);\n\n if (affectedRows === undefined) {\n throw new Error(\"No affected rows found\");\n }\n\n if (affectedRows > Number.MAX_SAFE_INTEGER) {\n throw new Error(\n `affectedRows BigInt value ${affectedRows.toString()} exceeds JS safe integer range`,\n );\n }\n\n return Number(affectedRows);\n}\n\n/**\n * Execute the retrieval phase of a Unit of Work using Kysely\n *\n * All retrieval queries are executed inside a single transaction to ensure\n * snapshot isolation - all reads see a consistent view of the database.\n *\n * @param kysely - The Kysely database instance\n * @param retrievalBatch - Array of compiled retrieval queries\n * @returns Array of query results matching the retrieval operations order\n *\n * @example\n * ```ts\n * const retrievalResults = await executeKyselyRetrievalPhase(kysely, compiled.retrievalBatch);\n * const [users, posts] = retrievalResults;\n * ```\n */\nexport async function executeKyselyRetrievalPhase(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n kysely: Kysely<any>,\n retrievalBatch: CompiledQuery[],\n): Promise<unknown[]> {\n // If no retrieval operations, return empty array immediately\n if (retrievalBatch.length === 0) {\n return [];\n }\n\n const retrievalResults: unknown[] = [];\n\n // Execute all retrieval queries inside a transaction for snapshot isolation\n await kysely.transaction().execute(async (tx) => {\n for (const compiledQuery of retrievalBatch) {\n const result = await tx.executeQuery(compiledQuery);\n retrievalResults.push(result.rows);\n }\n });\n\n return retrievalResults;\n}\n\n/**\n * Execute the mutation phase of a Unit of Work using Kysely\n *\n * All mutation queries are executed in a transaction with optimistic locking.\n * If any version check fails, the entire transaction is rolled back and\n * success=false is returned.\n *\n * @param kysely - The Kysely database instance\n * @param mutationBatch - Array of compiled mutation queries with expected affected rows\n * @returns Object with success flag and internal IDs from create operations\n *\n * @example\n * ```ts\n * const { success, createdInternalIds } = await executeKyselyMutationPhase(kysely, compiled.mutationBatch);\n * if (!success) {\n * console.log(\"Version conflict detected, retrying...\");\n * }\n * ```\n */\nexport async function executeKyselyMutationPhase(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n kysely: Kysely<any>,\n mutationBatch: CompiledMutation<CompiledQuery>[],\n): Promise<MutationResult> {\n // If there are no mutations, return success immediately\n if (mutationBatch.length === 0) {\n return { success: true, createdInternalIds: [] };\n }\n\n const createdInternalIds: (bigint | null)[] = [];\n\n // Execute mutation batch in a transaction\n try {\n await kysely.transaction().execute(async (tx) => {\n for (const compiledMutation of mutationBatch) {\n const result = await tx.executeQuery(compiledMutation.query);\n\n // For creates (expectedAffectedRows === null), try to extract internal ID\n if (compiledMutation.expectedAffectedRows === null) {\n // Check if result has rows (RETURNING clause supported)\n if (Array.isArray(result.rows) && result.rows.length > 0) {\n const row = result.rows[0] as Record<string, unknown>;\n if (\"_internalId\" in row || \"_internal_id\" in row) {\n const internalId = (row[\"_internalId\"] ?? row[\"_internal_id\"]) as bigint;\n createdInternalIds.push(internalId);\n } else {\n // RETURNING supported but _internalId not found\n createdInternalIds.push(null);\n }\n } else {\n // No RETURNING support (e.g., MySQL)\n createdInternalIds.push(null);\n }\n } else {\n // Check affected rows for updates/deletes\n const affectedRows = getAffectedRows(result);\n\n if (affectedRows !== compiledMutation.expectedAffectedRows) {\n // Version conflict detected - the UPDATE/DELETE didn't affect the expected number of rows\n // This means either the row doesn't exist or the version has changed\n throw new Error(\n `Version conflict: expected ${compiledMutation.expectedAffectedRows} rows affected, but got ${affectedRows}`,\n );\n }\n }\n }\n });\n\n return { success: true, createdInternalIds };\n } catch (error) {\n // Transaction failed - could be version conflict or other constraint violation\n // Return success=false to indicate the UOW should be retried\n if (error instanceof Error && error.message.includes(\"Version conflict\")) {\n return { success: false };\n }\n\n // Other database errors should be thrown\n throw error;\n }\n}\n"],"mappings":";AAGA,SAAS,gBAAgB,QAAsC;CAC7D,MAAM,eACJ,OAAO,mBACP,OAAO,mBAEN,kBAAkB,WAClB,OAAO,OAAO,oBAAoB,YAAY,OAAO,OAAO,oBAAoB,YAC7E,OAAO,kBACP;AAEN,KAAI,iBAAiB,OACnB,OAAM,IAAI,MAAM,yBAAyB;AAG3C,KAAI,eAAe,OAAO,iBACxB,OAAM,IAAI,MACR,6BAA6B,aAAa,UAAU,CAAC,gCACtD;AAGH,QAAO,OAAO,aAAa;;;;;;;;;;;;;;;;;;AAmB7B,eAAsB,4BAEpB,QACA,gBACoB;AAEpB,KAAI,eAAe,WAAW,EAC5B,QAAO,EAAE;CAGX,MAAMA,mBAA8B,EAAE;AAGtC,OAAM,OAAO,aAAa,CAAC,QAAQ,OAAO,OAAO;AAC/C,OAAK,MAAM,iBAAiB,gBAAgB;GAC1C,MAAM,SAAS,MAAM,GAAG,aAAa,cAAc;AACnD,oBAAiB,KAAK,OAAO,KAAK;;GAEpC;AAEF,QAAO;;;;;;;;;;;;;;;;;;;;;AAsBT,eAAsB,2BAEpB,QACA,eACyB;AAEzB,KAAI,cAAc,WAAW,EAC3B,QAAO;EAAE,SAAS;EAAM,oBAAoB,EAAE;EAAE;CAGlD,MAAMC,qBAAwC,EAAE;AAGhD,KAAI;AACF,QAAM,OAAO,aAAa,CAAC,QAAQ,OAAO,OAAO;AAC/C,QAAK,MAAM,oBAAoB,eAAe;IAC5C,MAAM,SAAS,MAAM,GAAG,aAAa,iBAAiB,MAAM;AAG5D,QAAI,iBAAiB,yBAAyB,KAE5C,KAAI,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,KAAK,SAAS,GAAG;KACxD,MAAM,MAAM,OAAO,KAAK;AACxB,SAAI,iBAAiB,OAAO,kBAAkB,KAAK;MACjD,MAAM,aAAc,IAAI,kBAAkB,IAAI;AAC9C,yBAAmB,KAAK,WAAW;WAGnC,oBAAmB,KAAK,KAAK;UAI/B,oBAAmB,KAAK,KAAK;SAE1B;KAEL,MAAM,eAAe,gBAAgB,OAAO;AAE5C,SAAI,iBAAiB,iBAAiB,qBAGpC,OAAM,IAAI,MACR,8BAA8B,iBAAiB,qBAAqB,0BAA0B,eAC/F;;;IAIP;AAEF,SAAO;GAAE,SAAS;GAAM;GAAoB;UACrC,OAAO;AAGd,MAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,mBAAmB,CACtE,QAAO,EAAE,SAAS,OAAO;AAI3B,QAAM"}
1
+ {"version":3,"file":"kysely-uow-executor.js","names":["retrievalResults: unknown[]","createdInternalIds: (bigint | null)[]"],"sources":["../../../src/adapters/kysely/kysely-uow-executor.ts"],"sourcesContent":["import type { CompiledQuery, Kysely, QueryResult } from \"kysely\";\nimport type { CompiledMutation, MutationResult } from \"../../query/unit-of-work\";\n\nfunction getAffectedRows(result: QueryResult<unknown>): number {\n const affectedRows =\n result.numAffectedRows ??\n result.numChangedRows ??\n // PGLite returns `affectedRows` instead of `numAffectedRows` or `numChangedRows`\n (\"affectedRows\" in result &&\n (typeof result[\"affectedRows\"] === \"number\" || typeof result[\"affectedRows\"] === \"bigint\")\n ? result[\"affectedRows\"]\n : undefined) ??\n // SQLite via SQLocal returns `numUpdatedRows` as BigInt\n (\"numUpdatedRows\" in result &&\n (typeof result[\"numUpdatedRows\"] === \"number\" || typeof result[\"numUpdatedRows\"] === \"bigint\")\n ? result[\"numUpdatedRows\"]\n : undefined);\n\n if (affectedRows === undefined) {\n throw new Error(`No affected rows found: ${JSON.stringify(result)}`);\n }\n\n if (typeof affectedRows === \"bigint\" && affectedRows > Number.MAX_SAFE_INTEGER) {\n throw new Error(\n `affectedRows BigInt value ${affectedRows.toString()} exceeds JS safe integer range`,\n );\n }\n\n return Number(affectedRows);\n}\n\n/**\n * Execute the retrieval phase of a Unit of Work using Kysely\n *\n * All retrieval queries are executed inside a single transaction to ensure\n * snapshot isolation - all reads see a consistent view of the database.\n *\n * @param kysely - The Kysely database instance\n * @param retrievalBatch - Array of compiled retrieval queries\n * @returns Array of query results matching the retrieval operations order\n *\n * @example\n * ```ts\n * const retrievalResults = await executeKyselyRetrievalPhase(kysely, compiled.retrievalBatch);\n * const [users, posts] = retrievalResults;\n * ```\n */\nexport async function executeKyselyRetrievalPhase(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n kysely: Kysely<any>,\n retrievalBatch: CompiledQuery[],\n): Promise<unknown[]> {\n // If no retrieval operations, return empty array immediately\n if (retrievalBatch.length === 0) {\n return [];\n }\n\n const retrievalResults: unknown[] = [];\n\n // Execute all retrieval queries inside a transaction for snapshot isolation\n await kysely.transaction().execute(async (tx) => {\n for (const compiledQuery of retrievalBatch) {\n const result = await tx.executeQuery(compiledQuery);\n retrievalResults.push(result.rows);\n }\n });\n\n return retrievalResults;\n}\n\n/**\n * Execute the mutation phase of a Unit of Work using Kysely\n *\n * All mutation queries are executed in a transaction with optimistic locking.\n * If any version check fails, the entire transaction is rolled back and\n * success=false is returned.\n *\n * @param kysely - The Kysely database instance\n * @param mutationBatch - Array of compiled mutation queries with expected affected rows\n * @returns Object with success flag and internal IDs from create operations\n *\n * @example\n * ```ts\n * const { success, createdInternalIds } = await executeKyselyMutationPhase(kysely, compiled.mutationBatch);\n * if (!success) {\n * console.log(\"Version conflict detected, retrying...\");\n * }\n * ```\n */\nexport async function executeKyselyMutationPhase(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n kysely: Kysely<any>,\n mutationBatch: CompiledMutation<CompiledQuery>[],\n): Promise<MutationResult> {\n // If there are no mutations, return success immediately\n if (mutationBatch.length === 0) {\n return { success: true, createdInternalIds: [] };\n }\n\n const createdInternalIds: (bigint | null)[] = [];\n\n // Execute mutation batch in a transaction\n try {\n await kysely.transaction().execute(async (tx) => {\n for (const compiledMutation of mutationBatch) {\n const result = await tx.executeQuery(compiledMutation.query);\n\n // Best-effort extraction: Try to get internal ID if available\n // This is optional - the system works without it by using subqueries for references\n if (compiledMutation.expectedAffectedRows === null) {\n if (Array.isArray(result.rows) && result.rows.length > 0) {\n const row = result.rows[0] as Record<string, unknown>;\n if (\"_internalId\" in row || \"_internal_id\" in row) {\n const internalId = (row[\"_internalId\"] ?? row[\"_internal_id\"]) as bigint;\n createdInternalIds.push(internalId);\n } else {\n // RETURNING supported but _internalId not found - that's okay\n createdInternalIds.push(null);\n }\n } else {\n // No rows returned (no RETURNING clause, or SQLite via executeQuery)\n // This is fine - references will use subqueries based on external IDs\n createdInternalIds.push(null);\n }\n } else if (compiledMutation.expectedAffectedRows !== null) {\n // Check affected rows for updates/deletes\n const affectedRows = getAffectedRows(result);\n\n if (affectedRows !== compiledMutation.expectedAffectedRows) {\n // Version conflict detected - the UPDATE/DELETE didn't affect the expected number of rows\n // This means either the row doesn't exist or the version has changed\n throw new Error(\n `Version conflict: expected ${compiledMutation.expectedAffectedRows} rows affected, but got ${affectedRows}`,\n );\n }\n }\n\n if (compiledMutation.expectedReturnedRows !== null) {\n // For SELECT queries (check operations), verify row count\n const rowCount = Array.isArray(result.rows) ? result.rows.length : 0;\n\n if (rowCount !== compiledMutation.expectedReturnedRows) {\n // Version conflict detected - the SELECT didn't return the expected number of rows\n // This means either the row doesn't exist or the version has changed\n throw new Error(\n `Version conflict: expected ${compiledMutation.expectedReturnedRows} rows returned, but got ${rowCount}`,\n );\n }\n }\n }\n });\n\n return { success: true, createdInternalIds };\n } catch (error) {\n // Transaction failed - could be version conflict or other constraint violation\n // Return success=false to indicate the UOW should be retried\n if (error instanceof Error && error.message.includes(\"Version conflict\")) {\n return { success: false };\n }\n\n // Other database errors should be thrown\n throw error;\n }\n}\n"],"mappings":";AAGA,SAAS,gBAAgB,QAAsC;CAC7D,MAAM,eACJ,OAAO,mBACP,OAAO,mBAEN,kBAAkB,WAClB,OAAO,OAAO,oBAAoB,YAAY,OAAO,OAAO,oBAAoB,YAC7E,OAAO,kBACP,YAEH,oBAAoB,WACpB,OAAO,OAAO,sBAAsB,YAAY,OAAO,OAAO,sBAAsB,YACjF,OAAO,oBACP;AAEN,KAAI,iBAAiB,OACnB,OAAM,IAAI,MAAM,2BAA2B,KAAK,UAAU,OAAO,GAAG;AAGtE,KAAI,OAAO,iBAAiB,YAAY,eAAe,OAAO,iBAC5D,OAAM,IAAI,MACR,6BAA6B,aAAa,UAAU,CAAC,gCACtD;AAGH,QAAO,OAAO,aAAa;;;;;;;;;;;;;;;;;;AAmB7B,eAAsB,4BAEpB,QACA,gBACoB;AAEpB,KAAI,eAAe,WAAW,EAC5B,QAAO,EAAE;CAGX,MAAMA,mBAA8B,EAAE;AAGtC,OAAM,OAAO,aAAa,CAAC,QAAQ,OAAO,OAAO;AAC/C,OAAK,MAAM,iBAAiB,gBAAgB;GAC1C,MAAM,SAAS,MAAM,GAAG,aAAa,cAAc;AACnD,oBAAiB,KAAK,OAAO,KAAK;;GAEpC;AAEF,QAAO;;;;;;;;;;;;;;;;;;;;;AAsBT,eAAsB,2BAEpB,QACA,eACyB;AAEzB,KAAI,cAAc,WAAW,EAC3B,QAAO;EAAE,SAAS;EAAM,oBAAoB,EAAE;EAAE;CAGlD,MAAMC,qBAAwC,EAAE;AAGhD,KAAI;AACF,QAAM,OAAO,aAAa,CAAC,QAAQ,OAAO,OAAO;AAC/C,QAAK,MAAM,oBAAoB,eAAe;IAC5C,MAAM,SAAS,MAAM,GAAG,aAAa,iBAAiB,MAAM;AAI5D,QAAI,iBAAiB,yBAAyB,KAC5C,KAAI,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,KAAK,SAAS,GAAG;KACxD,MAAM,MAAM,OAAO,KAAK;AACxB,SAAI,iBAAiB,OAAO,kBAAkB,KAAK;MACjD,MAAM,aAAc,IAAI,kBAAkB,IAAI;AAC9C,yBAAmB,KAAK,WAAW;WAGnC,oBAAmB,KAAK,KAAK;UAK/B,oBAAmB,KAAK,KAAK;aAEtB,iBAAiB,yBAAyB,MAAM;KAEzD,MAAM,eAAe,gBAAgB,OAAO;AAE5C,SAAI,iBAAiB,iBAAiB,qBAGpC,OAAM,IAAI,MACR,8BAA8B,iBAAiB,qBAAqB,0BAA0B,eAC/F;;AAIL,QAAI,iBAAiB,yBAAyB,MAAM;KAElD,MAAM,WAAW,MAAM,QAAQ,OAAO,KAAK,GAAG,OAAO,KAAK,SAAS;AAEnE,SAAI,aAAa,iBAAiB,qBAGhC,OAAM,IAAI,MACR,8BAA8B,iBAAiB,qBAAqB,0BAA0B,WAC/F;;;IAIP;AAEF,SAAO;GAAE,SAAS;GAAM;GAAoB;UACrC,OAAO;AAGd,MAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,mBAAmB,CACtE,QAAO,EAAE,SAAS,OAAO;AAI3B,QAAM"}
@@ -1,5 +1,5 @@
1
1
  import { schemaToDBType } from "../../../schema/serialize.js";
2
- import { SETTINGS_TABLE_NAME } from "../../../shared/settings-schema.js";
2
+ import { SETTINGS_TABLE_NAME } from "../../../fragments/internal-fragment.js";
3
3
  import { sql } from "kysely";
4
4
 
5
5
  //#region src/adapters/kysely/migration/execute-base.ts
@@ -1 +1 @@
1
- {"version":3,"file":"execute-base.js","names":["db: KyselyAny","provider: SQLProvider"],"sources":["../../../../src/adapters/kysely/migration/execute-base.ts"],"sourcesContent":["import { type ColumnBuilderCallback, type Kysely, type RawBuilder, sql } from \"kysely\";\nimport type {\n ColumnInfo,\n MigrationOperation,\n MigrationOperationMetadata,\n} from \"../../../migration-engine/shared\";\nimport type { SQLProvider } from \"../../../shared/providers\";\nimport { schemaToDBType } from \"../../../schema/serialize\";\nimport type { TableNameMapper } from \"../kysely-shared\";\nimport { SETTINGS_TABLE_NAME } from \"../../../shared/settings-schema\";\n\nexport type ExecuteNode = {\n compile(): { sql: string; parameters: readonly unknown[] };\n execute(): Promise<unknown>;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KyselyAny = Kysely<any>;\n\n/**\n * Migration executor interface.\n * Each provider implements this to handle database-specific migration execution.\n */\nexport interface MigrationExecutor<\n TMeta extends MigrationOperationMetadata = MigrationOperationMetadata,\n> {\n /**\n * Preprocess operations before execution.\n * Allows executors to combine, split, or transform operations based on provider capabilities.\n *\n * For example, SQLite can merge add-foreign-key operations into create-table operations.\n */\n preprocessOperations(operations: MigrationOperation[]): MigrationOperation<TMeta>[];\n\n /**\n * Execute a single migration operation.\n */\n executeOperation(\n operation: MigrationOperation<TMeta>,\n mapper?: TableNameMapper,\n ): ExecuteNode | ExecuteNode[];\n}\n\n/**\n * Base migration executor with common functionality.\n * Provider-specific executors should extend this class.\n */\nexport abstract class BaseMigrationExecutor<\n TMeta extends MigrationOperationMetadata = MigrationOperationMetadata,\n> implements MigrationExecutor<TMeta>\n{\n constructor(\n protected readonly db: KyselyAny,\n protected readonly provider: SQLProvider,\n ) {}\n\n /**\n * Default implementation: no preprocessing, no metadata.\n * Providers can override to transform operations.\n */\n preprocessOperations(operations: MigrationOperation[]): MigrationOperation<TMeta>[] {\n return operations as MigrationOperation<TMeta>[];\n }\n\n /**\n * Execute a single migration operation.\n * Must be implemented by provider-specific executors.\n */\n abstract executeOperation(\n operation: MigrationOperation<TMeta>,\n mapper?: TableNameMapper,\n ): ExecuteNode | ExecuteNode[];\n\n /**\n * Get table name, applying namespace mapping if provided.\n * Settings table is never namespaced.\n */\n protected getTableName(tableName: string, mapper?: TableNameMapper): string {\n return tableName === SETTINGS_TABLE_NAME\n ? tableName\n : mapper\n ? mapper.toPhysical(tableName)\n : tableName;\n }\n\n /**\n * Get column builder callback for creating/altering columns.\n */\n protected getColumnBuilderCallback(col: ColumnInfo): ColumnBuilderCallback {\n return (build) => {\n if (!col.isNullable) {\n build = build.notNull();\n }\n\n // Internal ID is the primary key with auto-increment\n if (col.role === \"internal-id\") {\n build = build.primaryKey();\n // Auto-increment for internal ID\n if (this.provider === \"postgresql\" || this.provider === \"cockroachdb\") {\n // SERIAL/BIGSERIAL handles auto-increment\n // Already handled in schemaToDBType\n } else if (this.provider === \"mysql\") {\n build = build.autoIncrement();\n } else if (this.provider === \"sqlite\") {\n build = build.autoIncrement();\n } else if (this.provider === \"mssql\") {\n build = build.identity();\n }\n }\n\n // External ID must be unique\n if (col.role === \"external-id\") {\n build = build.unique();\n }\n\n const defaultValue = this.defaultValueToDB(col);\n if (defaultValue) {\n build = build.defaultTo(defaultValue);\n }\n return build;\n };\n }\n\n /**\n * Convert column default value to database representation.\n */\n protected defaultValueToDB(column: ColumnInfo): RawBuilder<unknown> | undefined {\n const value = column.default;\n if (!value) {\n return undefined;\n }\n\n // MySQL doesn't support default values for TEXT columns\n if (this.provider === \"mysql\" && column.type === \"string\") {\n return undefined;\n }\n\n // Static default values: defaultTo(value)\n if (\"value\" in value && value.value !== undefined) {\n return sql.lit(value.value);\n }\n\n // Database-level special functions: defaultTo(b => b.now())\n if (\"dbSpecial\" in value && value.dbSpecial === \"now\") {\n return sql`CURRENT_TIMESTAMP`;\n }\n\n // Runtime defaults (defaultTo$) are NOT generated in SQL - they're handled in application code\n if (\"runtime\" in value) {\n return undefined;\n }\n\n return undefined;\n }\n\n /**\n * Wrap a raw SQL builder in an ExecuteNode.\n */\n protected rawToNode(raw: RawBuilder<unknown>): ExecuteNode {\n return {\n compile: () => raw.compile(this.db),\n execute: () => raw.execute(this.db),\n };\n }\n\n /**\n * Get the database type string for a column.\n */\n protected getDBType(col: ColumnInfo): string {\n return schemaToDBType(col, this.provider);\n }\n}\n\n// ============================================================================\n// Provider-Specific Helper Functions\n// ============================================================================\n\n/**\n * Returns the appropriate foreign key action based on the provider.\n * MSSQL doesn't support RESTRICT, so we use NO ACTION (functionally equivalent).\n */\nexport function getForeignKeyAction(provider: SQLProvider): \"restrict\" | \"no action\" {\n return provider === \"mssql\" ? \"no action\" : \"restrict\";\n}\n\n/**\n * Generates MSSQL default constraint name following the DF_tableName_columnName pattern.\n */\nexport function getMssqlDefaultConstraintName(tableName: string, columnName: string): string {\n const MSSQL_DEFAULT_CONSTRAINT_PREFIX = \"DF\" as const;\n return `${MSSQL_DEFAULT_CONSTRAINT_PREFIX}_${tableName}_${columnName}`;\n}\n\n/**\n * Generate SQL to drop MSSQL default constraint.\n */\nexport function mssqlDropDefaultConstraint(tableName: string, columnName: string) {\n return sql`\nDECLARE @ConstraintName NVARCHAR(200);\n\nSELECT @ConstraintName = dc.name\nFROM sys.default_constraints dc\nJOIN sys.columns c ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id\nJOIN sys.tables t ON t.object_id = c.object_id\nJOIN sys.schemas s ON t.schema_id = s.schema_id\nWHERE s.name = 'dbo' AND t.name = ${sql.lit(tableName)} AND c.name = ${sql.lit(columnName)};\n\nIF @ConstraintName IS NOT NULL\nBEGIN\n EXEC('ALTER TABLE [dbo].[' + ${sql.lit(tableName)} + '] DROP CONSTRAINT [' + @ConstraintName + ']');\nEND`;\n}\n\n/**\n * Create a unique index with provider-specific handling.\n */\nexport function createUniqueIndex(\n db: KyselyAny,\n name: string,\n tableName: string,\n cols: string[],\n provider: SQLProvider,\n) {\n const query = db.schema.createIndex(name).on(tableName).columns(cols).unique();\n\n if (provider === \"mssql\") {\n // MSSQL: ignore null values in unique indexes by default\n return query.where((b) => {\n return b.and(cols.map((col) => b(col, \"is not\", null)));\n });\n }\n\n return query;\n}\n\n/**\n * Drop a unique index with provider-specific handling.\n */\nexport function dropUniqueIndex(\n db: KyselyAny,\n name: string,\n tableName: string,\n provider: SQLProvider,\n) {\n let query = db.schema.dropIndex(name).ifExists();\n\n if (provider === \"cockroachdb\") {\n query = query.cascade();\n }\n\n if (provider === \"mssql\") {\n query = query.on(tableName);\n }\n\n return query;\n}\n"],"mappings":";;;;;;;;;AA+CA,IAAsB,wBAAtB,MAGA;CACE,YACE,AAAmBA,IACnB,AAAmBC,UACnB;EAFmB;EACA;;;;;;CAOrB,qBAAqB,YAA+D;AAClF,SAAO;;;;;;CAgBT,AAAU,aAAa,WAAmB,QAAkC;AAC1E,SAAO,cAAc,sBACjB,YACA,SACE,OAAO,WAAW,UAAU,GAC5B;;;;;CAMR,AAAU,yBAAyB,KAAwC;AACzE,UAAQ,UAAU;AAChB,OAAI,CAAC,IAAI,WACP,SAAQ,MAAM,SAAS;AAIzB,OAAI,IAAI,SAAS,eAAe;AAC9B,YAAQ,MAAM,YAAY;AAE1B,QAAI,KAAK,aAAa,gBAAgB,KAAK,aAAa,eAAe,YAG5D,KAAK,aAAa,QAC3B,SAAQ,MAAM,eAAe;aACpB,KAAK,aAAa,SAC3B,SAAQ,MAAM,eAAe;aACpB,KAAK,aAAa,QAC3B,SAAQ,MAAM,UAAU;;AAK5B,OAAI,IAAI,SAAS,cACf,SAAQ,MAAM,QAAQ;GAGxB,MAAM,eAAe,KAAK,iBAAiB,IAAI;AAC/C,OAAI,aACF,SAAQ,MAAM,UAAU,aAAa;AAEvC,UAAO;;;;;;CAOX,AAAU,iBAAiB,QAAqD;EAC9E,MAAM,QAAQ,OAAO;AACrB,MAAI,CAAC,MACH;AAIF,MAAI,KAAK,aAAa,WAAW,OAAO,SAAS,SAC/C;AAIF,MAAI,WAAW,SAAS,MAAM,UAAU,OACtC,QAAO,IAAI,IAAI,MAAM,MAAM;AAI7B,MAAI,eAAe,SAAS,MAAM,cAAc,MAC9C,QAAO,GAAG;AAIZ,MAAI,aAAa,MACf;;;;;CASJ,AAAU,UAAU,KAAuC;AACzD,SAAO;GACL,eAAe,IAAI,QAAQ,KAAK,GAAG;GACnC,eAAe,IAAI,QAAQ,KAAK,GAAG;GACpC;;;;;CAMH,AAAU,UAAU,KAAyB;AAC3C,SAAO,eAAe,KAAK,KAAK,SAAS;;;;;;;AAY7C,SAAgB,oBAAoB,UAAiD;AACnF,QAAO,aAAa,UAAU,cAAc;;;;;AAM9C,SAAgB,8BAA8B,WAAmB,YAA4B;AAE3F,QAAO,MAAsC,UAAU,GAAG;;;;;AAM5D,SAAgB,2BAA2B,WAAmB,YAAoB;AAChF,QAAO,GAAG;;;;;;;;oCAQwB,IAAI,IAAI,UAAU,CAAC,gBAAgB,IAAI,IAAI,WAAW,CAAC;;;;mCAIxD,IAAI,IAAI,UAAU,CAAC;;;;;;AAOtD,SAAgB,kBACd,IACA,MACA,WACA,MACA,UACA;CACA,MAAM,QAAQ,GAAG,OAAO,YAAY,KAAK,CAAC,GAAG,UAAU,CAAC,QAAQ,KAAK,CAAC,QAAQ;AAE9E,KAAI,aAAa,QAEf,QAAO,MAAM,OAAO,MAAM;AACxB,SAAO,EAAE,IAAI,KAAK,KAAK,QAAQ,EAAE,KAAK,UAAU,KAAK,CAAC,CAAC;GACvD;AAGJ,QAAO;;;;;AAMT,SAAgB,gBACd,IACA,MACA,WACA,UACA;CACA,IAAI,QAAQ,GAAG,OAAO,UAAU,KAAK,CAAC,UAAU;AAEhD,KAAI,aAAa,cACf,SAAQ,MAAM,SAAS;AAGzB,KAAI,aAAa,QACf,SAAQ,MAAM,GAAG,UAAU;AAG7B,QAAO"}
1
+ {"version":3,"file":"execute-base.js","names":["db: KyselyAny","provider: SQLProvider"],"sources":["../../../../src/adapters/kysely/migration/execute-base.ts"],"sourcesContent":["import { type ColumnBuilderCallback, type Kysely, type RawBuilder, sql } from \"kysely\";\nimport type {\n ColumnInfo,\n MigrationOperation,\n MigrationOperationMetadata,\n} from \"../../../migration-engine/shared\";\nimport type { SQLProvider } from \"../../../shared/providers\";\nimport { schemaToDBType } from \"../../../schema/serialize\";\nimport type { TableNameMapper } from \"../kysely-shared\";\nimport { SETTINGS_TABLE_NAME } from \"../../../fragments/internal-fragment\";\n\nexport type ExecuteNode = {\n compile(): { sql: string; parameters: readonly unknown[] };\n execute(): Promise<unknown>;\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KyselyAny = Kysely<any>;\n\n/**\n * Migration executor interface.\n * Each provider implements this to handle database-specific migration execution.\n */\nexport interface MigrationExecutor<\n TMeta extends MigrationOperationMetadata = MigrationOperationMetadata,\n> {\n /**\n * Preprocess operations before execution.\n * Allows executors to combine, split, or transform operations based on provider capabilities.\n *\n * For example, SQLite can merge add-foreign-key operations into create-table operations.\n */\n preprocessOperations(operations: MigrationOperation[]): MigrationOperation<TMeta>[];\n\n /**\n * Execute a single migration operation.\n */\n executeOperation(\n operation: MigrationOperation<TMeta>,\n mapper?: TableNameMapper,\n ): ExecuteNode | ExecuteNode[];\n}\n\n/**\n * Base migration executor with common functionality.\n * Provider-specific executors should extend this class.\n */\nexport abstract class BaseMigrationExecutor<\n TMeta extends MigrationOperationMetadata = MigrationOperationMetadata,\n> implements MigrationExecutor<TMeta>\n{\n constructor(\n protected readonly db: KyselyAny,\n protected readonly provider: SQLProvider,\n ) {}\n\n /**\n * Default implementation: no preprocessing, no metadata.\n * Providers can override to transform operations.\n */\n preprocessOperations(operations: MigrationOperation[]): MigrationOperation<TMeta>[] {\n return operations as MigrationOperation<TMeta>[];\n }\n\n /**\n * Execute a single migration operation.\n * Must be implemented by provider-specific executors.\n */\n abstract executeOperation(\n operation: MigrationOperation<TMeta>,\n mapper?: TableNameMapper,\n ): ExecuteNode | ExecuteNode[];\n\n /**\n * Get table name, applying namespace mapping if provided.\n * Settings table is never namespaced.\n */\n protected getTableName(tableName: string, mapper?: TableNameMapper): string {\n return tableName === SETTINGS_TABLE_NAME\n ? tableName\n : mapper\n ? mapper.toPhysical(tableName)\n : tableName;\n }\n\n /**\n * Get column builder callback for creating/altering columns.\n */\n protected getColumnBuilderCallback(col: ColumnInfo): ColumnBuilderCallback {\n return (build) => {\n if (!col.isNullable) {\n build = build.notNull();\n }\n\n // Internal ID is the primary key with auto-increment\n if (col.role === \"internal-id\") {\n build = build.primaryKey();\n // Auto-increment for internal ID\n if (this.provider === \"postgresql\" || this.provider === \"cockroachdb\") {\n // SERIAL/BIGSERIAL handles auto-increment\n // Already handled in schemaToDBType\n } else if (this.provider === \"mysql\") {\n build = build.autoIncrement();\n } else if (this.provider === \"sqlite\") {\n build = build.autoIncrement();\n } else if (this.provider === \"mssql\") {\n build = build.identity();\n }\n }\n\n // External ID must be unique\n if (col.role === \"external-id\") {\n build = build.unique();\n }\n\n const defaultValue = this.defaultValueToDB(col);\n if (defaultValue) {\n build = build.defaultTo(defaultValue);\n }\n return build;\n };\n }\n\n /**\n * Convert column default value to database representation.\n */\n protected defaultValueToDB(column: ColumnInfo): RawBuilder<unknown> | undefined {\n const value = column.default;\n if (!value) {\n return undefined;\n }\n\n // MySQL doesn't support default values for TEXT columns\n if (this.provider === \"mysql\" && column.type === \"string\") {\n return undefined;\n }\n\n // Static default values: defaultTo(value)\n if (\"value\" in value && value.value !== undefined) {\n return sql.lit(value.value);\n }\n\n // Database-level special functions: defaultTo(b => b.now())\n if (\"dbSpecial\" in value && value.dbSpecial === \"now\") {\n return sql`CURRENT_TIMESTAMP`;\n }\n\n // Runtime defaults (defaultTo$) are NOT generated in SQL - they're handled in application code\n if (\"runtime\" in value) {\n return undefined;\n }\n\n return undefined;\n }\n\n /**\n * Wrap a raw SQL builder in an ExecuteNode.\n */\n protected rawToNode(raw: RawBuilder<unknown>): ExecuteNode {\n return {\n compile: () => raw.compile(this.db),\n execute: () => raw.execute(this.db),\n };\n }\n\n /**\n * Get the database type string for a column.\n */\n protected getDBType(col: ColumnInfo): string {\n return schemaToDBType(col, this.provider);\n }\n}\n\n// ============================================================================\n// Provider-Specific Helper Functions\n// ============================================================================\n\n/**\n * Returns the appropriate foreign key action based on the provider.\n * MSSQL doesn't support RESTRICT, so we use NO ACTION (functionally equivalent).\n */\nexport function getForeignKeyAction(provider: SQLProvider): \"restrict\" | \"no action\" {\n return provider === \"mssql\" ? \"no action\" : \"restrict\";\n}\n\n/**\n * Generates MSSQL default constraint name following the DF_tableName_columnName pattern.\n */\nexport function getMssqlDefaultConstraintName(tableName: string, columnName: string): string {\n const MSSQL_DEFAULT_CONSTRAINT_PREFIX = \"DF\" as const;\n return `${MSSQL_DEFAULT_CONSTRAINT_PREFIX}_${tableName}_${columnName}`;\n}\n\n/**\n * Generate SQL to drop MSSQL default constraint.\n */\nexport function mssqlDropDefaultConstraint(tableName: string, columnName: string) {\n return sql`\nDECLARE @ConstraintName NVARCHAR(200);\n\nSELECT @ConstraintName = dc.name\nFROM sys.default_constraints dc\nJOIN sys.columns c ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id\nJOIN sys.tables t ON t.object_id = c.object_id\nJOIN sys.schemas s ON t.schema_id = s.schema_id\nWHERE s.name = 'dbo' AND t.name = ${sql.lit(tableName)} AND c.name = ${sql.lit(columnName)};\n\nIF @ConstraintName IS NOT NULL\nBEGIN\n EXEC('ALTER TABLE [dbo].[' + ${sql.lit(tableName)} + '] DROP CONSTRAINT [' + @ConstraintName + ']');\nEND`;\n}\n\n/**\n * Create a unique index with provider-specific handling.\n */\nexport function createUniqueIndex(\n db: KyselyAny,\n name: string,\n tableName: string,\n cols: string[],\n provider: SQLProvider,\n) {\n const query = db.schema.createIndex(name).on(tableName).columns(cols).unique();\n\n if (provider === \"mssql\") {\n // MSSQL: ignore null values in unique indexes by default\n return query.where((b) => {\n return b.and(cols.map((col) => b(col, \"is not\", null)));\n });\n }\n\n return query;\n}\n\n/**\n * Drop a unique index with provider-specific handling.\n */\nexport function dropUniqueIndex(\n db: KyselyAny,\n name: string,\n tableName: string,\n provider: SQLProvider,\n) {\n let query = db.schema.dropIndex(name).ifExists();\n\n if (provider === \"cockroachdb\") {\n query = query.cascade();\n }\n\n if (provider === \"mssql\") {\n query = query.on(tableName);\n }\n\n return query;\n}\n"],"mappings":";;;;;;;;;AA+CA,IAAsB,wBAAtB,MAGA;CACE,YACE,AAAmBA,IACnB,AAAmBC,UACnB;EAFmB;EACA;;;;;;CAOrB,qBAAqB,YAA+D;AAClF,SAAO;;;;;;CAgBT,AAAU,aAAa,WAAmB,QAAkC;AAC1E,SAAO,cAAc,sBACjB,YACA,SACE,OAAO,WAAW,UAAU,GAC5B;;;;;CAMR,AAAU,yBAAyB,KAAwC;AACzE,UAAQ,UAAU;AAChB,OAAI,CAAC,IAAI,WACP,SAAQ,MAAM,SAAS;AAIzB,OAAI,IAAI,SAAS,eAAe;AAC9B,YAAQ,MAAM,YAAY;AAE1B,QAAI,KAAK,aAAa,gBAAgB,KAAK,aAAa,eAAe,YAG5D,KAAK,aAAa,QAC3B,SAAQ,MAAM,eAAe;aACpB,KAAK,aAAa,SAC3B,SAAQ,MAAM,eAAe;aACpB,KAAK,aAAa,QAC3B,SAAQ,MAAM,UAAU;;AAK5B,OAAI,IAAI,SAAS,cACf,SAAQ,MAAM,QAAQ;GAGxB,MAAM,eAAe,KAAK,iBAAiB,IAAI;AAC/C,OAAI,aACF,SAAQ,MAAM,UAAU,aAAa;AAEvC,UAAO;;;;;;CAOX,AAAU,iBAAiB,QAAqD;EAC9E,MAAM,QAAQ,OAAO;AACrB,MAAI,CAAC,MACH;AAIF,MAAI,KAAK,aAAa,WAAW,OAAO,SAAS,SAC/C;AAIF,MAAI,WAAW,SAAS,MAAM,UAAU,OACtC,QAAO,IAAI,IAAI,MAAM,MAAM;AAI7B,MAAI,eAAe,SAAS,MAAM,cAAc,MAC9C,QAAO,GAAG;AAIZ,MAAI,aAAa,MACf;;;;;CASJ,AAAU,UAAU,KAAuC;AACzD,SAAO;GACL,eAAe,IAAI,QAAQ,KAAK,GAAG;GACnC,eAAe,IAAI,QAAQ,KAAK,GAAG;GACpC;;;;;CAMH,AAAU,UAAU,KAAyB;AAC3C,SAAO,eAAe,KAAK,KAAK,SAAS;;;;;;;AAY7C,SAAgB,oBAAoB,UAAiD;AACnF,QAAO,aAAa,UAAU,cAAc;;;;;AAM9C,SAAgB,8BAA8B,WAAmB,YAA4B;AAE3F,QAAO,MAAsC,UAAU,GAAG;;;;;AAM5D,SAAgB,2BAA2B,WAAmB,YAAoB;AAChF,QAAO,GAAG;;;;;;;;oCAQwB,IAAI,IAAI,UAAU,CAAC,gBAAgB,IAAI,IAAI,WAAW,CAAC;;;;mCAIxD,IAAI,IAAI,UAAU,CAAC;;;;;;AAOtD,SAAgB,kBACd,IACA,MACA,WACA,MACA,UACA;CACA,MAAM,QAAQ,GAAG,OAAO,YAAY,KAAK,CAAC,GAAG,UAAU,CAAC,QAAQ,KAAK,CAAC,QAAQ;AAE9E,KAAI,aAAa,QAEf,QAAO,MAAM,OAAO,MAAM;AACxB,SAAO,EAAE,IAAI,KAAK,KAAK,QAAQ,EAAE,KAAK,UAAU,KAAK,CAAC,CAAC;GACvD;AAGJ,QAAO;;;;;AAMT,SAAgB,gBACd,IACA,MACA,WACA,UACA;CACA,IAAI,QAAQ,GAAG,OAAO,UAAU,KAAK,CAAC,UAAU;AAEhD,KAAI,aAAa,cACf,SAAQ,MAAM,SAAS;AAGzB,KAAI,aAAa,QACf,SAAQ,MAAM,GAAG,UAAU;AAG7B,QAAO"}
@@ -0,0 +1,152 @@
1
+ import { AnySchema } from "./schema/create.js";
2
+ import { IUnitOfWork, TypedUnitOfWork } from "./query/unit-of-work.js";
3
+ import { AbstractQuery } from "./query/query.js";
4
+ import { DatabaseAdapter } from "./adapters/adapters.js";
5
+ import { AwaitedPromisesInObject, ExecuteRestrictedUnitOfWorkOptions } from "./query/execute-unit-of-work.js";
6
+ import { FragmentDefinition, FragmentDefinitionBuilder, FragnoPublicConfig, RequestThisContext, ServiceConstructorFn } from "@fragno-dev/core";
7
+
8
+ //#region src/db-fragment-definition-builder.d.ts
9
+
10
+ /**
11
+ * Extended FragnoPublicConfig that includes a database adapter.
12
+ * Use this type when creating fragments with database support.
13
+ */
14
+ type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {
15
+ databaseAdapter: DatabaseAdapter<any>;
16
+ };
17
+ /**
18
+ * Implicit dependencies that database fragments get automatically.
19
+ * These are injected without requiring explicit configuration.
20
+ */
21
+ type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {
22
+ /**
23
+ * Database query engine for the fragment's schema.
24
+ */
25
+ db: AbstractQuery<TSchema>;
26
+ /**
27
+ * The schema definition for this fragment.
28
+ */
29
+ schema: TSchema;
30
+ /**
31
+ * The database namespace for this fragment.
32
+ */
33
+ namespace: string;
34
+ /**
35
+ * Create a new Unit of Work for database operations.
36
+ */
37
+ createUnitOfWork: () => IUnitOfWork;
38
+ };
39
+ /**
40
+ * Service context for database fragments - provides restricted UOW access without execute methods.
41
+ */
42
+ type DatabaseServiceContext = RequestThisContext & {
43
+ /**
44
+ * Get a typed, restricted Unit of Work for the given schema.
45
+ * @param schema - Schema to get a typed view for
46
+ * @returns TypedUnitOfWork (restricted version without execute methods)
47
+ */
48
+ uow<TSchema extends AnySchema>(schema: TSchema): TypedUnitOfWork<TSchema, [], unknown>;
49
+ };
50
+ /**
51
+ * Handler context for database fragments - provides UOW execution with automatic retry support.
52
+ */
53
+ type DatabaseHandlerContext = RequestThisContext & {
54
+ /**
55
+ * Execute a Unit of Work with explicit phase control and automatic retry support.
56
+ * This method provides an API where users call forSchema to create a schema-specific
57
+ * UOW, then call executeRetrieve() and executeMutate() to execute the phases. The entire
58
+ * callback is re-executed on optimistic concurrency conflicts, ensuring retries work properly.
59
+ * Automatically provides the UOW factory from context.
60
+ * Promises in the returned object are awaited 1 level deep.
61
+ *
62
+ * @param callback - Async function that receives a context with forSchema, executeRetrieve, executeMutate, nonce, and currentAttempt
63
+ * @param options - Optional configuration for retry policy and abort signal
64
+ * @returns Promise resolving to the callback's return value with promises awaited 1 level deep
65
+ * @throws Error if retries are exhausted or callback throws an error
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const result = await this.uow(async ({ forSchema, executeRetrieve, executeMutate, nonce, currentAttempt }) => {
70
+ * const uow = forSchema(schema);
71
+ * const userId = uow.create("users", { name: "John" });
72
+ *
73
+ * // Execute retrieval phase
74
+ * await executeRetrieve();
75
+ *
76
+ * const profileId = uow.create("profiles", { userId });
77
+ *
78
+ * // Execute mutation phase
79
+ * await executeMutate();
80
+ *
81
+ * return { userId, profileId };
82
+ * });
83
+ * ```
84
+ */
85
+ uow<TResult>(callback: (context: {
86
+ forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;
87
+ executeRetrieve: () => Promise<void>;
88
+ executeMutate: () => Promise<void>;
89
+ nonce: string;
90
+ currentAttempt: number;
91
+ }) => Promise<TResult> | TResult, options?: Omit<ExecuteRestrictedUnitOfWorkOptions, "createUnitOfWork">): Promise<AwaitedPromisesInObject<TResult>>;
92
+ };
93
+ /**
94
+ * Database fragment context provided to user callbacks.
95
+ */
96
+ type DatabaseFragmentContext<TSchema extends AnySchema> = {
97
+ /**
98
+ * Database adapter instance.
99
+ */
100
+ databaseAdapter: DatabaseAdapter<any>;
101
+ /**
102
+ * ORM query engine for the fragment's schema.
103
+ */
104
+ db: AbstractQuery<TSchema>;
105
+ };
106
+ /**
107
+ * Storage type for database fragments - stores the Unit of Work.
108
+ */
109
+ type DatabaseRequestStorage = {
110
+ uow: IUnitOfWork;
111
+ };
112
+ /**
113
+ * Builder for database fragments that wraps the core fragment builder
114
+ * and provides database-specific functionality.
115
+ *
116
+ * Database fragments always require FragnoPublicConfigWithDatabase (which includes databaseAdapter).
117
+ */
118
+ declare class DatabaseFragmentDefinitionBuilder<TSchema extends AnySchema, TConfig, TDeps, TBaseServices, TServices, TServiceDependencies, TPrivateServices, TServiceThisContext extends RequestThisContext = DatabaseHandlerContext, THandlerThisContext extends RequestThisContext = DatabaseHandlerContext> {
119
+ #private;
120
+ constructor(baseBuilder: FragmentDefinitionBuilder<TConfig, FragnoPublicConfigWithDatabase, TDeps, TBaseServices, TServices, TServiceDependencies, TPrivateServices, TServiceThisContext, THandlerThisContext, DatabaseRequestStorage>, schema: TSchema, namespace?: string);
121
+ /**
122
+ * Define dependencies for this database fragment.
123
+ * The context includes database adapter and ORM instance.
124
+ */
125
+ withDependencies<TNewDeps>(fn: (context: {
126
+ config: TConfig;
127
+ options: FragnoPublicConfigWithDatabase;
128
+ db: AbstractQuery<TSchema>;
129
+ databaseAdapter: DatabaseAdapter<any>;
130
+ }) => TNewDeps): DatabaseFragmentDefinitionBuilder<TSchema, TConfig, TNewDeps & ImplicitDatabaseDependencies<TSchema>, {}, {}, TServiceDependencies, {}, TServiceThisContext, THandlerThisContext>;
131
+ providesBaseService<TNewService>(fn: ServiceConstructorFn<TConfig, FragnoPublicConfigWithDatabase, TDeps, TServiceDependencies, TPrivateServices, TNewService, TServiceThisContext>): DatabaseFragmentDefinitionBuilder<TSchema, TConfig, TDeps, TNewService, TServices, TServiceDependencies, TPrivateServices, TServiceThisContext, THandlerThisContext>;
132
+ providesService<TServiceName extends string, TService>(serviceName: TServiceName, fn: ServiceConstructorFn<TConfig, FragnoPublicConfigWithDatabase, TDeps, TServiceDependencies, TPrivateServices, TService, TServiceThisContext>): DatabaseFragmentDefinitionBuilder<TSchema, TConfig, TDeps, TBaseServices, TServices & { [K in TServiceName]: TService }, TServiceDependencies, TPrivateServices, TServiceThisContext, THandlerThisContext>;
133
+ /**
134
+ * Declare that this fragment uses a required service provided by the runtime.
135
+ * Delegates to the base builder.
136
+ */
137
+ usesService<TServiceName extends string, TService>(serviceName: TServiceName): DatabaseFragmentDefinitionBuilder<TSchema, TConfig, TDeps, TBaseServices, TServices, TServiceDependencies & { [K in TServiceName]: TService }, TPrivateServices, TServiceThisContext, THandlerThisContext>;
138
+ /**
139
+ * Declare that this fragment uses an optional service provided by the runtime.
140
+ * Delegates to the base builder.
141
+ */
142
+ usesOptionalService<TServiceName extends string, TService>(serviceName: TServiceName): DatabaseFragmentDefinitionBuilder<TSchema, TConfig, TDeps, TBaseServices, TServices, TServiceDependencies & { [K in TServiceName]: TService | undefined }, TPrivateServices, TServiceThisContext, THandlerThisContext>;
143
+ /**
144
+ * Build the final database fragment definition.
145
+ * This includes the request context setup for UnitOfWork management.
146
+ * Note: TDeps already includes ImplicitDatabaseDependencies from withDatabase().
147
+ */
148
+ build(): FragmentDefinition<TConfig, FragnoPublicConfigWithDatabase, TDeps, TBaseServices, TServices, TServiceDependencies, TPrivateServices, DatabaseServiceContext, DatabaseHandlerContext, DatabaseRequestStorage>;
149
+ }
150
+ //#endregion
151
+ export { DatabaseFragmentContext, DatabaseFragmentDefinitionBuilder, DatabaseHandlerContext, DatabaseRequestStorage, DatabaseServiceContext, FragnoPublicConfigWithDatabase, ImplicitDatabaseDependencies };
152
+ //# sourceMappingURL=db-fragment-definition-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-fragment-definition-builder.d.ts","names":[],"sources":["../src/db-fragment-definition-builder.ts"],"sourcesContent":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ import "./query/unit-of-work.js";
2
+ import { executeRestrictedUnitOfWork } from "./query/execute-unit-of-work.js";
3
+
4
+ //#region src/db-fragment-definition-builder.ts
5
+ /**
6
+ * Create database context from options.
7
+ * This extracts the database adapter and creates the ORM instance.
8
+ */
9
+ function createDatabaseContext(options, schema, namespace) {
10
+ const databaseAdapter = options.databaseAdapter;
11
+ if (!databaseAdapter) throw new Error("Database fragment requires a database adapter to be provided in options.databaseAdapter");
12
+ return {
13
+ databaseAdapter,
14
+ db: databaseAdapter.createQueryEngine(schema, namespace)
15
+ };
16
+ }
17
+ /**
18
+ * Builder for database fragments that wraps the core fragment builder
19
+ * and provides database-specific functionality.
20
+ *
21
+ * Database fragments always require FragnoPublicConfigWithDatabase (which includes databaseAdapter).
22
+ */
23
+ var DatabaseFragmentDefinitionBuilder = class DatabaseFragmentDefinitionBuilder {
24
+ #baseBuilder;
25
+ #schema;
26
+ #namespace;
27
+ constructor(baseBuilder, schema, namespace) {
28
+ this.#baseBuilder = baseBuilder;
29
+ this.#schema = schema;
30
+ this.#namespace = namespace ?? baseBuilder.name + "-db";
31
+ }
32
+ /**
33
+ * Define dependencies for this database fragment.
34
+ * The context includes database adapter and ORM instance.
35
+ */
36
+ withDependencies(fn) {
37
+ const wrappedFn = (context) => {
38
+ const dbContext = createDatabaseContext(context.options, this.#schema, this.#namespace);
39
+ const userDeps = fn({
40
+ config: context.config,
41
+ options: context.options,
42
+ db: dbContext.db,
43
+ databaseAdapter: dbContext.databaseAdapter
44
+ });
45
+ const createUow = () => dbContext.db.createUnitOfWork();
46
+ const implicitDeps = {
47
+ db: dbContext.db,
48
+ schema: this.#schema,
49
+ namespace: this.#namespace,
50
+ createUnitOfWork: createUow
51
+ };
52
+ return {
53
+ ...userDeps,
54
+ ...implicitDeps
55
+ };
56
+ };
57
+ return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.withDependencies(wrappedFn), this.#schema, this.#namespace);
58
+ }
59
+ providesBaseService(fn) {
60
+ return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.providesBaseService(fn), this.#schema, this.#namespace);
61
+ }
62
+ providesService(serviceName, fn) {
63
+ return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.providesService(serviceName, fn), this.#schema, this.#namespace);
64
+ }
65
+ /**
66
+ * Declare that this fragment uses a required service provided by the runtime.
67
+ * Delegates to the base builder.
68
+ */
69
+ usesService(serviceName) {
70
+ return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.usesService(serviceName), this.#schema, this.#namespace);
71
+ }
72
+ /**
73
+ * Declare that this fragment uses an optional service provided by the runtime.
74
+ * Delegates to the base builder.
75
+ */
76
+ usesOptionalService(serviceName) {
77
+ return new DatabaseFragmentDefinitionBuilder(this.#baseBuilder.usesOptionalService(serviceName), this.#schema, this.#namespace);
78
+ }
79
+ /**
80
+ * Build the final database fragment definition.
81
+ * This includes the request context setup for UnitOfWork management.
82
+ * Note: TDeps already includes ImplicitDatabaseDependencies from withDatabase().
83
+ */
84
+ build() {
85
+ const dependencies = (context) => {
86
+ const userDeps = this.#baseBuilder.build().dependencies?.(context);
87
+ const { db } = createDatabaseContext(context.options, this.#schema, this.#namespace);
88
+ const implicitDeps = {
89
+ db,
90
+ schema: this.#schema,
91
+ namespace: this.#namespace,
92
+ createUnitOfWork: () => db.createUnitOfWork()
93
+ };
94
+ return {
95
+ ...userDeps,
96
+ ...implicitDeps
97
+ };
98
+ };
99
+ return {
100
+ ...this.#baseBuilder.withExternalRequestStorage(({ options }) => {
101
+ return createDatabaseContext(options, this.#schema, this.#namespace).databaseAdapter.contextStorage;
102
+ }).withRequestStorage(({ options }) => {
103
+ return { uow: createDatabaseContext(options, this.#schema, this.#namespace).db.createUnitOfWork() };
104
+ }).withThisContext(({ storage }) => {
105
+ function forSchema(schema) {
106
+ const uow$1 = storage.getStore()?.uow;
107
+ if (!uow$1) throw new Error("No UnitOfWork in context. Service must be called within a route handler OR using `withUnitOfWork`.");
108
+ return uow$1.restrict().forSchema(schema);
109
+ }
110
+ const serviceContext = { uow: forSchema };
111
+ async function uow(callback, options) {
112
+ const currentStorage = storage.getStore();
113
+ if (!currentStorage) throw new Error("No storage in context. Handler must be called within a request context.");
114
+ const wrappedCallback = async (context) => {
115
+ return await callback(context);
116
+ };
117
+ return executeRestrictedUnitOfWork(wrappedCallback, {
118
+ ...options,
119
+ createUnitOfWork: () => {
120
+ currentStorage.uow.reset();
121
+ return currentStorage.uow;
122
+ }
123
+ });
124
+ }
125
+ return {
126
+ serviceContext,
127
+ handlerContext: { uow }
128
+ };
129
+ }).build(),
130
+ dependencies
131
+ };
132
+ }
133
+ };
134
+
135
+ //#endregion
136
+ export { DatabaseFragmentDefinitionBuilder };
137
+ //# sourceMappingURL=db-fragment-definition-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-fragment-definition-builder.js","names":["#baseBuilder","#schema","#namespace","implicitDeps: ImplicitDatabaseDependencies<TSchema>","uow","serviceContext: DatabaseServiceContext"],"sources":["../src/db-fragment-definition-builder.ts"],"sourcesContent":["import type { AnySchema } from \"./schema/create\";\nimport type { AbstractQuery } from \"./query/query\";\nimport type { DatabaseAdapter } from \"./adapters/adapters\";\nimport type { IUnitOfWork } from \"./query/unit-of-work\";\nimport { TypedUnitOfWork, UnitOfWork } from \"./query/unit-of-work\";\nimport type { RequestThisContext, FragnoPublicConfig } from \"@fragno-dev/core\";\nimport {\n FragmentDefinitionBuilder,\n type FragmentDefinition,\n type ServiceConstructorFn,\n} from \"@fragno-dev/core\";\nimport {\n executeRestrictedUnitOfWork,\n type AwaitedPromisesInObject,\n type ExecuteRestrictedUnitOfWorkOptions,\n} from \"./query/execute-unit-of-work\";\n\n/**\n * Extended FragnoPublicConfig that includes a database adapter.\n * Use this type when creating fragments with database support.\n */\nexport type FragnoPublicConfigWithDatabase = FragnoPublicConfig & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n databaseAdapter: DatabaseAdapter<any>;\n};\n\n/**\n * Implicit dependencies that database fragments get automatically.\n * These are injected without requiring explicit configuration.\n */\nexport type ImplicitDatabaseDependencies<TSchema extends AnySchema> = {\n /**\n * Database query engine for the fragment's schema.\n */\n db: AbstractQuery<TSchema>;\n /**\n * The schema definition for this fragment.\n */\n schema: TSchema;\n /**\n * The database namespace for this fragment.\n */\n namespace: string;\n /**\n * Create a new Unit of Work for database operations.\n */\n createUnitOfWork: () => IUnitOfWork;\n};\n\n/**\n * Service context for database fragments - provides restricted UOW access without execute methods.\n */\nexport type DatabaseServiceContext = RequestThisContext & {\n /**\n * Get a typed, restricted Unit of Work for the given schema.\n * @param schema - Schema to get a typed view for\n * @returns TypedUnitOfWork (restricted version without execute methods)\n */\n uow<TSchema extends AnySchema>(schema: TSchema): TypedUnitOfWork<TSchema, [], unknown>;\n};\n\n/**\n * Handler context for database fragments - provides UOW execution with automatic retry support.\n */\nexport type DatabaseHandlerContext = RequestThisContext & {\n /**\n * Execute a Unit of Work with explicit phase control and automatic retry support.\n * This method provides an API where users call forSchema to create a schema-specific\n * UOW, then call executeRetrieve() and executeMutate() to execute the phases. The entire\n * callback is re-executed on optimistic concurrency conflicts, ensuring retries work properly.\n * Automatically provides the UOW factory from context.\n * Promises in the returned object are awaited 1 level deep.\n *\n * @param callback - Async function that receives a context with forSchema, executeRetrieve, executeMutate, nonce, and currentAttempt\n * @param options - Optional configuration for retry policy and abort signal\n * @returns Promise resolving to the callback's return value with promises awaited 1 level deep\n * @throws Error if retries are exhausted or callback throws an error\n *\n * @example\n * ```ts\n * const result = await this.uow(async ({ forSchema, executeRetrieve, executeMutate, nonce, currentAttempt }) => {\n * const uow = forSchema(schema);\n * const userId = uow.create(\"users\", { name: \"John\" });\n *\n * // Execute retrieval phase\n * await executeRetrieve();\n *\n * const profileId = uow.create(\"profiles\", { userId });\n *\n * // Execute mutation phase\n * await executeMutate();\n *\n * return { userId, profileId };\n * });\n * ```\n */\n uow<TResult>(\n callback: (context: {\n forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;\n executeRetrieve: () => Promise<void>;\n executeMutate: () => Promise<void>;\n nonce: string;\n currentAttempt: number;\n }) => Promise<TResult> | TResult,\n options?: Omit<ExecuteRestrictedUnitOfWorkOptions, \"createUnitOfWork\">,\n ): Promise<AwaitedPromisesInObject<TResult>>;\n};\n\n/**\n * Database fragment context provided to user callbacks.\n */\nexport type DatabaseFragmentContext<TSchema extends AnySchema> = {\n /**\n * Database adapter instance.\n */\n databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n /**\n * ORM query engine for the fragment's schema.\n */\n db: AbstractQuery<TSchema>;\n};\n\n/**\n * Create database context from options.\n * This extracts the database adapter and creates the ORM instance.\n */\nfunction createDatabaseContext<TSchema extends AnySchema>(\n options: FragnoPublicConfigWithDatabase,\n schema: TSchema,\n namespace: string,\n): DatabaseFragmentContext<TSchema> {\n const databaseAdapter = options.databaseAdapter;\n\n if (!databaseAdapter) {\n throw new Error(\n \"Database fragment requires a database adapter to be provided in options.databaseAdapter\",\n );\n }\n\n const db = databaseAdapter.createQueryEngine(schema, namespace);\n\n return { databaseAdapter, db };\n}\n\n/**\n * Storage type for database fragments - stores the Unit of Work.\n */\nexport type DatabaseRequestStorage = {\n uow: IUnitOfWork;\n};\n\n/**\n * Builder for database fragments that wraps the core fragment builder\n * and provides database-specific functionality.\n *\n * Database fragments always require FragnoPublicConfigWithDatabase (which includes databaseAdapter).\n */\nexport class DatabaseFragmentDefinitionBuilder<\n TSchema extends AnySchema,\n TConfig,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext extends RequestThisContext = DatabaseHandlerContext,\n THandlerThisContext extends RequestThisContext = DatabaseHandlerContext,\n> {\n // Store the base builder - we'll replace its storage and context setup when building\n #baseBuilder: FragmentDefinitionBuilder<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n DatabaseRequestStorage\n >;\n #schema: TSchema;\n #namespace: string;\n\n constructor(\n baseBuilder: FragmentDefinitionBuilder<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n DatabaseRequestStorage\n >,\n schema: TSchema,\n namespace?: string,\n ) {\n this.#baseBuilder = baseBuilder;\n this.#schema = schema;\n this.#namespace = namespace ?? baseBuilder.name + \"-db\";\n }\n\n /**\n * Define dependencies for this database fragment.\n * The context includes database adapter and ORM instance.\n */\n withDependencies<TNewDeps>(\n fn: (context: {\n config: TConfig;\n options: FragnoPublicConfigWithDatabase;\n db: AbstractQuery<TSchema>;\n databaseAdapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n }) => TNewDeps,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TNewDeps & ImplicitDatabaseDependencies<TSchema>,\n {},\n {},\n TServiceDependencies,\n {},\n TServiceThisContext,\n THandlerThisContext\n > {\n // Wrap user function to inject DB context\n const wrappedFn = (context: { config: TConfig; options: FragnoPublicConfigWithDatabase }) => {\n const dbContext = createDatabaseContext(context.options, this.#schema, this.#namespace);\n\n // Call user function with enriched context\n const userDeps = fn({\n config: context.config,\n options: context.options,\n db: dbContext.db,\n databaseAdapter: dbContext.databaseAdapter,\n });\n\n // Create implicit dependencies\n const createUow = () => dbContext.db.createUnitOfWork();\n const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {\n db: dbContext.db,\n schema: this.#schema,\n namespace: this.#namespace,\n createUnitOfWork: createUow,\n };\n\n return {\n ...userDeps,\n ...implicitDeps,\n };\n };\n\n // Create new base builder with wrapped function\n const newBaseBuilder = this.#baseBuilder.withDependencies(wrappedFn);\n\n // Return new database builder with updated base\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n providesBaseService<TNewService>(\n fn: ServiceConstructorFn<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TServiceDependencies,\n TPrivateServices,\n TNewService,\n TServiceThisContext\n >,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TDeps,\n TNewService,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext\n > {\n const newBaseBuilder = this.#baseBuilder.providesBaseService<TNewService>(fn);\n\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n providesService<TServiceName extends string, TService>(\n serviceName: TServiceName,\n fn: ServiceConstructorFn<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TServiceDependencies,\n TPrivateServices,\n TService,\n TServiceThisContext\n >,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TDeps,\n TBaseServices,\n TServices & { [K in TServiceName]: TService },\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext\n > {\n const newBaseBuilder = this.#baseBuilder.providesService<TServiceName, TService>(\n serviceName,\n fn,\n );\n\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n /**\n * Declare that this fragment uses a required service provided by the runtime.\n * Delegates to the base builder.\n */\n usesService<TServiceName extends string, TService>(\n serviceName: TServiceName,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies & { [K in TServiceName]: TService },\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext\n > {\n const newBaseBuilder = this.#baseBuilder.usesService<TServiceName, TService>(serviceName);\n\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n /**\n * Declare that this fragment uses an optional service provided by the runtime.\n * Delegates to the base builder.\n */\n usesOptionalService<TServiceName extends string, TService>(\n serviceName: TServiceName,\n ): DatabaseFragmentDefinitionBuilder<\n TSchema,\n TConfig,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies & { [K in TServiceName]: TService | undefined },\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext\n > {\n const newBaseBuilder = this.#baseBuilder.usesOptionalService<TServiceName, TService>(\n serviceName,\n );\n\n return new DatabaseFragmentDefinitionBuilder(newBaseBuilder, this.#schema, this.#namespace);\n }\n\n /**\n * Build the final database fragment definition.\n * This includes the request context setup for UnitOfWork management.\n * Note: TDeps already includes ImplicitDatabaseDependencies from withDatabase().\n */\n build(): FragmentDefinition<\n TConfig,\n FragnoPublicConfigWithDatabase,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n DatabaseServiceContext,\n DatabaseHandlerContext,\n DatabaseRequestStorage\n > {\n // Ensure dependencies callback always exists for database fragments\n // If no user dependencies were defined, create a minimal one that only adds implicit deps\n const dependencies = (context: {\n config: TConfig;\n options: FragnoPublicConfigWithDatabase;\n }): TDeps => {\n const baseDef = this.#baseBuilder.build();\n const userDeps = baseDef.dependencies?.(context);\n\n const { db } = createDatabaseContext(context.options, this.#schema, this.#namespace);\n\n const implicitDeps: ImplicitDatabaseDependencies<TSchema> = {\n db,\n schema: this.#schema,\n namespace: this.#namespace,\n createUnitOfWork: () => db.createUnitOfWork(),\n };\n\n return {\n ...userDeps,\n ...implicitDeps,\n } as TDeps;\n };\n\n // Use the adapter's shared context storage (all fragments using the same adapter share this storage)\n const builderWithExternalStorage = this.#baseBuilder.withExternalRequestStorage(\n ({ options }) => {\n const dbContext = createDatabaseContext(options, this.#schema, this.#namespace);\n return dbContext.databaseAdapter.contextStorage;\n },\n );\n\n // Set up request storage to initialize the Unit of Work\n const builderWithStorage = builderWithExternalStorage.withRequestStorage(\n ({ options }): DatabaseRequestStorage => {\n // Create database context - needed here to create the UOW\n const dbContextForStorage = createDatabaseContext(options, this.#schema, this.#namespace);\n\n // Create a new Unit of Work for this request\n const uow: IUnitOfWork = dbContextForStorage.db.createUnitOfWork();\n\n return { uow };\n },\n );\n\n // Services get restricted context (no execute methods), handlers get execution context\n const builderWithContext = builderWithStorage.withThisContext<\n DatabaseServiceContext,\n DatabaseHandlerContext\n >(({ storage }) => {\n // Service context - forSchema method to get restricted typed UOW\n function forSchema<TSchema extends AnySchema>(\n schema: TSchema,\n ): TypedUnitOfWork<TSchema, [], unknown> {\n const uow = storage.getStore()?.uow;\n if (!uow) {\n throw new Error(\n \"No UnitOfWork in context. Service must be called within a route handler OR using `withUnitOfWork`.\",\n );\n }\n\n // Return typed view of restricted UOW\n return uow.restrict().forSchema(schema);\n }\n\n const serviceContext: DatabaseServiceContext = {\n uow: forSchema,\n };\n\n // Handler context - only executeRestrictedUnitOfWork\n async function uow<TResult>(\n callback: (context: {\n forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;\n executeRetrieve: () => Promise<void>;\n executeMutate: () => Promise<void>;\n nonce: string;\n currentAttempt: number;\n }) => Promise<TResult> | TResult,\n options?: Omit<ExecuteRestrictedUnitOfWorkOptions, \"createUnitOfWork\">,\n ): Promise<AwaitedPromisesInObject<TResult>> {\n const currentStorage = storage.getStore();\n if (!currentStorage) {\n throw new Error(\n \"No storage in context. Handler must be called within a request context.\",\n );\n }\n\n // Wrap callback to ensure it always returns a Promise\n const wrappedCallback = async (context: {\n forSchema: <S extends AnySchema>(schema: S) => TypedUnitOfWork<S, [], unknown>;\n executeRetrieve: () => Promise<void>;\n executeMutate: () => Promise<void>;\n nonce: string;\n currentAttempt: number;\n }): Promise<TResult> => {\n return await callback(context);\n };\n\n // Use the UOW from storage - reset it before each attempt for retry support\n // Cast is safe because IUnitOfWork is actually implemented by UnitOfWork\n return executeRestrictedUnitOfWork(wrappedCallback, {\n ...options,\n createUnitOfWork: () => {\n currentStorage.uow.reset();\n return currentStorage.uow as UnitOfWork;\n },\n });\n }\n\n const handlerContext: DatabaseHandlerContext = {\n uow,\n };\n\n return { serviceContext, handlerContext };\n });\n\n // Build the final definition\n const finalDef = builderWithContext.build();\n\n // Return the complete definition with proper typing and dependencies\n return {\n ...finalDef,\n dependencies,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { AnyColumn, AnyRelation, Column, FragnoId, IdColumn, Index, Schema, Table } from "../schema/create.js";
2
+ import { DatabaseHandlerContext, DatabaseRequestStorage, DatabaseServiceContext, FragnoPublicConfigWithDatabase, ImplicitDatabaseDependencies } from "../db-fragment-definition-builder.js";
3
+ import * as _fragno_dev_core0 from "@fragno-dev/core";
4
+
5
+ //#region src/fragments/internal-fragment.d.ts
6
+ declare const internalFragmentDef: _fragno_dev_core0.FragmentDefinition<{}, FragnoPublicConfigWithDatabase, ImplicitDatabaseDependencies<Schema<Record<"fragno_db_settings", Table<Record<string, AnyColumn> & Record<"id", IdColumn<"varchar(30)", string | FragnoId | null, FragnoId>> & Record<"key", Column<"string", string, string>> & Record<"value", Column<"string", string, string>>, Record<string, AnyRelation>, Record<string, Index<AnyColumn[], readonly string[]>> & Record<"unique_key", Index<readonly [Column<"string", string, string>] & AnyColumn[], readonly ["key"]>>>>>>, {}, {
7
+ settingsService: {
8
+ get(key: string): Promise<{
9
+ id: FragnoId;
10
+ key: string;
11
+ value: string;
12
+ } | undefined>;
13
+ set(key: string, value: string): Promise<void>;
14
+ delete(id: FragnoId): Promise<void>;
15
+ };
16
+ }, {}, {}, DatabaseServiceContext, DatabaseHandlerContext, DatabaseRequestStorage, {}>;
17
+ //#endregion
18
+ export { internalFragmentDef };
19
+ //# sourceMappingURL=internal-fragment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-fragment.d.ts","names":[],"sources":["../../src/fragments/internal-fragment.ts"],"sourcesContent":[],"mappings":";;;;;AA6Ba,cAAA,mBAuDH,oBAvDsB,kBAuDtB,CAAA,CAAA,CAAA,EAvDsB,8BAuDtB,EAvDsB,4BAuDtB,CAvDsB,MAuDtB,CAvDsB,MAuDtB,CAAA,oBAAA,EAvDsB,KAuDtB,CAvDsB,MAuDtB,CAAA,MAAA,EAvDsB,SAAA,CAuDtB,GAvDsB,MAuDtB,CAAA,IAAA,EAvDsB,QAuDtB,CAAA,aAAA,EAAA,MAAA,GAvDsB,QAuDtB,GAAA,IAAA,EAvDsB,QAuDtB,CAAA,CAAA,GAvDsB,MAuDtB,CAAA,KAAA,EAvDsB,MAuDtB,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,CAAA,GAvDsB,MAuDtB,CAAA,OAAA,EAvDsB,MAuDtB,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,CAAA,EAvDsB,MAuDtB,CAAA,MAAA,EAvDsB,WAAA,CAuDtB,EAvDsB,MAuDtB,CAAA,MAAA,EAvDsB,KAuDtB,CAvDsB,SAAA,EAuDtB,EAAA,SAAA,MAAA,EAAA,CAAA,CAAA,GAvDsB,MAuDtB,CAAA,YAAA,EAvDsB,KAuDtB,CAAA,SAAA,CAvDsB,MAuDtB,CAAA,QAAA,EAAA,MAAA,EAAA,MAAA,CAAA,CAAA,GAvDsB,SAAA,EAuDtB,EAAA,SAAA,CAAA,KAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA;EAvDsB,eAAA,EAAA;IAAA,GAAA,CAAA,GAAA,EAAA,MAAA,CAAA,EAkBF,OAlBE,CAAA;MAAA,EAAA,EAkBY,QAlBZ;MAAA,GAAA,EAAA,MAAA;MAAA,KAAA,EAAA,MAAA;IAAA,CAAA,GAAA,SAAA,CAAA;IAAA,GAAA,CAAA,GAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EA0BU,OA1BV,CAAA,IAAA,CAAA;IAAA,MAAA,CAAA,EAAA,EAgDT,QAhDS,CAAA,EAgDD,OAhDC,CAAA,IAAA,CAAA;EAAA,CAAA;CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,wBAAA,wBAAA,wBAAA,EAAA,CAAA,CAAA,CAAA"}
@@ -0,0 +1,39 @@
1
+ import { column, idColumn, schema } from "../schema/create.js";
2
+ import { FragmentDefinitionBuilder } from "../packages/fragno/dist/api/fragment-definition-builder.js";
3
+ import { DatabaseFragmentDefinitionBuilder } from "../db-fragment-definition-builder.js";
4
+
5
+ //#region src/fragments/internal-fragment.ts
6
+ const SETTINGS_TABLE_NAME = "fragno_db_settings";
7
+ const SETTINGS_NAMESPACE = "fragno-db-settings";
8
+ const settingsSchema = schema((s) => {
9
+ return s.addTable(SETTINGS_TABLE_NAME, (t) => {
10
+ return t.addColumn("id", idColumn()).addColumn("key", column("string")).addColumn("value", column("string")).createIndex("unique_key", ["key"], { unique: true });
11
+ });
12
+ });
13
+ const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(new FragmentDefinitionBuilder("$fragno-internal-fragment"), settingsSchema, "").providesService("settingsService", ({ defineService }) => {
14
+ return defineService({
15
+ async get(key) {
16
+ const [results] = await this.uow(settingsSchema).find(SETTINGS_TABLE_NAME, (b) => b.whereIndex("unique_key", (eb) => eb("key", "=", `${SETTINGS_NAMESPACE}.${key}`))).retrievalPhase;
17
+ return results?.[0];
18
+ },
19
+ async set(key, value) {
20
+ const uow = this.uow(settingsSchema);
21
+ const [existing] = await uow.find(SETTINGS_TABLE_NAME, (b) => b.whereIndex("unique_key", (eb) => eb("key", "=", `${SETTINGS_NAMESPACE}.${key}`))).retrievalPhase;
22
+ if (existing?.[0]) uow.update(SETTINGS_TABLE_NAME, existing[0].id, (b) => b.set({ value }).check());
23
+ else uow.create(SETTINGS_TABLE_NAME, {
24
+ key: `${SETTINGS_NAMESPACE}.${key}`,
25
+ value
26
+ });
27
+ await uow.mutationPhase;
28
+ },
29
+ async delete(id) {
30
+ const uow = this.uow(settingsSchema);
31
+ uow.delete(SETTINGS_TABLE_NAME, id);
32
+ await uow.mutationPhase;
33
+ }
34
+ });
35
+ }).build();
36
+
37
+ //#endregion
38
+ export { SETTINGS_NAMESPACE, SETTINGS_TABLE_NAME, internalFragmentDef, settingsSchema };
39
+ //# sourceMappingURL=internal-fragment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-fragment.js","names":[],"sources":["../../src/fragments/internal-fragment.ts"],"sourcesContent":["import { FragmentDefinitionBuilder } from \"@fragno-dev/core\";\nimport {\n DatabaseFragmentDefinitionBuilder,\n type DatabaseHandlerContext,\n type DatabaseRequestStorage,\n type DatabaseServiceContext,\n type FragnoPublicConfigWithDatabase,\n type ImplicitDatabaseDependencies,\n} from \"../db-fragment-definition-builder\";\nimport type { FragnoId } from \"../schema/create\";\nimport { schema, idColumn, column } from \"../schema/create\";\n\n// Constants for Fragno's internal settings table\nexport const SETTINGS_TABLE_NAME = \"fragno_db_settings\" as const;\nexport const SETTINGS_NAMESPACE = \"fragno-db-settings\" as const;\n\n// Settings schema for storing Fragno's internal key-value settings\nexport const settingsSchema = schema((s) => {\n return s.addTable(SETTINGS_TABLE_NAME, (t) => {\n return t\n .addColumn(\"id\", idColumn())\n .addColumn(\"key\", column(\"string\"))\n .addColumn(\"value\", column(\"string\"))\n .createIndex(\"unique_key\", [\"key\"], { unique: true });\n });\n});\n\n// This uses DatabaseFragmentDefinitionBuilder directly\n// to avoid circular dependency (it doesn't need to link to itself)\nexport const internalFragmentDef = new DatabaseFragmentDefinitionBuilder(\n new FragmentDefinitionBuilder<\n {},\n FragnoPublicConfigWithDatabase,\n ImplicitDatabaseDependencies<typeof settingsSchema>,\n {},\n {},\n {},\n {},\n DatabaseServiceContext,\n DatabaseHandlerContext,\n DatabaseRequestStorage\n >(\"$fragno-internal-fragment\"),\n settingsSchema,\n \"\", // intentionally blank namespace so there is no prefix\n)\n .providesService(\"settingsService\", ({ defineService }) => {\n return defineService({\n async get(key: string): Promise<{ id: FragnoId; key: string; value: string } | undefined> {\n const uow = this.uow(settingsSchema).find(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", `${SETTINGS_NAMESPACE}.${key}`)),\n );\n const [results] = await uow.retrievalPhase;\n return results?.[0];\n },\n\n async set(key: string, value: string) {\n const uow = this.uow(settingsSchema);\n\n // First, find if the key already exists\n const findUow = uow.find(SETTINGS_TABLE_NAME, (b) =>\n b.whereIndex(\"unique_key\", (eb) => eb(\"key\", \"=\", `${SETTINGS_NAMESPACE}.${key}`)),\n );\n const [existing] = await findUow.retrievalPhase;\n\n if (existing?.[0]) {\n uow.update(SETTINGS_TABLE_NAME, existing[0].id, (b) => b.set({ value }).check());\n } else {\n uow.create(SETTINGS_TABLE_NAME, {\n key: `${SETTINGS_NAMESPACE}.${key}`,\n value,\n });\n }\n\n // Await mutation phase - will throw if mutation fails\n await uow.mutationPhase;\n },\n\n async delete(id: FragnoId) {\n const uow = this.uow(settingsSchema);\n uow.delete(SETTINGS_TABLE_NAME, id);\n await uow.mutationPhase;\n },\n });\n })\n .build();\n"],"mappings":";;;;;AAaA,MAAa,sBAAsB;AACnC,MAAa,qBAAqB;AAGlC,MAAa,iBAAiB,QAAQ,MAAM;AAC1C,QAAO,EAAE,SAAS,sBAAsB,MAAM;AAC5C,SAAO,EACJ,UAAU,MAAM,UAAU,CAAC,CAC3B,UAAU,OAAO,OAAO,SAAS,CAAC,CAClC,UAAU,SAAS,OAAO,SAAS,CAAC,CACpC,YAAY,cAAc,CAAC,MAAM,EAAE,EAAE,QAAQ,MAAM,CAAC;GACvD;EACF;AAIF,MAAa,sBAAsB,IAAI,kCACrC,IAAI,0BAWF,4BAA4B,EAC9B,gBACA,GACD,CACE,gBAAgB,oBAAoB,EAAE,oBAAoB;AACzD,QAAO,cAAc;EACnB,MAAM,IAAI,KAAgF;GAIxF,MAAM,CAAC,WAAW,MAHN,KAAK,IAAI,eAAe,CAAC,KAAK,sBAAsB,MAC9D,EAAE,WAAW,eAAe,OAAO,GAAG,OAAO,KAAK,GAAG,mBAAmB,GAAG,MAAM,CAAC,CACnF,CAC2B;AAC5B,UAAO,UAAU;;EAGnB,MAAM,IAAI,KAAa,OAAe;GACpC,MAAM,MAAM,KAAK,IAAI,eAAe;GAMpC,MAAM,CAAC,YAAY,MAHH,IAAI,KAAK,sBAAsB,MAC7C,EAAE,WAAW,eAAe,OAAO,GAAG,OAAO,KAAK,GAAG,mBAAmB,GAAG,MAAM,CAAC,CACnF,CACgC;AAEjC,OAAI,WAAW,GACb,KAAI,OAAO,qBAAqB,SAAS,GAAG,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC;OAEhF,KAAI,OAAO,qBAAqB;IAC9B,KAAK,GAAG,mBAAmB,GAAG;IAC9B;IACD,CAAC;AAIJ,SAAM,IAAI;;EAGZ,MAAM,OAAO,IAAc;GACzB,MAAM,MAAM,KAAK,IAAI,eAAe;AACpC,OAAI,OAAO,qBAAqB,GAAG;AACnC,SAAM,IAAI;;EAEb,CAAC;EACF,CACD,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"generation-engine.d.ts","names":[],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":[],"mappings":";;;;;UAaiB,sBAAA;;EAAA,IAAA,EAAA,MAAA;EAMA,SAAA,EAAA,MAAA;AASjB;AAOsB,UAhBL,wBAAA,CAgB+B;EAEN,MAAA,EAAA,MAAA;EAAf,IAAA,EAAA,MAAA;EAEd,SAAA,EAAA,MAAA;EAMF,WAAA,EAAA,MAAA;EAAR,SAAA,EAAA,MAAA;EAAO,iBAAA,CAAA,EApBY,iBAoBZ;AAiJV;AAAgF,UAlK/D,sBAAA,CAkK+D;EAAf,SAAA,EAAA,MAAA;EACpD,UAAA,EAAA,OAAA;EACF,WAAA,EAAA,MAAA;EAAR,SAAA,EAAA,MAAA;;AAyIa,iBAtSM,0BAuSb,CAAA,yBArSkB,cAsSxB,CAtSuC,SAsSjB,EAAA,GAAA,CAAA,EAAA,CAAA,CAAA,SAAA,EApSZ,UAoSY,EAAA,QAAA,EAAA;;;;IA9RtB,QAAQ;;;;;;;;iBAiJW,2CAA2C,eAAe,yBACnE,aACV,QAAQ;;;;;;;;;;;iBAyIK,6BAAA,QACP,6BACN"}
1
+ {"version":3,"file":"generation-engine.d.ts","names":[],"sources":["../../src/migration-engine/generation-engine.ts"],"sourcesContent":[],"mappings":";;;;;UAeiB,sBAAA;;EAAA,IAAA,EAAA,MAAA;EAMA,SAAA,EAAA,MAAA;AASjB;AAOsB,UAhBL,wBAAA,CAgB+B;EAEN,MAAA,EAAA,MAAA;EAAf,IAAA,EAAA,MAAA;EAEd,SAAA,EAAA,MAAA;EAMF,WAAA,EAAA,MAAA;EAAR,SAAA,EAAA,MAAA;EAAO,iBAAA,CAAA,EApBY,iBAoBZ;AA8KV;AAAgF,UA/L/D,sBAAA,CA+L+D;EAAf,SAAA,EAAA,MAAA;EACpD,UAAA,EAAA,OAAA;EACF,WAAA,EAAA,MAAA;EAAR,SAAA,EAAA,MAAA;;AA0Ja,iBApVM,0BAqVb,CAAA,yBAnVkB,cAoVxB,CApVuC,SAoVjB,EAAA,GAAA,CAAA,EAAA,CAAA,CAAA,SAAA,EAlVZ,UAkVY,EAAA,QAAA,EAAA;;;;IA5UtB,QAAQ;;;;;;;;iBA8KW,2CAA2C,eAAe,yBACnE,aACV,QAAQ;;;;;;;;;;;iBA0JK,6BAAA,QACP,6BACN"}