@fragno-dev/db 0.1.11 → 0.1.13

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 (71) hide show
  1. package/.turbo/turbo-build.log +41 -39
  2. package/CHANGELOG.md +19 -0
  3. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  4. package/dist/adapters/drizzle/drizzle-adapter.js +1 -1
  5. package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
  6. package/dist/adapters/drizzle/drizzle-query.js +42 -34
  7. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  8. package/dist/adapters/drizzle/drizzle-uow-compiler.js +2 -1
  9. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-uow-decoder.js +25 -1
  11. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  12. package/dist/adapters/drizzle/generate.js +1 -1
  13. package/dist/adapters/kysely/kysely-adapter.d.ts +4 -3
  14. package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
  15. package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
  16. package/dist/adapters/kysely/kysely-query.d.ts +22 -0
  17. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -0
  18. package/dist/adapters/kysely/kysely-query.js +101 -51
  19. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  20. package/dist/adapters/kysely/kysely-uow-compiler.js +2 -1
  21. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  22. package/dist/adapters/kysely/kysely-uow-executor.js +2 -2
  23. package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
  24. package/dist/adapters/kysely/migration/execute-base.js +1 -1
  25. package/dist/migration-engine/generation-engine.d.ts +1 -1
  26. package/dist/migration-engine/generation-engine.d.ts.map +1 -1
  27. package/dist/migration-engine/generation-engine.js.map +1 -1
  28. package/dist/mod.d.ts +7 -6
  29. package/dist/mod.d.ts.map +1 -1
  30. package/dist/mod.js +2 -1
  31. package/dist/mod.js.map +1 -1
  32. package/dist/query/cursor.d.ts +67 -32
  33. package/dist/query/cursor.d.ts.map +1 -1
  34. package/dist/query/cursor.js +84 -31
  35. package/dist/query/cursor.js.map +1 -1
  36. package/dist/query/query.d.ts +29 -8
  37. package/dist/query/query.d.ts.map +1 -1
  38. package/dist/query/result-transform.js +17 -5
  39. package/dist/query/result-transform.js.map +1 -1
  40. package/dist/query/unit-of-work.d.ts +19 -8
  41. package/dist/query/unit-of-work.d.ts.map +1 -1
  42. package/dist/query/unit-of-work.js +54 -12
  43. package/dist/query/unit-of-work.js.map +1 -1
  44. package/dist/schema/serialize.js +2 -0
  45. package/dist/schema/serialize.js.map +1 -1
  46. package/package.json +3 -3
  47. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +242 -55
  48. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +95 -39
  49. package/src/adapters/drizzle/drizzle-query.test.ts +54 -4
  50. package/src/adapters/drizzle/drizzle-query.ts +74 -60
  51. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +82 -6
  52. package/src/adapters/drizzle/drizzle-uow-compiler.ts +3 -2
  53. package/src/adapters/drizzle/drizzle-uow-decoder.ts +40 -1
  54. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +190 -4
  55. package/src/adapters/kysely/kysely-adapter.ts +6 -3
  56. package/src/adapters/kysely/kysely-query.test.ts +498 -0
  57. package/src/adapters/kysely/kysely-query.ts +187 -83
  58. package/src/adapters/kysely/kysely-uow-compiler.test.ts +85 -3
  59. package/src/adapters/kysely/kysely-uow-compiler.ts +3 -2
  60. package/src/adapters/kysely/kysely-uow-executor.ts +5 -9
  61. package/src/migration-engine/generation-engine.ts +2 -1
  62. package/src/mod.ts +12 -7
  63. package/src/query/cursor.test.ts +113 -68
  64. package/src/query/cursor.ts +127 -36
  65. package/src/query/query-type.test.ts +34 -14
  66. package/src/query/query.ts +94 -34
  67. package/src/query/result-transform.test.ts +5 -5
  68. package/src/query/result-transform.ts +29 -11
  69. package/src/query/unit-of-work.ts +141 -26
  70. package/src/schema/serialize.test.ts +223 -0
  71. package/src/schema/serialize.ts +16 -0
@@ -1 +1 @@
1
- {"version":3,"file":"unit-of-work.js","names":["#table","#tableName","#indexName","#whereClause","#countMode","#selectClause","#orderByIndexClause","#afterCursor","#beforeCursor","#pageSizeValue","#joinClause","compiledJoins: CompiledJoin[] | undefined","options: FindOptions<TTable, TSelect>","#id","#setValues","#checkVersion","compiled: CompiledJoin[]","builder: Record<string, unknown>","conditions: Condition | undefined","orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined","#schema","#compiler","#executor","#decoder","#name","#config","#state","#retrievalOps","retrievalBatch: unknown[]","#retrievalResults","externalId: string","#mutationOps","mutationBatch: CompiledMutation<unknown>[]","#createdInternalIds","createdIds: FragnoId[]","retrievalBatch: TOutput[]","mutationBatch: CompiledMutation<TOutput>[]"],"sources":["../../src/query/unit-of-work.ts"],"sourcesContent":["import type { AnySchema, AnyTable, Index, IdColumn, AnyColumn, Relation } from \"../schema/create\";\nimport { FragnoId } from \"../schema/create\";\nimport type { Condition, ConditionBuilder } from \"./condition-builder\";\nimport type { SelectClause, TableToInsertValues, TableToUpdateValues, SelectResult } from \"./query\";\nimport { buildCondition } from \"./condition-builder\";\nimport type { CompiledJoin } from \"./orm/orm\";\n\n/**\n * Builder for updateMany operations that supports both whereIndex and set chaining\n */\nexport interface UpdateManyBuilder<TTable extends AnyTable> {\n whereIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n condition?: (eb: IndexSpecificConditionBuilder<TTable, TIndexName>) => Condition | boolean,\n ): this;\n set(values: TableToUpdateValues<TTable>): this;\n}\n\n/**\n * Extract column names from a single index\n */\nexport type IndexColumns<TIndex extends Index> = TIndex[\"columnNames\"][number];\n\ntype RemoveEmptyObject<T> = T extends object ? (keyof T extends never ? never : T) : never;\n\n/**\n * Extract all indexed column names from a table's indexes\n */\ntype IndexedColumns<TIndexes extends Record<string, Index>> = TIndexes[keyof TIndexes] extends Index\n ? IndexColumns<TIndexes[keyof TIndexes]>\n : never;\n\ntype OmitNever<T> = { [K in keyof T as T[K] extends never ? never : K]: T[K] };\n\n/**\n * Extract the name of the ID column from a table\n * Checks if column has 'id' property set to true (which IdColumn class has)\n */\nexport type InferIdColumnName<TTable extends AnyTable> = keyof OmitNever<{\n [K in keyof TTable[\"columns\"]]: TTable[\"columns\"][K] extends IdColumn<\n infer _,\n infer __,\n infer ___\n >\n ? K\n : never;\n}>;\n\n/**\n * Get the columns for a specific index name.\n * For \"primary\", returns only the ID column.\n * For named indexes, returns the columns defined in that index.\n */\ntype ColumnsForIndex<\n TTable extends AnyTable,\n TIndexName extends ValidIndexName<TTable>,\n> = TIndexName extends \"primary\"\n ? Pick<TTable[\"columns\"], InferIdColumnName<TTable>>\n : TIndexName extends keyof TTable[\"indexes\"]\n ? Pick<TTable[\"columns\"], IndexColumns<TTable[\"indexes\"][TIndexName]>>\n : never;\n\n/**\n * ConditionBuilder restricted to indexed columns only.\n * Used throughout Unit of Work to ensure all queries can leverage indexes for optimal performance.\n */\nexport type IndexedConditionBuilder<TTable extends AnyTable> = ConditionBuilder<\n Pick<TTable[\"columns\"], IndexedColumns<TTable[\"indexes\"]>>\n>;\n\n/**\n * ConditionBuilder restricted to columns in a specific index.\n */\ntype IndexSpecificConditionBuilder<\n TTable extends AnyTable,\n TIndexName extends ValidIndexName<TTable>,\n> = ConditionBuilder<ColumnsForIndex<TTable, TIndexName>>;\n\n/**\n * Valid index names for a table, including the static \"primary\" index\n */\nexport type ValidIndexName<TTable extends AnyTable> =\n | \"primary\"\n | (string & keyof TTable[\"indexes\"]);\n\n/**\n * Find options for Unit of Work (internal, used after builder finalization)\n */\ntype FindOptions<\n TTable extends AnyTable = AnyTable,\n TSelect extends SelectClause<TTable> = SelectClause<TTable>,\n> = {\n /**\n * Which index to use for this query (required)\n */\n useIndex: string;\n /**\n * Select clause - which columns to return\n */\n select?: TSelect;\n /**\n * Where clause - filtering restricted to indexed columns only\n */\n where?: (eb: IndexedConditionBuilder<TTable>) => Condition | boolean;\n /**\n * Order by index - specify which index to order by and direction\n */\n orderByIndex?: {\n indexName: string;\n direction: \"asc\" | \"desc\";\n };\n /**\n * Cursor for pagination - continue after this cursor\n */\n after?: string;\n /**\n * Cursor for pagination - continue before this cursor\n */\n before?: string;\n /**\n * Number of results per page\n */\n pageSize?: number;\n /**\n * Join operations to include related data\n */\n joins?: CompiledJoin[];\n};\n\n/**\n * Unit of Work state machine\n */\nexport type UOWState = \"building-retrieval\" | \"building-mutation\" | \"executed\";\n\n/**\n * Retrieval operation - read operations in the first phase\n */\nexport type RetrievalOperation<\n TSchema extends AnySchema,\n TTable extends AnyTable = TSchema[\"tables\"][keyof TSchema[\"tables\"]],\n> =\n | {\n type: \"find\";\n table: TTable;\n indexName: string;\n options: FindOptions<TTable, SelectClause<TTable>>;\n }\n | {\n type: \"count\";\n table: TTable;\n indexName: string;\n options: Pick<FindOptions<TTable>, \"where\" | \"useIndex\">;\n };\n\n/**\n * Mutation operations - write operations in the second phase\n */\nexport type MutationOperation<\n TSchema extends AnySchema,\n TTable extends AnyTable = TSchema[\"tables\"][keyof TSchema[\"tables\"]],\n> =\n | {\n type: \"update\";\n table: TTable[\"name\"];\n id: FragnoId | string;\n checkVersion: boolean;\n set: TableToUpdateValues<TTable>;\n }\n | {\n type: \"create\";\n table: TTable[\"name\"];\n values: TableToInsertValues<TTable>;\n generatedExternalId: string;\n }\n | {\n type: \"delete\";\n table: TTable[\"name\"];\n id: FragnoId | string;\n checkVersion: boolean;\n };\n\n/**\n * Compiled mutation with metadata for execution\n */\nexport interface CompiledMutation<TOutput> {\n query: TOutput;\n /**\n * Number of rows this operation must affect for the transaction to succeed.\n * If actual affected rows doesn't match, it indicates a version conflict.\n * null means don't check affected rows (e.g., for create operations).\n */\n expectedAffectedRows: number | null;\n}\n\n/**\n * Compiler interface for Unit of Work operations\n */\nexport interface UOWCompiler<TSchema extends AnySchema, TOutput> {\n /**\n * Compile a retrieval operation to the adapter's query format\n */\n compileRetrievalOperation(op: RetrievalOperation<TSchema>): TOutput | null;\n\n /**\n * Compile a mutation operation to the adapter's query format\n */\n compileMutationOperation(op: MutationOperation<TSchema>): CompiledMutation<TOutput> | null;\n}\n\nexport type MutationResult =\n | { success: true; createdInternalIds: (bigint | null)[] }\n | { success: false };\n\n/**\n * Executor interface for Unit of Work operations\n */\nexport interface UOWExecutor<TOutput, TRawResult = unknown> {\n /**\n * Execute the retrieval phase - all queries run in a single transaction for snapshot isolation\n */\n executeRetrievalPhase(retrievalBatch: TOutput[]): Promise<TRawResult[]>;\n\n /**\n * Execute the mutation phase - all queries run in a transaction with version checks\n * Returns success status indicating if mutations completed without conflicts,\n * and internal IDs for create operations (null if database doesn't support RETURNING)\n */\n executeMutationPhase(mutationBatch: CompiledMutation<TOutput>[]): Promise<MutationResult>;\n}\n\n/**\n * Decoder interface for Unit of Work retrieval results\n *\n * Transforms raw database results into application format (e.g., converting raw columns\n * into FragnoId objects with external ID, internal ID, and version).\n */\nexport interface UOWDecoder<TSchema extends AnySchema, TRawInput = unknown> {\n /**\n * Decode raw database results from the retrieval phase\n *\n * @param rawResults - Array of raw result sets from database queries\n * @param operations - Array of retrieval operations that produced these results\n * @returns Decoded results in application format\n */\n (rawResults: TRawInput[], operations: RetrievalOperation<TSchema>[]): unknown[];\n}\n\n/**\n * Builder for find operations in Unit of Work\n */\nexport class FindBuilder<\n TTable extends AnyTable,\n TSelect extends SelectClause<TTable> = true,\n TJoinOut = {},\n> {\n readonly #table: TTable;\n readonly #tableName: string;\n\n #indexName?: string;\n #whereClause?: (eb: IndexedConditionBuilder<TTable>) => Condition | boolean;\n #orderByIndexClause?: {\n indexName: string;\n direction: \"asc\" | \"desc\";\n };\n #afterCursor?: string;\n #beforeCursor?: string;\n #pageSizeValue?: number;\n #selectClause?: TSelect;\n #joinClause?: (jb: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TJoinOut>;\n #countMode = false;\n\n constructor(tableName: string, table: TTable) {\n this.#tableName = tableName;\n this.#table = table;\n }\n\n /**\n * Specify which index to use and optionally filter the results\n */\n whereIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n condition?: (eb: IndexSpecificConditionBuilder<TTable, TIndexName>) => Condition | boolean,\n ): this {\n // Validate index exists (primary is always valid)\n if (indexName !== \"primary\" && !(indexName in this.#table.indexes)) {\n throw new Error(\n `Index \"${String(indexName)}\" not found on table \"${this.#tableName}\". ` +\n `Available indexes: primary, ${Object.keys(this.#table.indexes).join(\", \")}`,\n );\n }\n\n this.#indexName = indexName === \"primary\" ? \"_primary\" : indexName;\n if (condition) {\n // Safe: IndexSpecificConditionBuilder is a subset of IndexedConditionBuilder.\n // The condition will only reference columns in the specific index, which are also indexed columns.\n this.#whereClause = condition as unknown as (\n eb: IndexedConditionBuilder<TTable>,\n ) => Condition | boolean;\n }\n return this;\n }\n\n /**\n * Specify columns to select\n * @throws Error if selectCount() has already been called\n */\n select<const TNewSelect extends SelectClause<TTable>>(\n columns: TNewSelect,\n ): FindBuilder<TTable, TNewSelect, TJoinOut> {\n if (this.#countMode) {\n throw new Error(\n `Cannot call select() after selectCount() on table \"${this.#tableName}\". ` +\n `Use either select() or selectCount(), not both.`,\n );\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).#selectClause = columns;\n return this as unknown as FindBuilder<TTable, TNewSelect, TJoinOut>;\n }\n\n /**\n * Select count instead of records\n * @throws Error if select() has already been called\n */\n selectCount(): this {\n if (this.#selectClause !== undefined) {\n throw new Error(\n `Cannot call selectCount() after select() on table \"${this.#tableName}\". ` +\n `Use either select() or selectCount(), not both.`,\n );\n }\n this.#countMode = true;\n return this;\n }\n\n /**\n * Order results by index in ascending or descending order\n */\n orderByIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n direction: \"asc\" | \"desc\",\n ): this {\n // Validate index exists (primary is always valid)\n if (indexName !== \"primary\" && !(indexName in this.#table.indexes)) {\n throw new Error(\n `Index \"${String(indexName)}\" not found on table \"${this.#tableName}\". ` +\n `Available indexes: primary, ${Object.keys(this.#table.indexes).join(\", \")}`,\n );\n }\n\n this.#orderByIndexClause = {\n indexName: indexName === \"primary\" ? \"_primary\" : indexName,\n direction,\n };\n return this;\n }\n\n /**\n * Set cursor to continue pagination after this point (forward pagination)\n */\n after(cursor: string): this {\n this.#afterCursor = cursor;\n return this;\n }\n\n /**\n * Set cursor to continue pagination before this point (backward pagination)\n */\n before(cursor: string): this {\n this.#beforeCursor = cursor;\n return this;\n }\n\n /**\n * Set the number of results per page\n */\n pageSize(size: number): this {\n this.#pageSizeValue = size;\n return this;\n }\n\n /**\n * Add joins to include related data\n * Join where clauses are restricted to indexed columns only\n */\n join<TNewJoinOut>(\n joinFn: (jb: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TNewJoinOut>,\n ): FindBuilder<TTable, TSelect, TNewJoinOut> {\n this.#joinClause = joinFn;\n return this as unknown as FindBuilder<TTable, TSelect, TNewJoinOut>;\n }\n\n /**\n * @internal\n */\n build():\n | { type: \"find\"; indexName: string; options: FindOptions<TTable, TSelect> }\n | {\n type: \"count\";\n indexName: string;\n options: Pick<FindOptions<TTable>, \"where\" | \"useIndex\">;\n } {\n if (!this.#indexName) {\n throw new Error(\n `Must specify an index using .whereIndex() before finalizing find operation on table \"${this.#tableName}\"`,\n );\n }\n\n // If in count mode, return count operation\n if (this.#countMode) {\n return {\n type: \"count\",\n indexName: this.#indexName,\n options: {\n useIndex: this.#indexName,\n where: this.#whereClause,\n },\n };\n }\n\n // Compile joins if provided\n let compiledJoins: CompiledJoin[] | undefined;\n if (this.#joinClause) {\n compiledJoins = buildJoinIndexed(this.#table, this.#joinClause);\n }\n\n const options: FindOptions<TTable, TSelect> = {\n useIndex: this.#indexName,\n select: this.#selectClause,\n where: this.#whereClause,\n orderByIndex: this.#orderByIndexClause,\n after: this.#afterCursor,\n before: this.#beforeCursor,\n pageSize: this.#pageSizeValue,\n joins: compiledJoins,\n };\n\n return { type: \"find\", indexName: this.#indexName, options };\n }\n}\n\n/**\n * Builder for update operations in Unit of Work\n */\nexport class UpdateBuilder<TTable extends AnyTable> {\n readonly #tableName: string;\n readonly #id: FragnoId | string;\n\n #checkVersion = false;\n #setValues?: TableToUpdateValues<TTable>;\n\n constructor(tableName: string, id: FragnoId | string) {\n this.#tableName = tableName;\n this.#id = id;\n }\n\n /**\n * Specify values to update\n */\n set(values: TableToUpdateValues<TTable>): this {\n this.#setValues = values;\n return this;\n }\n\n /**\n * Enable version checking for optimistic concurrency control\n * @throws Error if the ID is just a string (no version available)\n */\n check(): this {\n if (typeof this.#id === \"string\") {\n throw new Error(\n `Cannot use check() with a string ID on table \"${this.#tableName}\". ` +\n `Version checking requires a FragnoId with version information.`,\n );\n }\n this.#checkVersion = true;\n return this;\n }\n\n /**\n * @internal\n */\n build(): {\n id: FragnoId | string;\n checkVersion: boolean;\n set: TableToUpdateValues<TTable>;\n } {\n if (!this.#setValues) {\n throw new Error(\n `Must specify values using .set() before finalizing update operation on table \"${this.#tableName}\"`,\n );\n }\n\n return {\n id: this.#id,\n checkVersion: this.#checkVersion,\n set: this.#setValues,\n };\n }\n}\n\n/**\n * Builder for delete operations in Unit of Work\n */\nexport class DeleteBuilder {\n readonly #tableName: string;\n readonly #id: FragnoId | string;\n\n #checkVersion = false;\n\n constructor(tableName: string, id: FragnoId | string) {\n this.#tableName = tableName;\n this.#id = id;\n }\n\n /**\n * Enable version checking for optimistic concurrency control\n * @throws Error if the ID is just a string (no version available)\n */\n check(): this {\n if (typeof this.#id === \"string\") {\n throw new Error(\n `Cannot use check() with a string ID on table \"${this.#tableName}\". ` +\n `Version checking requires a FragnoId with version information.`,\n );\n }\n this.#checkVersion = true;\n return this;\n }\n\n /**\n * @internal\n */\n build(): { id: FragnoId | string; checkVersion: boolean } {\n return {\n id: this.#id,\n checkVersion: this.#checkVersion,\n };\n }\n}\n\n/**\n * Builder for join operations in Unit of Work\n * Similar to FindBuilder but tailored for joins (no cursor pagination, no count mode)\n */\nexport class JoinFindBuilder<\n TTable extends AnyTable,\n TSelect extends SelectClause<TTable> = true,\n TJoinOut = {},\n> {\n readonly #table: TTable;\n readonly #tableName: string;\n\n #indexName?: string;\n #whereClause?: (eb: IndexedConditionBuilder<TTable>) => Condition | boolean;\n #orderByIndexClause?: {\n indexName: string;\n direction: \"asc\" | \"desc\";\n };\n #pageSizeValue?: number;\n #selectClause?: TSelect;\n #joinClause?: (jb: IndexedJoinBuilder<TTable, TJoinOut>) => IndexedJoinBuilder<TTable, TJoinOut>;\n\n constructor(tableName: string, table: TTable) {\n this.#tableName = tableName;\n this.#table = table;\n }\n\n /**\n * Specify which index to use and optionally filter the results\n */\n whereIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n condition?: (eb: IndexSpecificConditionBuilder<TTable, TIndexName>) => Condition | boolean,\n ): this {\n // Validate index exists (primary is always valid)\n if (indexName !== \"primary\" && !(indexName in this.#table.indexes)) {\n throw new Error(\n `Index \"${String(indexName)}\" not found on table \"${this.#tableName}\". ` +\n `Available indexes: primary, ${Object.keys(this.#table.indexes).join(\", \")}`,\n );\n }\n\n this.#indexName = indexName === \"primary\" ? \"_primary\" : indexName;\n if (condition) {\n // Safe: IndexSpecificConditionBuilder is a subset of IndexedConditionBuilder.\n // The condition will only reference columns in the specific index, which are also indexed columns.\n this.#whereClause = condition as unknown as (\n eb: IndexedConditionBuilder<TTable>,\n ) => Condition | boolean;\n }\n return this;\n }\n\n /**\n * Specify columns to select\n */\n select<const TNewSelect extends SelectClause<TTable>>(\n columns: TNewSelect,\n ): JoinFindBuilder<TTable, TNewSelect, TJoinOut> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).#selectClause = columns;\n return this as unknown as JoinFindBuilder<TTable, TNewSelect, TJoinOut>;\n }\n\n /**\n * Order results by index in ascending or descending order\n */\n orderByIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n direction: \"asc\" | \"desc\",\n ): this {\n // Validate index exists (primary is always valid)\n if (indexName !== \"primary\" && !(indexName in this.#table.indexes)) {\n throw new Error(\n `Index \"${String(indexName)}\" not found on table \"${this.#tableName}\". ` +\n `Available indexes: primary, ${Object.keys(this.#table.indexes).join(\", \")}`,\n );\n }\n\n this.#orderByIndexClause = {\n indexName: indexName === \"primary\" ? \"_primary\" : indexName,\n direction,\n };\n return this;\n }\n\n /**\n * Set the number of results to return\n */\n pageSize(size: number): this {\n this.#pageSizeValue = size;\n return this;\n }\n\n /**\n * Add joins to include related data\n * Join where clauses are restricted to indexed columns only\n */\n join<TNewJoinOut>(\n joinFn: (jb: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TNewJoinOut>,\n ): JoinFindBuilder<TTable, TSelect, TJoinOut & TNewJoinOut> {\n this.#joinClause = joinFn;\n return this as unknown as JoinFindBuilder<TTable, TSelect, TJoinOut & TNewJoinOut>;\n }\n\n /**\n * @internal\n */\n build(): {\n indexName: string | undefined;\n select: TSelect | undefined;\n where: ((eb: IndexedConditionBuilder<TTable>) => Condition | boolean) | undefined;\n orderByIndex:\n | {\n indexName: string;\n direction: \"asc\" | \"desc\";\n }\n | undefined;\n pageSize: number | undefined;\n joins: CompiledJoin[] | undefined;\n } {\n // Compile joins if provided\n let compiledJoins: CompiledJoin[] | undefined;\n if (this.#joinClause) {\n compiledJoins = buildJoinIndexed(this.#table, this.#joinClause);\n }\n\n return {\n indexName: this.#indexName,\n select: this.#selectClause,\n where: this.#whereClause,\n orderByIndex: this.#orderByIndexClause,\n pageSize: this.#pageSizeValue,\n joins: compiledJoins,\n };\n }\n}\n\ninterface MapRelationType<T> {\n // FIXME: Not sure why we need the RemoveEmptyObject, we should somehow fix at the source where it's added to the union\n one: RemoveEmptyObject<T> | null;\n many: RemoveEmptyObject<T>[];\n}\n\n/**\n * Join builder with indexed-only where clauses for Unit of Work\n * TJoinOut accumulates the types of all joined relations\n */\nexport type IndexedJoinBuilder<TTable extends AnyTable, TJoinOut> = {\n [K in keyof TTable[\"relations\"]]: TTable[\"relations\"][K] extends Relation<\n infer TRelationType,\n infer TTargetTable\n >\n ? <TSelect extends SelectClause<TTable[\"relations\"][K][\"table\"]> = true, TNestedJoinOut = {}>(\n builderFn?: (\n builder: JoinFindBuilder<TTable[\"relations\"][K][\"table\"]>,\n ) => JoinFindBuilder<TTable[\"relations\"][K][\"table\"], TSelect, TNestedJoinOut>,\n ) => IndexedJoinBuilder<\n TTable,\n TJoinOut & {\n [P in K]: MapRelationType<\n SelectResult<TTargetTable, TNestedJoinOut, TSelect>\n >[TRelationType];\n }\n >\n : never;\n};\n\n/**\n * Build join operations with indexed-only where clauses for Unit of Work\n * This ensures all join conditions can leverage indexes for optimal performance\n */\nexport function buildJoinIndexed<TTable extends AnyTable, TJoinOut>(\n table: TTable,\n fn: (builder: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TJoinOut>,\n): CompiledJoin[] {\n const compiled: CompiledJoin[] = [];\n const builder: Record<string, unknown> = {};\n\n for (const name in table.relations) {\n const relation = table.relations[name]!;\n\n builder[name] = (builderFn?: (b: JoinFindBuilder<AnyTable>) => JoinFindBuilder<AnyTable>) => {\n // Create join builder for this relation's table\n const joinBuilder = new JoinFindBuilder(relation.table.ormName, relation.table);\n if (builderFn) {\n builderFn(joinBuilder);\n }\n const config = joinBuilder.build();\n\n // Build condition with indexed columns only\n let conditions: Condition | undefined;\n if (config.where) {\n const cond = buildCondition(relation.table.columns, config.where);\n if (cond === true) {\n conditions = undefined;\n } else if (cond === false) {\n // If condition evaluates to false, skip this join\n compiled.push({\n relation,\n options: false,\n });\n delete builder[name];\n return builder;\n } else {\n conditions = cond;\n }\n }\n\n // Build orderBy from orderByIndex if provided\n let orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined;\n if (config.orderByIndex) {\n const index = relation.table.indexes[config.orderByIndex.indexName];\n if (index) {\n // Use all columns from the index for ordering\n orderBy = index.columns.map(\n (col) => [col, config.orderByIndex!.direction] as [AnyColumn, \"asc\" | \"desc\"],\n );\n } else {\n // Fallback to ID column if index not found\n orderBy = [[relation.table.getIdColumn(), config.orderByIndex.direction]];\n }\n }\n\n compiled.push({\n relation,\n options: {\n select: config.select ?? true,\n where: conditions,\n orderBy,\n join: config.joins,\n limit: config.pageSize,\n },\n });\n\n delete builder[name];\n return builder;\n };\n }\n\n fn(builder as IndexedJoinBuilder<TTable, {}>);\n return compiled;\n}\n\nexport function createUnitOfWork<\n const TSchema extends AnySchema,\n const TRetrievalResults extends unknown[] = [],\n const TRawInput = unknown,\n>(\n schema: TSchema,\n compiler: UOWCompiler<TSchema, unknown>,\n executor: UOWExecutor<unknown, TRawInput>,\n decoder: UOWDecoder<TSchema, TRawInput>,\n name?: string,\n): UnitOfWork<TSchema, TRetrievalResults, TRawInput> {\n return new UnitOfWork(schema, compiler, executor, decoder, name) as UnitOfWork<\n TSchema,\n TRetrievalResults,\n TRawInput\n >;\n}\n\nexport interface UnitOfWorkConfig {\n dryRun?: boolean;\n onQuery?: (query: unknown) => void;\n}\n\n/**\n * Unit of Work implementation with optimistic concurrency control\n *\n * UOW has two phases:\n * 1. Retrieval phase: Read operations to fetch entities with their versions\n * 2. Mutation phase: Write operations that check versions before committing\n *\n * @example\n * ```ts\n * const uow = queryEngine.createUnitOfWork(\"update-user-balance\");\n *\n * // Retrieval phase\n * uow.find(\"users\", (b) => b.where(\"primary\", (eb) => eb(\"id\", \"=\", userId)));\n *\n * // Execute retrieval and transition to mutation phase\n * const [users] = await uow.executeRetrieve();\n *\n * // Mutation phase with version check\n * const user = users[0];\n * uow.update(\"users\", user.id, (b) => b.set({ balance: newBalance }).check());\n *\n * // Execute mutations\n * const { success } = await uow.executeMutations();\n * if (!success) {\n * // Handle version conflict\n * }\n * ```\n */\nexport class UnitOfWork<\n const TSchema extends AnySchema,\n const TRetrievalResults extends unknown[] = [],\n const TRawInput = unknown,\n> {\n #schema: TSchema;\n\n #name?: string;\n #config?: UnitOfWorkConfig;\n\n #state: UOWState = \"building-retrieval\";\n\n #retrievalOps: RetrievalOperation<TSchema>[] = [];\n #mutationOps: MutationOperation<TSchema>[] = [];\n\n #compiler: UOWCompiler<TSchema, unknown>;\n #executor: UOWExecutor<unknown, TRawInput>;\n #decoder: UOWDecoder<TSchema, TRawInput>;\n\n #retrievalResults?: TRetrievalResults;\n #createdInternalIds: (bigint | null)[] = [];\n\n constructor(\n schema: TSchema,\n compiler: UOWCompiler<TSchema, unknown>,\n executor: UOWExecutor<unknown, TRawInput>,\n decoder: UOWDecoder<TSchema, TRawInput>,\n name?: string,\n config?: UnitOfWorkConfig,\n ) {\n this.#schema = schema;\n this.#compiler = compiler;\n this.#executor = executor;\n this.#decoder = decoder;\n this.#name = name;\n this.#config = config;\n }\n\n get schema(): TSchema {\n return this.#schema;\n }\n\n get state(): UOWState {\n return this.#state;\n }\n\n get name(): string | undefined {\n return this.#name;\n }\n\n /**\n * Execute the retrieval phase and transition to mutation phase\n * Returns all results from find operations\n */\n async executeRetrieve(): Promise<TRetrievalResults> {\n if (this.#retrievalOps.length === 0) {\n return [] as unknown as TRetrievalResults;\n }\n\n if (this.#state !== \"building-retrieval\") {\n throw new Error(\n `Cannot execute retrieval from state ${this.#state}. Must be in building-retrieval state.`,\n );\n }\n\n // Compile retrieval operations\n const retrievalBatch: unknown[] = [];\n for (const op of this.#retrievalOps) {\n const compiled = this.#compiler.compileRetrievalOperation(op);\n if (compiled !== null) {\n this.#config?.onQuery?.(compiled);\n\n retrievalBatch.push(compiled);\n }\n }\n\n if (this.#config?.dryRun) {\n this.#state = \"executed\";\n return [] as unknown as TRetrievalResults;\n }\n\n const results = this.#decoder(\n await this.#executor.executeRetrievalPhase(retrievalBatch),\n this.#retrievalOps,\n );\n\n // Store results and transition to mutation phase\n this.#retrievalResults = results as TRetrievalResults;\n this.#state = \"building-mutation\";\n\n return this.#retrievalResults;\n }\n\n /**\n * Add a find operation using a builder callback (retrieval phase only)\n */\n find<\n TTableName extends keyof TSchema[\"tables\"] & string,\n TSelect extends SelectClause<TSchema[\"tables\"][TTableName]> = true,\n TJoinOut = {},\n >(\n tableName: TTableName,\n builderFn?: (\n // We omit \"build\" because we don't want to expose it to the user\n builder: Omit<FindBuilder<TSchema[\"tables\"][TTableName]>, \"build\">,\n ) => Omit<FindBuilder<TSchema[\"tables\"][TTableName], TSelect, TJoinOut>, \"build\">,\n ): UnitOfWork<\n TSchema,\n [...TRetrievalResults, SelectResult<TSchema[\"tables\"][TTableName], TJoinOut, TSelect>[]],\n TRawInput\n > {\n if (this.#state !== \"building-retrieval\") {\n throw new Error(\n `find() can only be called during retrieval phase. Current state: ${this.#state}`,\n );\n }\n\n const table = this.#schema.tables[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n // Create builder, pass to callback (or use default), then extract configuration\n const builder = new FindBuilder(tableName, table as TSchema[\"tables\"][TTableName]);\n if (builderFn) {\n builderFn(builder);\n } else {\n // Default to primary index with no filter\n builder.whereIndex(\"primary\");\n }\n const { indexName, options, type } = builder.build();\n\n this.#retrievalOps.push({\n type,\n // Safe: we know the table is part of the schema from the find() method\n table: table as TSchema[\"tables\"][TTableName],\n indexName,\n // Safe: we're storing the options for later compilation\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: options as any,\n });\n\n return this as unknown as UnitOfWork<\n TSchema,\n [...TRetrievalResults, SelectResult<TSchema[\"tables\"][TTableName], TJoinOut, TSelect>[]],\n TRawInput\n >;\n }\n\n /**\n * Add a create operation (mutation phase only)\n */\n create<TableName extends keyof TSchema[\"tables\"] & string>(\n table: TableName,\n values: TableToInsertValues<TSchema[\"tables\"][TableName]>,\n ): this {\n if (this.#state === \"executed\") {\n throw new Error(`create() can only be called during mutation phase.`);\n }\n\n const tableSchema = this.#schema.tables[table];\n if (!tableSchema) {\n throw new Error(`Table ${table} not found in schema`);\n }\n\n const idColumn = tableSchema.getIdColumn();\n let externalId: string;\n let updatedValues = values;\n\n // Check if ID value is provided in values\n const providedIdValue = (values as Record<string, unknown>)[idColumn.ormName];\n\n if (providedIdValue !== undefined) {\n // Extract string from FragnoId or use string directly\n if (\n typeof providedIdValue === \"object\" &&\n providedIdValue !== null &&\n \"externalId\" in providedIdValue\n ) {\n externalId = (providedIdValue as FragnoId).externalId;\n } else {\n externalId = providedIdValue as string;\n }\n } else {\n // Generate using the column's default configuration\n const generated = idColumn.generateDefaultValue();\n if (generated === undefined) {\n throw new Error(\n `No ID value provided and ID column ${idColumn.ormName} has no default generator`,\n );\n }\n externalId = generated as string;\n\n // Add the generated ID to values so it's used in the insert\n updatedValues = {\n ...values,\n [idColumn.ormName]: externalId,\n } as TableToInsertValues<TSchema[\"tables\"][TableName]>;\n }\n\n this.#mutationOps.push({\n type: \"create\",\n table,\n values: updatedValues,\n generatedExternalId: externalId,\n });\n\n return this;\n }\n\n /**\n * Add an update operation using a builder callback (mutation phase only)\n */\n update<TableName extends keyof TSchema[\"tables\"] & string>(\n table: TableName,\n id: FragnoId | string,\n builderFn: (\n // We omit \"build\" because we don't want to expose it to the user\n builder: Omit<UpdateBuilder<TSchema[\"tables\"][TableName]>, \"build\">,\n ) => Omit<UpdateBuilder<TSchema[\"tables\"][TableName]>, \"build\">,\n ): this {\n if (this.#state === \"executed\") {\n throw new Error(`update() can only be called during mutation phase.`);\n }\n\n // Create builder, pass to callback, then extract configuration\n const builder = new UpdateBuilder<TSchema[\"tables\"][TableName]>(table, id);\n builderFn(builder);\n const { id: opId, checkVersion, set } = builder.build();\n\n this.#mutationOps.push({\n type: \"update\",\n table,\n id: opId,\n checkVersion,\n set,\n });\n\n return this;\n }\n\n /**\n * Add a delete operation using a builder callback (mutation phase only)\n */\n delete<TableName extends keyof TSchema[\"tables\"] & string>(\n table: TableName,\n id: FragnoId | string,\n builderFn?: (\n // We omit \"build\" because we don't want to expose it to the user\n builder: Omit<DeleteBuilder, \"build\">,\n ) => Omit<DeleteBuilder, \"build\">,\n ): this {\n if (this.#state === \"executed\") {\n throw new Error(`delete() can only be called during mutation phase.`);\n }\n\n // Create builder, optionally pass to callback, then extract configuration\n const builder = new DeleteBuilder(table, id);\n builderFn?.(builder);\n const { id: opId, checkVersion } = builder.build();\n\n this.#mutationOps.push({\n type: \"delete\",\n table,\n id: opId,\n checkVersion,\n });\n\n return this;\n }\n\n /**\n * Execute the mutation phase\n * Returns success flag indicating if mutations completed without conflicts\n */\n async executeMutations(): Promise<{ success: boolean }> {\n if (this.#state === \"executed\") {\n throw new Error(`Cannot execute mutations from state ${this.#state}.`);\n }\n\n // Compile mutation operations\n const mutationBatch: CompiledMutation<unknown>[] = [];\n for (const op of this.#mutationOps) {\n const compiled = this.#compiler.compileMutationOperation(op);\n if (compiled !== null) {\n this.#config?.onQuery?.(compiled);\n mutationBatch.push(compiled);\n }\n }\n\n if (this.#config?.dryRun) {\n this.#state = \"executed\";\n return {\n success: true,\n };\n }\n\n // Execute mutation phase\n const result = await this.#executor.executeMutationPhase(mutationBatch);\n this.#state = \"executed\";\n\n if (result.success) {\n this.#createdInternalIds = result.createdInternalIds;\n }\n\n return {\n success: result.success,\n };\n }\n\n /**\n * Get the retrieval operations (for inspection/debugging)\n */\n getRetrievalOperations(): ReadonlyArray<RetrievalOperation<TSchema>> {\n return this.#retrievalOps;\n }\n\n /**\n * Get the mutation operations (for inspection/debugging)\n */\n getMutationOperations(): ReadonlyArray<MutationOperation<TSchema>> {\n return this.#mutationOps;\n }\n\n /**\n * Get the IDs of created entities after executeMutations() has been called.\n * Returns FragnoId objects with external IDs (always available) and internal IDs\n * (available when database supports RETURNING).\n *\n * @throws Error if called before executeMutations()\n * @returns Array of FragnoIds in the same order as create() calls\n */\n getCreatedIds(): FragnoId[] {\n if (this.#state !== \"executed\") {\n throw new Error(\n `getCreatedIds() can only be called after executeMutations(). Current state: ${this.#state}`,\n );\n }\n\n const createdIds: FragnoId[] = [];\n let createIndex = 0;\n\n for (const op of this.#mutationOps) {\n if (op.type === \"create\") {\n const internalId = this.#createdInternalIds[createIndex] ?? undefined;\n createdIds.push(\n new FragnoId({\n externalId: op.generatedExternalId,\n internalId,\n version: 0, // New records always start at version 0\n }),\n );\n createIndex++;\n }\n }\n\n return createdIds;\n }\n\n /**\n * @internal\n * Compile the unit of work to executable queries for testing\n */\n compile<TOutput>(compiler: UOWCompiler<TSchema, TOutput>): {\n name?: string;\n retrievalBatch: TOutput[];\n mutationBatch: CompiledMutation<TOutput>[];\n } {\n const retrievalBatch: TOutput[] = [];\n for (const op of this.#retrievalOps) {\n const compiled = compiler.compileRetrievalOperation(op);\n if (compiled !== null) {\n retrievalBatch.push(compiled);\n }\n }\n\n const mutationBatch: CompiledMutation<TOutput>[] = [];\n for (const op of this.#mutationOps) {\n const compiled = compiler.compileMutationOperation(op);\n if (compiled !== null) {\n mutationBatch.push(compiled);\n }\n }\n\n return {\n name: this.#name,\n retrievalBatch,\n mutationBatch,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"unit-of-work.js","names":["#table","#tableName","#indexName","#whereClause","#countMode","#selectClause","#orderByIndexClause","#afterCursor","#cursorMetadata","#beforeCursor","#pageSizeValue","#joinClause","compiledJoins: CompiledJoin[] | undefined","options: FindOptions<TTable, TSelect>","#id","#setValues","#checkVersion","compiled: CompiledJoin[]","builder: Record<string, unknown>","conditions: Condition | undefined","orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined","#schema","#compiler","#executor","#decoder","#name","#config","#state","#retrievalOps","retrievalBatch: unknown[]","#retrievalResults","externalId: string","#mutationOps","mutationBatch: CompiledMutation<unknown>[]","#createdInternalIds","createdIds: FragnoId[]","retrievalBatch: TOutput[]","mutationBatch: CompiledMutation<TOutput>[]"],"sources":["../../src/query/unit-of-work.ts"],"sourcesContent":["import type { AnySchema, AnyTable, Index, IdColumn, AnyColumn, Relation } from \"../schema/create\";\nimport { FragnoId } from \"../schema/create\";\nimport type { Condition, ConditionBuilder } from \"./condition-builder\";\nimport type { SelectClause, TableToInsertValues, TableToUpdateValues, SelectResult } from \"./query\";\nimport { buildCondition } from \"./condition-builder\";\nimport type { CompiledJoin } from \"./orm/orm\";\nimport type { CursorResult } from \"./cursor\";\nimport { Cursor } from \"./cursor\";\n\n/**\n * Builder for updateMany operations that supports both whereIndex and set chaining\n */\nexport interface UpdateManyBuilder<TTable extends AnyTable> {\n whereIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n condition?: (eb: IndexSpecificConditionBuilder<TTable, TIndexName>) => Condition | boolean,\n ): this;\n set(values: TableToUpdateValues<TTable>): this;\n}\n\n/**\n * Extract column names from a single index\n */\nexport type IndexColumns<TIndex extends Index> = TIndex[\"columnNames\"][number];\n\ntype RemoveEmptyObject<T> = T extends object ? (keyof T extends never ? never : T) : never;\n\n/**\n * Extract all indexed column names from a table's indexes\n */\ntype IndexedColumns<TIndexes extends Record<string, Index>> = TIndexes[keyof TIndexes] extends Index\n ? IndexColumns<TIndexes[keyof TIndexes]>\n : never;\n\ntype OmitNever<T> = { [K in keyof T as T[K] extends never ? never : K]: T[K] };\n\n/**\n * Extract the name of the ID column from a table\n * Checks if column has 'id' property set to true (which IdColumn class has)\n */\nexport type InferIdColumnName<TTable extends AnyTable> = keyof OmitNever<{\n [K in keyof TTable[\"columns\"]]: TTable[\"columns\"][K] extends IdColumn<\n infer _,\n infer __,\n infer ___\n >\n ? K\n : never;\n}>;\n\n/**\n * Get the columns for a specific index name.\n * For \"primary\", returns only the ID column.\n * For named indexes, returns the columns defined in that index.\n */\ntype ColumnsForIndex<\n TTable extends AnyTable,\n TIndexName extends ValidIndexName<TTable>,\n> = TIndexName extends \"primary\"\n ? Pick<TTable[\"columns\"], InferIdColumnName<TTable>>\n : TIndexName extends keyof TTable[\"indexes\"]\n ? Pick<TTable[\"columns\"], IndexColumns<TTable[\"indexes\"][TIndexName]>>\n : never;\n\n/**\n * ConditionBuilder restricted to indexed columns only.\n * Used throughout Unit of Work to ensure all queries can leverage indexes for optimal performance.\n */\nexport type IndexedConditionBuilder<TTable extends AnyTable> = ConditionBuilder<\n Pick<TTable[\"columns\"], IndexedColumns<TTable[\"indexes\"]>>\n>;\n\n/**\n * ConditionBuilder restricted to columns in a specific index.\n */\ntype IndexSpecificConditionBuilder<\n TTable extends AnyTable,\n TIndexName extends ValidIndexName<TTable>,\n> = ConditionBuilder<ColumnsForIndex<TTable, TIndexName>>;\n\n/**\n * Valid index names for a table, including the static \"primary\" index\n */\nexport type ValidIndexName<TTable extends AnyTable> =\n | \"primary\"\n | (string & keyof TTable[\"indexes\"]);\n\n/**\n * Find options for Unit of Work (internal, used after builder finalization)\n */\ntype FindOptions<\n TTable extends AnyTable = AnyTable,\n TSelect extends SelectClause<TTable> = SelectClause<TTable>,\n> = {\n /**\n * Which index to use for this query (required)\n */\n useIndex: string;\n /**\n * Select clause - which columns to return\n */\n select?: TSelect;\n /**\n * Where clause - filtering restricted to indexed columns only\n */\n where?: (eb: IndexedConditionBuilder<TTable>) => Condition | boolean;\n /**\n * Order by index - specify which index to order by and direction\n */\n orderByIndex?: {\n indexName: string;\n direction: \"asc\" | \"desc\";\n };\n /**\n * Cursor for pagination - continue after this cursor\n */\n after?: Cursor | string;\n /**\n * Cursor for pagination - continue before this cursor\n */\n before?: Cursor | string;\n /**\n * Number of results per page\n */\n pageSize?: number;\n /**\n * Join operations to include related data\n */\n joins?: CompiledJoin[];\n};\n\n/**\n * Unit of Work state machine\n */\nexport type UOWState = \"building-retrieval\" | \"building-mutation\" | \"executed\";\n\n/**\n * Retrieval operation - read operations in the first phase\n */\nexport type RetrievalOperation<\n TSchema extends AnySchema,\n TTable extends AnyTable = TSchema[\"tables\"][keyof TSchema[\"tables\"]],\n> =\n | {\n type: \"find\";\n table: TTable;\n indexName: string;\n options: FindOptions<TTable, SelectClause<TTable>>;\n withCursor?: boolean;\n }\n | {\n type: \"count\";\n table: TTable;\n indexName: string;\n options: Pick<FindOptions<TTable>, \"where\" | \"useIndex\">;\n };\n\n/**\n * Mutation operations - write operations in the second phase\n */\nexport type MutationOperation<\n TSchema extends AnySchema,\n TTable extends AnyTable = TSchema[\"tables\"][keyof TSchema[\"tables\"]],\n> =\n | {\n type: \"update\";\n table: TTable[\"name\"];\n id: FragnoId | string;\n checkVersion: boolean;\n set: TableToUpdateValues<TTable>;\n }\n | {\n type: \"create\";\n table: TTable[\"name\"];\n values: TableToInsertValues<TTable>;\n generatedExternalId: string;\n }\n | {\n type: \"delete\";\n table: TTable[\"name\"];\n id: FragnoId | string;\n checkVersion: boolean;\n };\n\n/**\n * Compiled mutation with metadata for execution\n */\nexport interface CompiledMutation<TOutput> {\n query: TOutput;\n /**\n * Number of rows this operation must affect for the transaction to succeed.\n * If actual affected rows doesn't match, it indicates a version conflict.\n * null means don't check affected rows (e.g., for create operations).\n */\n expectedAffectedRows: number | null;\n}\n\n/**\n * Compiler interface for Unit of Work operations\n */\nexport interface UOWCompiler<TSchema extends AnySchema, TOutput> {\n /**\n * Compile a retrieval operation to the adapter's query format\n */\n compileRetrievalOperation(op: RetrievalOperation<TSchema>): TOutput | null;\n\n /**\n * Compile a mutation operation to the adapter's query format\n */\n compileMutationOperation(op: MutationOperation<TSchema>): CompiledMutation<TOutput> | null;\n}\n\nexport type MutationResult =\n | { success: true; createdInternalIds: (bigint | null)[] }\n | { success: false };\n\n/**\n * Executor interface for Unit of Work operations\n */\nexport interface UOWExecutor<TOutput, TRawResult = unknown> {\n /**\n * Execute the retrieval phase - all queries run in a single transaction for snapshot isolation\n */\n executeRetrievalPhase(retrievalBatch: TOutput[]): Promise<TRawResult[]>;\n\n /**\n * Execute the mutation phase - all queries run in a transaction with version checks\n * Returns success status indicating if mutations completed without conflicts,\n * and internal IDs for create operations (null if database doesn't support RETURNING)\n */\n executeMutationPhase(mutationBatch: CompiledMutation<TOutput>[]): Promise<MutationResult>;\n}\n\n/**\n * Decoder interface for Unit of Work retrieval results\n *\n * Transforms raw database results into application format (e.g., converting raw columns\n * into FragnoId objects with external ID, internal ID, and version).\n */\nexport interface UOWDecoder<TSchema extends AnySchema, TRawInput = unknown> {\n /**\n * Decode raw database results from the retrieval phase\n *\n * @param rawResults - Array of raw result sets from database queries\n * @param operations - Array of retrieval operations that produced these results\n * @returns Decoded results in application format\n */\n (rawResults: TRawInput[], operations: RetrievalOperation<TSchema>[]): unknown[];\n}\n\n/**\n * Builder for find operations in Unit of Work\n */\nexport class FindBuilder<\n TTable extends AnyTable,\n TSelect extends SelectClause<TTable> = true,\n TJoinOut = {},\n> {\n readonly #table: TTable;\n readonly #tableName: string;\n\n #indexName?: string;\n #whereClause?: (eb: IndexedConditionBuilder<TTable>) => Condition | boolean;\n #orderByIndexClause?: {\n indexName: string;\n direction: \"asc\" | \"desc\";\n };\n #afterCursor?: Cursor | string;\n #beforeCursor?: Cursor | string;\n #pageSizeValue?: number;\n #selectClause?: TSelect;\n #joinClause?: (jb: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TJoinOut>;\n #countMode = false;\n #cursorMetadata?: Cursor;\n\n constructor(tableName: string, table: TTable) {\n this.#tableName = tableName;\n this.#table = table;\n }\n\n /**\n * Specify which index to use and optionally filter the results\n */\n whereIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n condition?: (eb: IndexSpecificConditionBuilder<TTable, TIndexName>) => Condition | boolean,\n ): this {\n // Validate index exists (primary is always valid)\n if (indexName !== \"primary\" && !(indexName in this.#table.indexes)) {\n throw new Error(\n `Index \"${String(indexName)}\" not found on table \"${this.#tableName}\". ` +\n `Available indexes: primary, ${Object.keys(this.#table.indexes).join(\", \")}`,\n );\n }\n\n this.#indexName = indexName === \"primary\" ? \"_primary\" : indexName;\n if (condition) {\n // Safe: IndexSpecificConditionBuilder is a subset of IndexedConditionBuilder.\n // The condition will only reference columns in the specific index, which are also indexed columns.\n this.#whereClause = condition as unknown as (\n eb: IndexedConditionBuilder<TTable>,\n ) => Condition | boolean;\n }\n return this;\n }\n\n /**\n * Specify columns to select\n * @throws Error if selectCount() has already been called\n */\n select<const TNewSelect extends SelectClause<TTable>>(\n columns: TNewSelect,\n ): FindBuilder<TTable, TNewSelect, TJoinOut> {\n if (this.#countMode) {\n throw new Error(\n `Cannot call select() after selectCount() on table \"${this.#tableName}\". ` +\n `Use either select() or selectCount(), not both.`,\n );\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).#selectClause = columns;\n return this as unknown as FindBuilder<TTable, TNewSelect, TJoinOut>;\n }\n\n /**\n * Select count instead of records\n * @throws Error if select() has already been called\n */\n selectCount(): this {\n if (this.#selectClause !== undefined) {\n throw new Error(\n `Cannot call selectCount() after select() on table \"${this.#tableName}\". ` +\n `Use either select() or selectCount(), not both.`,\n );\n }\n this.#countMode = true;\n return this;\n }\n\n /**\n * Order results by index in ascending or descending order\n */\n orderByIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n direction: \"asc\" | \"desc\",\n ): this {\n // Validate index exists (primary is always valid)\n if (indexName !== \"primary\" && !(indexName in this.#table.indexes)) {\n throw new Error(\n `Index \"${String(indexName)}\" not found on table \"${this.#tableName}\". ` +\n `Available indexes: primary, ${Object.keys(this.#table.indexes).join(\", \")}`,\n );\n }\n\n this.#orderByIndexClause = {\n indexName: indexName === \"primary\" ? \"_primary\" : indexName,\n direction,\n };\n return this;\n }\n\n /**\n * Set cursor to continue pagination after this point (forward pagination)\n * If a Cursor object is provided, its metadata will be used to set defaults for\n * index, orderByIndex, and pageSize (if not explicitly set)\n */\n after(cursor: Cursor | string): this {\n this.#afterCursor = cursor;\n if (cursor instanceof Cursor) {\n this.#cursorMetadata = cursor;\n }\n return this;\n }\n\n /**\n * Set cursor to continue pagination before this point (backward pagination)\n * If a Cursor object is provided, its metadata will be used to set defaults for\n * index, orderByIndex, and pageSize (if not explicitly set)\n */\n before(cursor: Cursor | string): this {\n this.#beforeCursor = cursor;\n if (cursor instanceof Cursor) {\n this.#cursorMetadata = cursor;\n }\n return this;\n }\n\n /**\n * Set the number of results per page\n */\n pageSize(size: number): this {\n this.#pageSizeValue = size;\n return this;\n }\n\n /**\n * Add joins to include related data\n * Join where clauses are restricted to indexed columns only\n */\n join<TNewJoinOut>(\n joinFn: (jb: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TNewJoinOut>,\n ): FindBuilder<TTable, TSelect, TNewJoinOut> {\n this.#joinClause = joinFn;\n return this as unknown as FindBuilder<TTable, TSelect, TNewJoinOut>;\n }\n\n /**\n * @internal\n */\n build():\n | { type: \"find\"; indexName: string; options: FindOptions<TTable, TSelect> }\n | {\n type: \"count\";\n indexName: string;\n options: Pick<FindOptions<TTable>, \"where\" | \"useIndex\">;\n } {\n // Apply cursor metadata as defaults if available and not explicitly set\n let indexName = this.#indexName;\n let orderByIndex = this.#orderByIndexClause;\n let pageSize = this.#pageSizeValue;\n\n if (this.#cursorMetadata) {\n // Use cursor metadata as defaults\n if (!indexName) {\n indexName = this.#cursorMetadata.indexName;\n }\n if (!orderByIndex) {\n orderByIndex = {\n indexName: this.#cursorMetadata.indexName,\n direction: this.#cursorMetadata.orderDirection,\n };\n }\n if (pageSize === undefined) {\n pageSize = this.#cursorMetadata.pageSize;\n }\n\n // Validate that explicit params match cursor params\n if (indexName && indexName !== this.#cursorMetadata.indexName) {\n throw new Error(\n `Index mismatch: builder specifies \"${indexName}\" but cursor specifies \"${this.#cursorMetadata.indexName}\"`,\n );\n }\n if (\n orderByIndex &&\n (orderByIndex.indexName !== this.#cursorMetadata.indexName ||\n orderByIndex.direction !== this.#cursorMetadata.orderDirection)\n ) {\n throw new Error(`Order mismatch: builder and cursor specify different ordering`);\n }\n if (pageSize !== undefined && pageSize !== this.#cursorMetadata.pageSize) {\n throw new Error(\n `Page size mismatch: builder specifies ${pageSize} but cursor specifies ${this.#cursorMetadata.pageSize}`,\n );\n }\n }\n\n if (!indexName) {\n throw new Error(\n `Must specify an index using .whereIndex() before finalizing find operation on table \"${this.#tableName}\"`,\n );\n }\n\n // If in count mode, return count operation\n if (this.#countMode) {\n return {\n type: \"count\",\n indexName,\n options: {\n useIndex: indexName,\n where: this.#whereClause,\n },\n };\n }\n\n // Compile joins if provided\n let compiledJoins: CompiledJoin[] | undefined;\n if (this.#joinClause) {\n compiledJoins = buildJoinIndexed(this.#table, this.#joinClause);\n }\n\n // Convert Cursor objects to strings for after/before\n const afterCursor =\n this.#afterCursor instanceof Cursor ? this.#afterCursor.encode() : this.#afterCursor;\n const beforeCursor =\n this.#beforeCursor instanceof Cursor ? this.#beforeCursor.encode() : this.#beforeCursor;\n\n const options: FindOptions<TTable, TSelect> = {\n useIndex: indexName,\n select: this.#selectClause,\n where: this.#whereClause,\n orderByIndex,\n after: afterCursor,\n before: beforeCursor,\n pageSize,\n joins: compiledJoins,\n };\n\n return { type: \"find\", indexName, options };\n }\n}\n\n/**\n * Builder for update operations in Unit of Work\n */\nexport class UpdateBuilder<TTable extends AnyTable> {\n readonly #tableName: string;\n readonly #id: FragnoId | string;\n\n #checkVersion = false;\n #setValues?: TableToUpdateValues<TTable>;\n\n constructor(tableName: string, id: FragnoId | string) {\n this.#tableName = tableName;\n this.#id = id;\n }\n\n /**\n * Specify values to update\n */\n set(values: TableToUpdateValues<TTable>): this {\n this.#setValues = values;\n return this;\n }\n\n /**\n * Enable version checking for optimistic concurrency control\n * @throws Error if the ID is just a string (no version available)\n */\n check(): this {\n if (typeof this.#id === \"string\") {\n throw new Error(\n `Cannot use check() with a string ID on table \"${this.#tableName}\". ` +\n `Version checking requires a FragnoId with version information.`,\n );\n }\n this.#checkVersion = true;\n return this;\n }\n\n /**\n * @internal\n */\n build(): {\n id: FragnoId | string;\n checkVersion: boolean;\n set: TableToUpdateValues<TTable>;\n } {\n if (!this.#setValues) {\n throw new Error(\n `Must specify values using .set() before finalizing update operation on table \"${this.#tableName}\"`,\n );\n }\n\n return {\n id: this.#id,\n checkVersion: this.#checkVersion,\n set: this.#setValues,\n };\n }\n}\n\n/**\n * Builder for delete operations in Unit of Work\n */\nexport class DeleteBuilder {\n readonly #tableName: string;\n readonly #id: FragnoId | string;\n\n #checkVersion = false;\n\n constructor(tableName: string, id: FragnoId | string) {\n this.#tableName = tableName;\n this.#id = id;\n }\n\n /**\n * Enable version checking for optimistic concurrency control\n * @throws Error if the ID is just a string (no version available)\n */\n check(): this {\n if (typeof this.#id === \"string\") {\n throw new Error(\n `Cannot use check() with a string ID on table \"${this.#tableName}\". ` +\n `Version checking requires a FragnoId with version information.`,\n );\n }\n this.#checkVersion = true;\n return this;\n }\n\n /**\n * @internal\n */\n build(): { id: FragnoId | string; checkVersion: boolean } {\n return {\n id: this.#id,\n checkVersion: this.#checkVersion,\n };\n }\n}\n\n/**\n * Builder for join operations in Unit of Work\n * Similar to FindBuilder but tailored for joins (no cursor pagination, no count mode)\n */\nexport class JoinFindBuilder<\n TTable extends AnyTable,\n TSelect extends SelectClause<TTable> = true,\n TJoinOut = {},\n> {\n readonly #table: TTable;\n readonly #tableName: string;\n\n #indexName?: string;\n #whereClause?: (eb: IndexedConditionBuilder<TTable>) => Condition | boolean;\n #orderByIndexClause?: {\n indexName: string;\n direction: \"asc\" | \"desc\";\n };\n #pageSizeValue?: number;\n #selectClause?: TSelect;\n #joinClause?: (jb: IndexedJoinBuilder<TTable, TJoinOut>) => IndexedJoinBuilder<TTable, TJoinOut>;\n\n constructor(tableName: string, table: TTable) {\n this.#tableName = tableName;\n this.#table = table;\n }\n\n /**\n * Specify which index to use and optionally filter the results\n */\n whereIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n condition?: (eb: IndexSpecificConditionBuilder<TTable, TIndexName>) => Condition | boolean,\n ): this {\n // Validate index exists (primary is always valid)\n if (indexName !== \"primary\" && !(indexName in this.#table.indexes)) {\n throw new Error(\n `Index \"${String(indexName)}\" not found on table \"${this.#tableName}\". ` +\n `Available indexes: primary, ${Object.keys(this.#table.indexes).join(\", \")}`,\n );\n }\n\n this.#indexName = indexName === \"primary\" ? \"_primary\" : indexName;\n if (condition) {\n // Safe: IndexSpecificConditionBuilder is a subset of IndexedConditionBuilder.\n // The condition will only reference columns in the specific index, which are also indexed columns.\n this.#whereClause = condition as unknown as (\n eb: IndexedConditionBuilder<TTable>,\n ) => Condition | boolean;\n }\n return this;\n }\n\n /**\n * Specify columns to select\n */\n select<const TNewSelect extends SelectClause<TTable>>(\n columns: TNewSelect,\n ): JoinFindBuilder<TTable, TNewSelect, TJoinOut> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).#selectClause = columns;\n return this as unknown as JoinFindBuilder<TTable, TNewSelect, TJoinOut>;\n }\n\n /**\n * Order results by index in ascending or descending order\n */\n orderByIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n direction: \"asc\" | \"desc\",\n ): this {\n // Validate index exists (primary is always valid)\n if (indexName !== \"primary\" && !(indexName in this.#table.indexes)) {\n throw new Error(\n `Index \"${String(indexName)}\" not found on table \"${this.#tableName}\". ` +\n `Available indexes: primary, ${Object.keys(this.#table.indexes).join(\", \")}`,\n );\n }\n\n this.#orderByIndexClause = {\n indexName: indexName === \"primary\" ? \"_primary\" : indexName,\n direction,\n };\n return this;\n }\n\n /**\n * Set the number of results to return\n */\n pageSize(size: number): this {\n this.#pageSizeValue = size;\n return this;\n }\n\n /**\n * Add joins to include related data\n * Join where clauses are restricted to indexed columns only\n */\n join<TNewJoinOut>(\n joinFn: (jb: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TNewJoinOut>,\n ): JoinFindBuilder<TTable, TSelect, TJoinOut & TNewJoinOut> {\n this.#joinClause = joinFn;\n return this as unknown as JoinFindBuilder<TTable, TSelect, TJoinOut & TNewJoinOut>;\n }\n\n /**\n * @internal\n */\n build(): {\n indexName: string | undefined;\n select: TSelect | undefined;\n where: ((eb: IndexedConditionBuilder<TTable>) => Condition | boolean) | undefined;\n orderByIndex:\n | {\n indexName: string;\n direction: \"asc\" | \"desc\";\n }\n | undefined;\n pageSize: number | undefined;\n joins: CompiledJoin[] | undefined;\n } {\n // Compile joins if provided\n let compiledJoins: CompiledJoin[] | undefined;\n if (this.#joinClause) {\n compiledJoins = buildJoinIndexed(this.#table, this.#joinClause);\n }\n\n return {\n indexName: this.#indexName,\n select: this.#selectClause,\n where: this.#whereClause,\n orderByIndex: this.#orderByIndexClause,\n pageSize: this.#pageSizeValue,\n joins: compiledJoins,\n };\n }\n}\n\ninterface MapRelationType<T> {\n // FIXME: Not sure why we need the RemoveEmptyObject, we should somehow fix at the source where it's added to the union\n one: RemoveEmptyObject<T> | null;\n many: RemoveEmptyObject<T>[];\n}\n\n/**\n * Join builder with indexed-only where clauses for Unit of Work\n * TJoinOut accumulates the types of all joined relations\n */\nexport type IndexedJoinBuilder<TTable extends AnyTable, TJoinOut> = {\n [K in keyof TTable[\"relations\"]]: TTable[\"relations\"][K] extends Relation<\n infer TRelationType,\n infer TTargetTable\n >\n ? <TSelect extends SelectClause<TTable[\"relations\"][K][\"table\"]> = true, TNestedJoinOut = {}>(\n builderFn?: (\n builder: JoinFindBuilder<TTable[\"relations\"][K][\"table\"]>,\n ) => JoinFindBuilder<TTable[\"relations\"][K][\"table\"], TSelect, TNestedJoinOut>,\n ) => IndexedJoinBuilder<\n TTable,\n TJoinOut & {\n [P in K]: MapRelationType<\n SelectResult<TTargetTable, TNestedJoinOut, TSelect>\n >[TRelationType];\n }\n >\n : never;\n};\n\n/**\n * Build join operations with indexed-only where clauses for Unit of Work\n * This ensures all join conditions can leverage indexes for optimal performance\n */\nexport function buildJoinIndexed<TTable extends AnyTable, TJoinOut>(\n table: TTable,\n fn: (builder: IndexedJoinBuilder<TTable, {}>) => IndexedJoinBuilder<TTable, TJoinOut>,\n): CompiledJoin[] {\n const compiled: CompiledJoin[] = [];\n const builder: Record<string, unknown> = {};\n\n for (const name in table.relations) {\n const relation = table.relations[name]!;\n\n builder[name] = (builderFn?: (b: JoinFindBuilder<AnyTable>) => JoinFindBuilder<AnyTable>) => {\n // Create join builder for this relation's table\n const joinBuilder = new JoinFindBuilder(relation.table.ormName, relation.table);\n if (builderFn) {\n builderFn(joinBuilder);\n }\n const config = joinBuilder.build();\n\n // Build condition with indexed columns only\n let conditions: Condition | undefined;\n if (config.where) {\n const cond = buildCondition(relation.table.columns, config.where);\n if (cond === true) {\n conditions = undefined;\n } else if (cond === false) {\n // If condition evaluates to false, skip this join\n compiled.push({\n relation,\n options: false,\n });\n delete builder[name];\n return builder;\n } else {\n conditions = cond;\n }\n }\n\n // Build orderBy from orderByIndex if provided\n let orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined;\n if (config.orderByIndex) {\n const index = relation.table.indexes[config.orderByIndex.indexName];\n if (index) {\n // Use all columns from the index for ordering\n orderBy = index.columns.map(\n (col) => [col, config.orderByIndex!.direction] as [AnyColumn, \"asc\" | \"desc\"],\n );\n } else {\n // Fallback to ID column if index not found\n orderBy = [[relation.table.getIdColumn(), config.orderByIndex.direction]];\n }\n }\n\n compiled.push({\n relation,\n options: {\n select: config.select ?? true,\n where: conditions,\n orderBy,\n join: config.joins,\n limit: config.pageSize,\n },\n });\n\n delete builder[name];\n return builder;\n };\n }\n\n fn(builder as IndexedJoinBuilder<TTable, {}>);\n return compiled;\n}\n\nexport function createUnitOfWork<\n const TSchema extends AnySchema,\n const TRetrievalResults extends unknown[] = [],\n const TRawInput = unknown,\n>(\n schema: TSchema,\n compiler: UOWCompiler<TSchema, unknown>,\n executor: UOWExecutor<unknown, TRawInput>,\n decoder: UOWDecoder<TSchema, TRawInput>,\n name?: string,\n): UnitOfWork<TSchema, TRetrievalResults, TRawInput> {\n return new UnitOfWork(schema, compiler, executor, decoder, name) as UnitOfWork<\n TSchema,\n TRetrievalResults,\n TRawInput\n >;\n}\n\nexport interface UnitOfWorkConfig {\n dryRun?: boolean;\n onQuery?: (query: unknown) => void;\n}\n\n/**\n * Unit of Work implementation with optimistic concurrency control\n *\n * UOW has two phases:\n * 1. Retrieval phase: Read operations to fetch entities with their versions\n * 2. Mutation phase: Write operations that check versions before committing\n *\n * @example\n * ```ts\n * const uow = queryEngine.createUnitOfWork(\"update-user-balance\");\n *\n * // Retrieval phase\n * uow.find(\"users\", (b) => b.where(\"primary\", (eb) => eb(\"id\", \"=\", userId)));\n *\n * // Execute retrieval and transition to mutation phase\n * const [users] = await uow.executeRetrieve();\n *\n * // Mutation phase with version check\n * const user = users[0];\n * uow.update(\"users\", user.id, (b) => b.set({ balance: newBalance }).check());\n *\n * // Execute mutations\n * const { success } = await uow.executeMutations();\n * if (!success) {\n * // Handle version conflict\n * }\n * ```\n */\nexport class UnitOfWork<\n const TSchema extends AnySchema,\n const TRetrievalResults extends unknown[] = [],\n const TRawInput = unknown,\n> {\n #schema: TSchema;\n\n #name?: string;\n #config?: UnitOfWorkConfig;\n\n #state: UOWState = \"building-retrieval\";\n\n #retrievalOps: RetrievalOperation<TSchema>[] = [];\n #mutationOps: MutationOperation<TSchema>[] = [];\n\n #compiler: UOWCompiler<TSchema, unknown>;\n #executor: UOWExecutor<unknown, TRawInput>;\n #decoder: UOWDecoder<TSchema, TRawInput>;\n\n #retrievalResults?: TRetrievalResults;\n #createdInternalIds: (bigint | null)[] = [];\n\n constructor(\n schema: TSchema,\n compiler: UOWCompiler<TSchema, unknown>,\n executor: UOWExecutor<unknown, TRawInput>,\n decoder: UOWDecoder<TSchema, TRawInput>,\n name?: string,\n config?: UnitOfWorkConfig,\n ) {\n this.#schema = schema;\n this.#compiler = compiler;\n this.#executor = executor;\n this.#decoder = decoder;\n this.#name = name;\n this.#config = config;\n }\n\n get schema(): TSchema {\n return this.#schema;\n }\n\n get state(): UOWState {\n return this.#state;\n }\n\n get name(): string | undefined {\n return this.#name;\n }\n\n /**\n * Execute the retrieval phase and transition to mutation phase\n * Returns all results from find operations\n */\n async executeRetrieve(): Promise<TRetrievalResults> {\n if (this.#retrievalOps.length === 0) {\n return [] as unknown as TRetrievalResults;\n }\n\n if (this.#state !== \"building-retrieval\") {\n throw new Error(\n `Cannot execute retrieval from state ${this.#state}. Must be in building-retrieval state.`,\n );\n }\n\n // Compile retrieval operations\n const retrievalBatch: unknown[] = [];\n for (const op of this.#retrievalOps) {\n const compiled = this.#compiler.compileRetrievalOperation(op);\n if (compiled !== null) {\n this.#config?.onQuery?.(compiled);\n\n retrievalBatch.push(compiled);\n }\n }\n\n if (this.#config?.dryRun) {\n this.#state = \"executed\";\n return [] as unknown as TRetrievalResults;\n }\n\n const results = this.#decoder(\n await this.#executor.executeRetrievalPhase(retrievalBatch),\n this.#retrievalOps,\n );\n\n // Store results and transition to mutation phase\n this.#retrievalResults = results as TRetrievalResults;\n this.#state = \"building-mutation\";\n\n return this.#retrievalResults;\n }\n\n /**\n * Add a find operation using a builder callback (retrieval phase only)\n */\n find<\n TTableName extends keyof TSchema[\"tables\"] & string,\n TSelect extends SelectClause<TSchema[\"tables\"][TTableName]> = true,\n TJoinOut = {},\n >(\n tableName: TTableName,\n builderFn?: (\n // We omit \"build\" because we don't want to expose it to the user\n builder: Omit<FindBuilder<TSchema[\"tables\"][TTableName]>, \"build\">,\n ) => Omit<FindBuilder<TSchema[\"tables\"][TTableName], TSelect, TJoinOut>, \"build\"> | void,\n ): UnitOfWork<\n TSchema,\n [...TRetrievalResults, SelectResult<TSchema[\"tables\"][TTableName], TJoinOut, TSelect>[]],\n TRawInput\n > {\n if (this.#state !== \"building-retrieval\") {\n throw new Error(\n `find() can only be called during retrieval phase. Current state: ${this.#state}`,\n );\n }\n\n const table = this.#schema.tables[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n // Create builder, pass to callback (or use default), then extract configuration\n const builder = new FindBuilder(tableName, table as TSchema[\"tables\"][TTableName]);\n if (builderFn) {\n builderFn(builder);\n } else {\n // Default to primary index with no filter\n builder.whereIndex(\"primary\");\n }\n const { indexName, options, type } = builder.build();\n\n this.#retrievalOps.push({\n type,\n // Safe: we know the table is part of the schema from the find() method\n table: table as TSchema[\"tables\"][TTableName],\n indexName,\n // Safe: we're storing the options for later compilation\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: options as any,\n });\n\n return this as unknown as UnitOfWork<\n TSchema,\n [...TRetrievalResults, SelectResult<TSchema[\"tables\"][TTableName], TJoinOut, TSelect>[]],\n TRawInput\n >;\n }\n\n /**\n * Add a find operation with cursor metadata (retrieval phase only)\n */\n findWithCursor<\n TTableName extends keyof TSchema[\"tables\"] & string,\n TSelect extends SelectClause<TSchema[\"tables\"][TTableName]> = true,\n TJoinOut = {},\n >(\n tableName: TTableName,\n builderFn: (\n // We omit \"build\" because we don't want to expose it to the user\n builder: Omit<FindBuilder<TSchema[\"tables\"][TTableName]>, \"build\">,\n ) => Omit<FindBuilder<TSchema[\"tables\"][TTableName], TSelect, TJoinOut>, \"build\"> | void,\n ): UnitOfWork<\n TSchema,\n [\n ...TRetrievalResults,\n CursorResult<SelectResult<TSchema[\"tables\"][TTableName], TJoinOut, TSelect>>,\n ],\n TRawInput\n > {\n if (this.#state !== \"building-retrieval\") {\n throw new Error(\n `findWithCursor() can only be called during retrieval phase. Current state: ${this.#state}`,\n );\n }\n\n const table = this.#schema.tables[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n // Create builder and pass to callback\n const builder = new FindBuilder(tableName, table as TSchema[\"tables\"][TTableName]);\n builderFn(builder);\n const { indexName, options, type } = builder.build();\n\n this.#retrievalOps.push({\n type,\n // Safe: we know the table is part of the schema from the findWithCursor() method\n table: table as TSchema[\"tables\"][TTableName],\n indexName,\n // Safe: we're storing the options for later compilation\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: options as any,\n withCursor: true,\n });\n\n return this as unknown as UnitOfWork<\n TSchema,\n [\n ...TRetrievalResults,\n CursorResult<SelectResult<TSchema[\"tables\"][TTableName], TJoinOut, TSelect>>,\n ],\n TRawInput\n >;\n }\n\n /**\n * Add a create operation (mutation phase only)\n * Returns a FragnoId with the external ID that can be used immediately in subsequent operations\n */\n create<TableName extends keyof TSchema[\"tables\"] & string>(\n table: TableName,\n values: TableToInsertValues<TSchema[\"tables\"][TableName]>,\n ): FragnoId {\n if (this.#state === \"executed\") {\n throw new Error(`create() can only be called during mutation phase.`);\n }\n\n const tableSchema = this.#schema.tables[table];\n if (!tableSchema) {\n throw new Error(`Table ${table} not found in schema`);\n }\n\n const idColumn = tableSchema.getIdColumn();\n let externalId: string;\n let updatedValues = values;\n\n // Check if ID value is provided in values\n const providedIdValue = (values as Record<string, unknown>)[idColumn.ormName];\n\n if (providedIdValue !== undefined) {\n // Extract string from FragnoId or use string directly\n if (\n typeof providedIdValue === \"object\" &&\n providedIdValue !== null &&\n \"externalId\" in providedIdValue\n ) {\n externalId = (providedIdValue as FragnoId).externalId;\n } else {\n externalId = providedIdValue as string;\n }\n } else {\n // Generate using the column's default configuration\n const generated = idColumn.generateDefaultValue();\n if (generated === undefined) {\n throw new Error(\n `No ID value provided and ID column ${idColumn.ormName} has no default generator`,\n );\n }\n externalId = generated as string;\n\n // Add the generated ID to values so it's used in the insert\n updatedValues = {\n ...values,\n [idColumn.ormName]: externalId,\n } as TableToInsertValues<TSchema[\"tables\"][TableName]>;\n }\n\n this.#mutationOps.push({\n type: \"create\",\n table,\n values: updatedValues,\n generatedExternalId: externalId,\n });\n\n return FragnoId.fromExternal(externalId, 0);\n }\n\n /**\n * Add an update operation using a builder callback (mutation phase only)\n */\n update<TableName extends keyof TSchema[\"tables\"] & string>(\n table: TableName,\n id: FragnoId | string,\n builderFn: (\n // We omit \"build\" because we don't want to expose it to the user\n builder: Omit<UpdateBuilder<TSchema[\"tables\"][TableName]>, \"build\">,\n ) => Omit<UpdateBuilder<TSchema[\"tables\"][TableName]>, \"build\"> | void,\n ): void {\n if (this.#state === \"executed\") {\n throw new Error(`update() can only be called during mutation phase.`);\n }\n\n // Create builder, pass to callback, then extract configuration\n const builder = new UpdateBuilder<TSchema[\"tables\"][TableName]>(table, id);\n builderFn(builder);\n const { id: opId, checkVersion, set } = builder.build();\n\n this.#mutationOps.push({\n type: \"update\",\n table,\n id: opId,\n checkVersion,\n set,\n });\n }\n\n /**\n * Add a delete operation using a builder callback (mutation phase only)\n */\n delete<TableName extends keyof TSchema[\"tables\"] & string>(\n table: TableName,\n id: FragnoId | string,\n builderFn?: (\n // We omit \"build\" because we don't want to expose it to the user\n builder: Omit<DeleteBuilder, \"build\">,\n ) => Omit<DeleteBuilder, \"build\"> | void,\n ): void {\n if (this.#state === \"executed\") {\n throw new Error(`delete() can only be called during mutation phase.`);\n }\n\n // Create builder, optionally pass to callback, then extract configuration\n const builder = new DeleteBuilder(table, id);\n builderFn?.(builder);\n const { id: opId, checkVersion } = builder.build();\n\n this.#mutationOps.push({\n type: \"delete\",\n table,\n id: opId,\n checkVersion,\n });\n }\n\n /**\n * Execute the mutation phase\n * Returns success flag indicating if mutations completed without conflicts\n */\n async executeMutations(): Promise<{ success: boolean }> {\n if (this.#state === \"executed\") {\n throw new Error(`Cannot execute mutations from state ${this.#state}.`);\n }\n\n // Compile mutation operations\n const mutationBatch: CompiledMutation<unknown>[] = [];\n for (const op of this.#mutationOps) {\n const compiled = this.#compiler.compileMutationOperation(op);\n if (compiled !== null) {\n this.#config?.onQuery?.(compiled);\n mutationBatch.push(compiled);\n }\n }\n\n if (this.#config?.dryRun) {\n this.#state = \"executed\";\n return {\n success: true,\n };\n }\n\n // Execute mutation phase\n const result = await this.#executor.executeMutationPhase(mutationBatch);\n this.#state = \"executed\";\n\n if (result.success) {\n this.#createdInternalIds = result.createdInternalIds;\n }\n\n return {\n success: result.success,\n };\n }\n\n /**\n * Get the retrieval operations (for inspection/debugging)\n */\n getRetrievalOperations(): ReadonlyArray<RetrievalOperation<TSchema>> {\n return this.#retrievalOps;\n }\n\n /**\n * Get the mutation operations (for inspection/debugging)\n */\n getMutationOperations(): ReadonlyArray<MutationOperation<TSchema>> {\n return this.#mutationOps;\n }\n\n /**\n * Get the IDs of created entities after executeMutations() has been called.\n * Returns FragnoId objects with external IDs (always available) and internal IDs\n * (available when database supports RETURNING).\n *\n * @throws Error if called before executeMutations()\n * @returns Array of FragnoIds in the same order as create() calls\n */\n getCreatedIds(): FragnoId[] {\n if (this.#state !== \"executed\") {\n throw new Error(\n `getCreatedIds() can only be called after executeMutations(). Current state: ${this.#state}`,\n );\n }\n\n const createdIds: FragnoId[] = [];\n let createIndex = 0;\n\n for (const op of this.#mutationOps) {\n if (op.type === \"create\") {\n const internalId = this.#createdInternalIds[createIndex] ?? undefined;\n createdIds.push(\n new FragnoId({\n externalId: op.generatedExternalId,\n internalId,\n version: 0, // New records always start at version 0\n }),\n );\n createIndex++;\n }\n }\n\n return createdIds;\n }\n\n /**\n * @internal\n * Compile the unit of work to executable queries for testing\n */\n compile<TOutput>(compiler: UOWCompiler<TSchema, TOutput>): {\n name?: string;\n retrievalBatch: TOutput[];\n mutationBatch: CompiledMutation<TOutput>[];\n } {\n const retrievalBatch: TOutput[] = [];\n for (const op of this.#retrievalOps) {\n const compiled = compiler.compileRetrievalOperation(op);\n if (compiled !== null) {\n retrievalBatch.push(compiled);\n }\n }\n\n const mutationBatch: CompiledMutation<TOutput>[] = [];\n for (const op of this.#mutationOps) {\n const compiled = compiler.compileMutationOperation(op);\n if (compiled !== null) {\n mutationBatch.push(compiled);\n }\n }\n\n return {\n name: this.#name,\n retrievalBatch,\n mutationBatch,\n };\n }\n}\n"],"mappings":"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"}
@@ -66,6 +66,8 @@ function deserialize(value, col, provider) {
66
66
  if (value === null) return null;
67
67
  if (!supportJson.includes(provider) && col.type === "json" && typeof value === "string") return JSON.parse(value);
68
68
  if (provider === "sqlite" && (col.type === "timestamp" || col.type === "date") && (typeof value === "number" || typeof value === "string")) return new Date(value);
69
+ if ((provider === "postgresql" || provider === "cockroachdb") && (col.type === "timestamp" || col.type === "date") && typeof value === "string") return new Date(value);
70
+ if (provider === "mysql" && (col.type === "timestamp" || col.type === "date") && typeof value === "string") return new Date(value);
69
71
  if (col.type === "bool" && typeof value === "number") return value === 1;
70
72
  if (col.type === "bigint" && value instanceof Buffer) return value.readBigInt64BE(0);
71
73
  if (col.type === "bigint" && typeof value === "string") return BigInt(value);
@@ -1 +1 @@
1
- {"version":3,"file":"serialize.js","names":["supportJson: SQLProvider[]"],"sources":["../../src/schema/serialize.ts"],"sourcesContent":["import type { SQLProvider } from \"../shared/providers\";\nimport type { AnyColumn } from \"./create\";\nimport { FragnoId, FragnoReference } from \"./create\";\n\nexport interface AdditionalColumnMetadata {\n length?: number;\n precision?: number;\n scale?: number;\n}\n\n/**\n * Get the possible column types that the raw DB type can map to.\n */\nexport function dbToSchemaType(\n dbType: string,\n provider: SQLProvider,\n additional: AdditionalColumnMetadata,\n): (AnyColumn[\"type\"] | \"varchar(n)\")[] {\n dbType = dbType.toLowerCase();\n if (provider === \"sqlite\") {\n switch (dbType) {\n case \"integer\":\n return [\"bool\", \"date\", \"timestamp\", \"bigint\", \"integer\"];\n case \"text\":\n return [\"json\", \"string\", \"bigint\", \"varchar(n)\"];\n case \"real\":\n case \"numeric\":\n return [\"decimal\"];\n case \"blob\":\n return [\"bigint\", \"binary\"];\n default:\n return [dbType as AnyColumn[\"type\"]];\n }\n }\n\n if (provider === \"postgresql\" || provider === \"cockroachdb\") {\n switch (dbType) {\n case \"decimal\":\n case \"real\":\n case \"numeric\":\n case \"double precision\":\n return [\"decimal\"];\n case \"timestamp\":\n case \"timestamptz\":\n return [\"timestamp\"];\n case \"varchar\": {\n const len = additional.length;\n if (len != null) {\n return [`varchar(${len})`];\n }\n return [\"string\"];\n }\n case \"text\":\n return [\"string\"];\n case \"boolean\":\n case \"bool\":\n return [\"bool\"];\n case \"bytea\":\n return [\"binary\"];\n default:\n return [dbType as AnyColumn[\"type\"]];\n }\n }\n\n if (provider === \"mysql\") {\n switch (dbType) {\n case \"bool\":\n case \"boolean\":\n return [\"bool\"];\n case \"integer\":\n case \"int\":\n return [\"integer\"];\n case \"decimal\":\n case \"numeric\":\n case \"float\":\n case \"double\":\n return [\"decimal\"];\n case \"datetime\":\n return [\"timestamp\"];\n case \"varchar\": {\n const len = additional.length;\n if (len != null) {\n return [`varchar(${len})`];\n }\n return [\"string\"];\n }\n case \"text\":\n return [\"string\"];\n case \"longblob\":\n case \"blob\":\n case \"mediumblob\":\n case \"tinyblob\":\n return [\"binary\"];\n default:\n return [dbType as AnyColumn[\"type\"]];\n }\n }\n\n if (provider === \"mssql\") {\n switch (dbType) {\n case \"int\":\n return [\"integer\"];\n case \"decimal\":\n case \"float\":\n case \"real\":\n case \"numeric\":\n return [\"decimal\"];\n case \"bit\":\n return [\"bool\"];\n case \"datetime\":\n case \"datetime2\":\n return [\"timestamp\"];\n case \"nvarchar\":\n case \"varchar\": {\n const len = additional.length;\n if (len != null) {\n return [`varchar(${len})`];\n }\n return [\"string\", \"json\"];\n }\n case \"ntext\":\n case \"text\":\n case \"varchar(max)\":\n case \"nvarchar(max)\":\n return [\"string\", \"json\"];\n case \"binary\":\n case \"varbinary\":\n return [\"binary\"];\n default:\n return [dbType as AnyColumn[\"type\"]];\n }\n }\n\n throw new Error(`unhandled database provider: ${provider}`);\n}\n\n/**\n * Database type literals that can be returned by schemaToDBType\n */\nexport type DBTypeLiteral =\n // PostgreSQL/CockroachDB types\n | \"bigserial\"\n | \"serial\"\n | \"boolean\"\n | \"bool\"\n | \"json\"\n | \"text\"\n | \"bytea\"\n | \"timestamp\"\n | \"timestamptz\"\n | \"bigint\"\n | \"integer\"\n | \"decimal\"\n | \"date\"\n // MySQL types\n | \"longblob\"\n | \"datetime\"\n // SQLite types\n | \"blob\"\n | \"real\"\n // MSSQL types\n | \"bit\"\n | \"int\"\n | \"varbinary(max)\"\n | \"varchar(max)\"\n // varchar with length parameter\n | `varchar(${number})`;\n\nexport function schemaToDBType(\n column: AnyColumn | Pick<AnyColumn, \"type\">,\n provider: SQLProvider,\n): DBTypeLiteral {\n const { type } = column;\n\n // Handle internal ID columns with auto-increment\n if (\"role\" in column && column.role === \"internal-id\") {\n if (provider === \"postgresql\" || provider === \"cockroachdb\") {\n return \"bigserial\";\n }\n if (provider === \"mysql\") {\n return \"bigint\";\n }\n if (provider === \"sqlite\") {\n return \"integer\"; // SQLite uses INTEGER for auto-increment\n }\n if (provider === \"mssql\") {\n return \"bigint\";\n }\n }\n\n if (\"role\" in column && column.role === \"reference\") {\n if (provider === \"sqlite\") {\n return \"integer\";\n }\n // Other providers use bigint for references\n }\n\n if (provider === \"sqlite\") {\n switch (type) {\n case \"integer\":\n case \"timestamp\":\n case \"date\":\n case \"bool\":\n return \"integer\";\n case \"binary\":\n case \"bigint\":\n return \"blob\";\n case \"json\":\n case \"string\":\n return \"text\";\n case \"decimal\":\n return \"real\";\n default:\n // sqlite doesn't support varchar\n if (type.startsWith(\"varchar\")) {\n return \"text\";\n }\n }\n }\n\n if (provider === \"mssql\") {\n switch (type) {\n case \"bool\":\n return \"bit\";\n case \"timestamp\":\n return \"datetime\";\n case \"integer\":\n return \"int\";\n case \"string\":\n return \"varchar(max)\";\n case \"binary\":\n return \"varbinary(max)\";\n // only 2025 preview supports JSON natively\n case \"json\":\n return \"varchar(max)\";\n default:\n if (type.startsWith(\"varchar\")) {\n return type as `varchar(${number})`;\n }\n return type;\n }\n }\n\n if (provider === \"postgresql\" || provider === \"cockroachdb\") {\n switch (type) {\n case \"bool\":\n return \"boolean\";\n case \"json\":\n return \"json\";\n case \"string\":\n return \"text\";\n case \"binary\":\n return \"bytea\";\n default:\n if (type.startsWith(\"varchar\")) {\n return type as `varchar(${number})`;\n }\n return type;\n }\n }\n\n if (provider === \"mysql\") {\n switch (type) {\n case \"bool\":\n return \"boolean\";\n case \"string\":\n return \"text\";\n case \"binary\":\n return \"longblob\";\n default:\n if (type.startsWith(\"varchar\")) {\n return type as `varchar(${number})`;\n }\n return type;\n }\n }\n\n throw new Error(`cannot handle ${provider} ${type}`);\n}\n\nconst supportJson: SQLProvider[] = [\"postgresql\", \"cockroachdb\", \"mysql\"];\n\n/**\n * Parse from driver value\n */\nexport function deserialize(value: unknown, col: AnyColumn, provider: SQLProvider) {\n if (value === null) {\n return null;\n }\n\n if (!supportJson.includes(provider) && col.type === \"json\" && typeof value === \"string\") {\n return JSON.parse(value);\n }\n\n if (\n provider === \"sqlite\" &&\n (col.type === \"timestamp\" || col.type === \"date\") &&\n (typeof value === \"number\" || typeof value === \"string\")\n ) {\n return new Date(value);\n }\n\n if (col.type === \"bool\" && typeof value === \"number\") {\n return value === 1;\n }\n\n if (col.type === \"bigint\" && value instanceof Buffer) {\n return value.readBigInt64BE(0);\n }\n\n if (col.type === \"bigint\" && typeof value === \"string\") {\n return BigInt(value);\n }\n\n if (col.type === \"binary\" && value instanceof Buffer) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n }\n\n return value;\n}\n\n/**\n * Encode to driver value\n */\nexport function serialize(value: unknown, col: AnyColumn, provider: SQLProvider) {\n if (value === null) {\n return null;\n }\n\n // Handle FragnoReference objects (for reference columns)\n if (value instanceof FragnoReference) {\n return value.internalId;\n }\n\n // Handle FragnoId objects\n if (value instanceof FragnoId) {\n // For external ID columns, use the external ID\n if (col.role === \"external-id\") {\n return value.externalId;\n }\n // For internal ID columns, use the internal ID (must be present)\n if (col.role === \"internal-id\") {\n if (!value.internalId) {\n throw new Error(`FragnoId must have internalId for internal-id column ${col.name}`);\n }\n return value.internalId;\n }\n // For reference columns, prefer internal ID if available\n if (col.role === \"reference\") {\n return value.databaseId;\n }\n // Default to external ID for other columns\n return value.externalId;\n }\n\n if (!supportJson.includes(provider) && col.type === \"json\") {\n return JSON.stringify(value);\n }\n\n if (provider === \"sqlite\" && value instanceof Date) {\n return value.getTime();\n }\n\n if (provider === \"sqlite\" && typeof value === \"boolean\") {\n return value ? 1 : 0;\n }\n\n if (provider === \"sqlite\" && typeof value === \"bigint\") {\n const buf = Buffer.alloc(8);\n buf.writeBigInt64BE(value);\n return buf;\n }\n\n // most drivers accept Buffer\n if (col.type === \"binary\" && value instanceof Uint8Array) {\n return Buffer.from(value);\n }\n\n return value;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"serialize.js","names":["supportJson: SQLProvider[]"],"sources":["../../src/schema/serialize.ts"],"sourcesContent":["import type { SQLProvider } from \"../shared/providers\";\nimport type { AnyColumn } from \"./create\";\nimport { FragnoId, FragnoReference } from \"./create\";\n\nexport interface AdditionalColumnMetadata {\n length?: number;\n precision?: number;\n scale?: number;\n}\n\n/**\n * Get the possible column types that the raw DB type can map to.\n */\nexport function dbToSchemaType(\n dbType: string,\n provider: SQLProvider,\n additional: AdditionalColumnMetadata,\n): (AnyColumn[\"type\"] | \"varchar(n)\")[] {\n dbType = dbType.toLowerCase();\n if (provider === \"sqlite\") {\n switch (dbType) {\n case \"integer\":\n return [\"bool\", \"date\", \"timestamp\", \"bigint\", \"integer\"];\n case \"text\":\n return [\"json\", \"string\", \"bigint\", \"varchar(n)\"];\n case \"real\":\n case \"numeric\":\n return [\"decimal\"];\n case \"blob\":\n return [\"bigint\", \"binary\"];\n default:\n return [dbType as AnyColumn[\"type\"]];\n }\n }\n\n if (provider === \"postgresql\" || provider === \"cockroachdb\") {\n switch (dbType) {\n case \"decimal\":\n case \"real\":\n case \"numeric\":\n case \"double precision\":\n return [\"decimal\"];\n case \"timestamp\":\n case \"timestamptz\":\n return [\"timestamp\"];\n case \"varchar\": {\n const len = additional.length;\n if (len != null) {\n return [`varchar(${len})`];\n }\n return [\"string\"];\n }\n case \"text\":\n return [\"string\"];\n case \"boolean\":\n case \"bool\":\n return [\"bool\"];\n case \"bytea\":\n return [\"binary\"];\n default:\n return [dbType as AnyColumn[\"type\"]];\n }\n }\n\n if (provider === \"mysql\") {\n switch (dbType) {\n case \"bool\":\n case \"boolean\":\n return [\"bool\"];\n case \"integer\":\n case \"int\":\n return [\"integer\"];\n case \"decimal\":\n case \"numeric\":\n case \"float\":\n case \"double\":\n return [\"decimal\"];\n case \"datetime\":\n return [\"timestamp\"];\n case \"varchar\": {\n const len = additional.length;\n if (len != null) {\n return [`varchar(${len})`];\n }\n return [\"string\"];\n }\n case \"text\":\n return [\"string\"];\n case \"longblob\":\n case \"blob\":\n case \"mediumblob\":\n case \"tinyblob\":\n return [\"binary\"];\n default:\n return [dbType as AnyColumn[\"type\"]];\n }\n }\n\n if (provider === \"mssql\") {\n switch (dbType) {\n case \"int\":\n return [\"integer\"];\n case \"decimal\":\n case \"float\":\n case \"real\":\n case \"numeric\":\n return [\"decimal\"];\n case \"bit\":\n return [\"bool\"];\n case \"datetime\":\n case \"datetime2\":\n return [\"timestamp\"];\n case \"nvarchar\":\n case \"varchar\": {\n const len = additional.length;\n if (len != null) {\n return [`varchar(${len})`];\n }\n return [\"string\", \"json\"];\n }\n case \"ntext\":\n case \"text\":\n case \"varchar(max)\":\n case \"nvarchar(max)\":\n return [\"string\", \"json\"];\n case \"binary\":\n case \"varbinary\":\n return [\"binary\"];\n default:\n return [dbType as AnyColumn[\"type\"]];\n }\n }\n\n throw new Error(`unhandled database provider: ${provider}`);\n}\n\n/**\n * Database type literals that can be returned by schemaToDBType\n */\nexport type DBTypeLiteral =\n // PostgreSQL/CockroachDB types\n | \"bigserial\"\n | \"serial\"\n | \"boolean\"\n | \"bool\"\n | \"json\"\n | \"text\"\n | \"bytea\"\n | \"timestamp\"\n | \"timestamptz\"\n | \"bigint\"\n | \"integer\"\n | \"decimal\"\n | \"date\"\n // MySQL types\n | \"longblob\"\n | \"datetime\"\n // SQLite types\n | \"blob\"\n | \"real\"\n // MSSQL types\n | \"bit\"\n | \"int\"\n | \"varbinary(max)\"\n | \"varchar(max)\"\n // varchar with length parameter\n | `varchar(${number})`;\n\nexport function schemaToDBType(\n column: AnyColumn | Pick<AnyColumn, \"type\">,\n provider: SQLProvider,\n): DBTypeLiteral {\n const { type } = column;\n\n // Handle internal ID columns with auto-increment\n if (\"role\" in column && column.role === \"internal-id\") {\n if (provider === \"postgresql\" || provider === \"cockroachdb\") {\n return \"bigserial\";\n }\n if (provider === \"mysql\") {\n return \"bigint\";\n }\n if (provider === \"sqlite\") {\n return \"integer\"; // SQLite uses INTEGER for auto-increment\n }\n if (provider === \"mssql\") {\n return \"bigint\";\n }\n }\n\n if (\"role\" in column && column.role === \"reference\") {\n if (provider === \"sqlite\") {\n return \"integer\";\n }\n // Other providers use bigint for references\n }\n\n if (provider === \"sqlite\") {\n switch (type) {\n case \"integer\":\n case \"timestamp\":\n case \"date\":\n case \"bool\":\n return \"integer\";\n case \"binary\":\n case \"bigint\":\n return \"blob\";\n case \"json\":\n case \"string\":\n return \"text\";\n case \"decimal\":\n return \"real\";\n default:\n // sqlite doesn't support varchar\n if (type.startsWith(\"varchar\")) {\n return \"text\";\n }\n }\n }\n\n if (provider === \"mssql\") {\n switch (type) {\n case \"bool\":\n return \"bit\";\n case \"timestamp\":\n return \"datetime\";\n case \"integer\":\n return \"int\";\n case \"string\":\n return \"varchar(max)\";\n case \"binary\":\n return \"varbinary(max)\";\n // only 2025 preview supports JSON natively\n case \"json\":\n return \"varchar(max)\";\n default:\n if (type.startsWith(\"varchar\")) {\n return type as `varchar(${number})`;\n }\n return type;\n }\n }\n\n if (provider === \"postgresql\" || provider === \"cockroachdb\") {\n switch (type) {\n case \"bool\":\n return \"boolean\";\n case \"json\":\n return \"json\";\n case \"string\":\n return \"text\";\n case \"binary\":\n return \"bytea\";\n default:\n if (type.startsWith(\"varchar\")) {\n return type as `varchar(${number})`;\n }\n return type;\n }\n }\n\n if (provider === \"mysql\") {\n switch (type) {\n case \"bool\":\n return \"boolean\";\n case \"string\":\n return \"text\";\n case \"binary\":\n return \"longblob\";\n default:\n if (type.startsWith(\"varchar\")) {\n return type as `varchar(${number})`;\n }\n return type;\n }\n }\n\n throw new Error(`cannot handle ${provider} ${type}`);\n}\n\nconst supportJson: SQLProvider[] = [\"postgresql\", \"cockroachdb\", \"mysql\"];\n\n/**\n * Parse from driver value\n */\nexport function deserialize(value: unknown, col: AnyColumn, provider: SQLProvider) {\n if (value === null) {\n return null;\n }\n\n if (!supportJson.includes(provider) && col.type === \"json\" && typeof value === \"string\") {\n return JSON.parse(value);\n }\n\n if (\n provider === \"sqlite\" &&\n (col.type === \"timestamp\" || col.type === \"date\") &&\n (typeof value === \"number\" || typeof value === \"string\")\n ) {\n return new Date(value);\n }\n\n if (\n (provider === \"postgresql\" || provider === \"cockroachdb\") &&\n (col.type === \"timestamp\" || col.type === \"date\") &&\n typeof value === \"string\"\n ) {\n return new Date(value);\n }\n\n if (\n provider === \"mysql\" &&\n (col.type === \"timestamp\" || col.type === \"date\") &&\n typeof value === \"string\"\n ) {\n return new Date(value);\n }\n\n if (col.type === \"bool\" && typeof value === \"number\") {\n return value === 1;\n }\n\n if (col.type === \"bigint\" && value instanceof Buffer) {\n return value.readBigInt64BE(0);\n }\n\n if (col.type === \"bigint\" && typeof value === \"string\") {\n return BigInt(value);\n }\n\n if (col.type === \"binary\" && value instanceof Buffer) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n }\n\n return value;\n}\n\n/**\n * Encode to driver value\n */\nexport function serialize(value: unknown, col: AnyColumn, provider: SQLProvider) {\n if (value === null) {\n return null;\n }\n\n // Handle FragnoReference objects (for reference columns)\n if (value instanceof FragnoReference) {\n return value.internalId;\n }\n\n // Handle FragnoId objects\n if (value instanceof FragnoId) {\n // For external ID columns, use the external ID\n if (col.role === \"external-id\") {\n return value.externalId;\n }\n // For internal ID columns, use the internal ID (must be present)\n if (col.role === \"internal-id\") {\n if (!value.internalId) {\n throw new Error(`FragnoId must have internalId for internal-id column ${col.name}`);\n }\n return value.internalId;\n }\n // For reference columns, prefer internal ID if available\n if (col.role === \"reference\") {\n return value.databaseId;\n }\n // Default to external ID for other columns\n return value.externalId;\n }\n\n if (!supportJson.includes(provider) && col.type === \"json\") {\n return JSON.stringify(value);\n }\n\n if (provider === \"sqlite\" && value instanceof Date) {\n return value.getTime();\n }\n\n if (provider === \"sqlite\" && typeof value === \"boolean\") {\n return value ? 1 : 0;\n }\n\n if (provider === \"sqlite\" && typeof value === \"bigint\") {\n const buf = Buffer.alloc(8);\n buf.writeBigInt64BE(value);\n return buf;\n }\n\n // most drivers accept Buffer\n if (col.type === \"binary\" && value instanceof Uint8Array) {\n return Buffer.from(value);\n }\n\n return value;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragno-dev/db",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -74,9 +74,9 @@
74
74
  "kysely-pglite": "^0.6.1",
75
75
  "vitest": "^3.2.4",
76
76
  "zod": "^4.1.12",
77
- "@fragno-dev/core": "0.1.5",
77
+ "@fragno-private/vitest-config": "0.0.0",
78
78
  "@fragno-private/typescript-config": "0.0.1",
79
- "@fragno-private/vitest-config": "0.0.0"
79
+ "@fragno-dev/core": "0.1.6"
80
80
  },
81
81
  "repository": {
82
82
  "type": "git",