@fragno-dev/db 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +48 -41
- package/CHANGELOG.md +6 -0
- package/dist/adapters/adapters.d.ts +13 -1
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +2 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +6 -1
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +6 -4
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts +0 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +49 -36
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/shared.d.ts +14 -1
- package/dist/adapters/drizzle/shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts +2 -0
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +7 -2
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +5 -3
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.d.ts +11 -0
- package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -0
- package/dist/adapters/kysely/kysely-uow-compiler.js +38 -9
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/bind-services.d.ts +7 -0
- package/dist/bind-services.d.ts.map +1 -0
- package/dist/bind-services.js +14 -0
- package/dist/bind-services.js.map +1 -0
- package/dist/fragment.d.ts +131 -12
- package/dist/fragment.d.ts.map +1 -1
- package/dist/fragment.js +107 -8
- package/dist/fragment.js.map +1 -1
- package/dist/mod.d.ts +4 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +3 -2
- package/dist/mod.js.map +1 -1
- package/dist/query/query.d.ts +2 -2
- package/dist/query/query.d.ts.map +1 -1
- package/dist/query/unit-of-work.d.ts +100 -15
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +214 -7
- package/dist/query/unit-of-work.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/adapters.ts +14 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +6 -1
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +133 -5
- package/src/adapters/drizzle/drizzle-adapter.ts +16 -1
- package/src/adapters/drizzle/drizzle-query.ts +26 -15
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +57 -57
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +79 -39
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +2 -5
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +2 -2
- package/src/adapters/kysely/kysely-adapter.ts +16 -1
- package/src/adapters/kysely/kysely-query.ts +26 -15
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +43 -43
- package/src/adapters/kysely/kysely-uow-compiler.ts +50 -14
- package/src/adapters/kysely/kysely-uow-joins.test.ts +30 -30
- package/src/bind-services.test.ts +214 -0
- package/src/bind-services.ts +37 -0
- package/src/db-fragment.test.ts +800 -0
- package/src/fragment.ts +557 -28
- package/src/mod.ts +19 -0
- package/src/query/query.ts +2 -2
- package/src/query/unit-of-work-multi-schema.test.ts +64 -0
- package/src/query/unit-of-work-types.test.ts +13 -0
- package/src/query/unit-of-work.test.ts +5 -9
- package/src/query/unit-of-work.ts +511 -62
- package/src/uow-context-integration.test.ts +102 -0
- package/src/uow-context.test.ts +182 -0
- package/src/fragment.test.ts +0 -341
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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","#schemaNamespaceMap","#retrievalPhasePromise","#retrievalPhaseResolve","#mutationPhasePromise","#mutationPhaseResolve","#state","#retrievalOps","retrievalBatch: unknown[]","#retrievalResults","externalId: string","#mutationOps","mutationBatch: CompiledMutation<unknown>[]","#createdInternalIds","createdIds: FragnoId[]","retrievalBatch: TOutput[]","mutationBatch: CompiledMutation<TOutput>[]","#namespace","#parent","#operationIndices"],"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 {\n SelectClause,\n TableToInsertValues,\n TableToUpdateValues,\n SelectResult,\n ExtractSelect,\n ExtractJoinOut,\n} from \"./query\";\nimport { buildCondition } from \"./condition-builder\";\nimport type { CompiledJoin } from \"./orm/orm\";\nimport type { CursorResult } from \"./cursor\";\nimport { Cursor } from \"./cursor\";\nimport type { Prettify } from \"../util/types\";\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 schema: TSchema;\n namespace?: string;\n table: TTable;\n indexName: string;\n options: FindOptions<TTable, SelectClause<TTable>>;\n withCursor?: boolean;\n }\n | {\n type: \"count\";\n schema: TSchema;\n namespace?: string;\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 schema: TSchema;\n namespace?: string;\n table: TTable[\"name\"];\n id: FragnoId | string;\n checkVersion: boolean;\n set: TableToUpdateValues<TTable>;\n }\n | {\n type: \"create\";\n schema: TSchema;\n namespace?: string;\n table: TTable[\"name\"];\n values: TableToInsertValues<TTable>;\n generatedExternalId: string;\n }\n | {\n type: \"delete\";\n schema: TSchema;\n namespace?: string;\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<TOutput> {\n /**\n * Compile a retrieval operation to the adapter's query format\n */\n compileRetrievalOperation(op: RetrievalOperation<AnySchema>): TOutput | null;\n\n /**\n * Compile a mutation operation to the adapter's query format\n */\n compileMutationOperation(op: MutationOperation<AnySchema>): 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<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<AnySchema>[]): 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\n/**\n * Base interface for Unit of Work with schema-agnostic methods only.\n * This allows UOW instances to be passed between services that use different schemas.\n */\nexport interface IUnitOfWorkBase {\n // Getters (schema-agnostic)\n readonly state: UOWState;\n readonly name: string | undefined;\n readonly retrievalPhase: Promise<unknown[]>;\n readonly mutationPhase: Promise<void>;\n\n // Execution (schema-agnostic)\n executeRetrieve(): Promise<unknown[]>;\n executeMutations(): Promise<{ success: boolean }>;\n\n // Inspection (schema-agnostic)\n getRetrievalOperations(): ReadonlyArray<RetrievalOperation<AnySchema>>;\n getMutationOperations(): ReadonlyArray<MutationOperation<AnySchema>>;\n getCreatedIds(): FragnoId[];\n\n // Schema-specific view (for cross-schema operations)\n forSchema<TOtherSchema extends AnySchema>(\n schema: TOtherSchema,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): UnitOfWorkSchemaView<TOtherSchema, [], any>;\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<unknown>,\n executor: UOWExecutor<unknown, TRawInput>,\n decoder: UOWDecoder<TRawInput>,\n name?: string,\n): UnitOfWork<TSchema, TRetrievalResults, TRawInput> {\n return new UnitOfWork(schema, compiler, executor, decoder, name);\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> implements IUnitOfWorkBase\n{\n #schema: TSchema;\n\n #name?: string;\n #config?: UnitOfWorkConfig;\n\n #state: UOWState = \"building-retrieval\";\n\n // Operations can now come from any schema\n #retrievalOps: RetrievalOperation<AnySchema>[] = [];\n #mutationOps: MutationOperation<AnySchema>[] = [];\n\n #compiler: UOWCompiler<unknown>;\n #executor: UOWExecutor<unknown, TRawInput>;\n #decoder: UOWDecoder<TRawInput>;\n #schemaNamespaceMap?: WeakMap<AnySchema, string>;\n\n #retrievalResults?: TRetrievalResults;\n #createdInternalIds: (bigint | null)[] = [];\n\n // Phase coordination promises\n #retrievalPhaseResolve?: (value: TRetrievalResults) => void;\n #mutationPhaseResolve?: () => void;\n #retrievalPhasePromise: Promise<TRetrievalResults>;\n #mutationPhasePromise: Promise<void>;\n\n constructor(\n schema: TSchema,\n compiler: UOWCompiler<unknown>,\n executor: UOWExecutor<unknown, TRawInput>,\n decoder: UOWDecoder<TRawInput>,\n name?: string,\n config?: UnitOfWorkConfig,\n schemaNamespaceMap?: WeakMap<AnySchema, string>,\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 this.#schemaNamespaceMap = schemaNamespaceMap;\n\n // Initialize phase coordination promises\n this.#retrievalPhasePromise = new Promise<TRetrievalResults>((resolve) => {\n this.#retrievalPhaseResolve = resolve;\n });\n this.#mutationPhasePromise = new Promise<void>((resolve) => {\n this.#mutationPhaseResolve = resolve;\n });\n }\n\n get schema(): TSchema {\n return this.#schema;\n }\n\n get $results(): Prettify<TRetrievalResults> {\n throw new Error(\"type only\");\n }\n\n /**\n * Get a schema-specific view of this UOW for type-safe operations\n * Returns a wrapper that uses a different schema for operations.\n * The namespace is automatically resolved from the schema-namespace map.\n */\n forSchema<TOtherSchema extends AnySchema>(\n schema: TOtherSchema,\n ): UnitOfWorkSchemaView<TOtherSchema, [], TRawInput> {\n // Look up namespace from map\n const resolvedNamespace = this.#schemaNamespaceMap?.get(schema);\n\n // Safe cast: UnitOfWorkSchemaView starts with empty result types\n // As operations are added, the types will accumulate correctly\n return new UnitOfWorkSchemaView(\n schema,\n resolvedNamespace,\n this as unknown as UnitOfWork<AnySchema, unknown[], TRawInput>,\n );\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 * Promise that resolves when the retrieval phase is executed\n * Service methods can await this to coordinate multi-phase logic\n */\n get retrievalPhase(): Promise<TRetrievalResults> {\n return this.#retrievalPhasePromise;\n }\n\n /**\n * Promise that resolves when the mutation phase is executed\n * Service methods can await this to coordinate multi-phase logic\n */\n get mutationPhase(): Promise<void> {\n return this.#mutationPhasePromise;\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.#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 if (this.#retrievalOps.length === 0) {\n this.#state = \"building-mutation\";\n const emptyResults = [] as unknown as TRetrievalResults;\n this.#retrievalPhaseResolve?.(emptyResults);\n return emptyResults;\n }\n\n // Compile retrieval operations using single compiler\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 retrievalBatch.push(compiled);\n }\n }\n\n if (this.#config?.dryRun) {\n this.#state = \"executed\";\n return [] as unknown as TRetrievalResults;\n }\n\n // Execute all operations together (ideally in same transaction)\n const rawResults = await this.#executor.executeRetrievalPhase(retrievalBatch);\n\n // Decode results using single decoder\n const results = this.#decoder(rawResults, this.#retrievalOps);\n\n // Store results and transition to mutation phase\n this.#retrievalResults = results as TRetrievalResults;\n this.#state = \"building-mutation\";\n\n // Resolve the retrieval phase promise to unblock waiting service methods\n this.#retrievalPhaseResolve?.(this.#retrievalResults);\n\n return this.#retrievalResults;\n }\n\n /**\n * Add a find operation using a builder callback (retrieval phase only)\n */\n find<TTableName extends keyof TSchema[\"tables\"] & string, const TBuilderResult>(\n tableName: TTableName,\n builderFn: (\n builder: Omit<FindBuilder<TSchema[\"tables\"][TTableName]>, \"build\">,\n ) => TBuilderResult,\n ): UnitOfWork<\n TSchema,\n [\n ...TRetrievalResults,\n SelectResult<\n TSchema[\"tables\"][TTableName],\n ExtractJoinOut<TBuilderResult>,\n Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema[\"tables\"][TTableName]>>\n >[],\n ],\n TRawInput\n >;\n find<TTableName extends keyof TSchema[\"tables\"] & string>(\n tableName: TTableName,\n ): UnitOfWork<\n TSchema,\n [...TRetrievalResults, SelectResult<TSchema[\"tables\"][TTableName], {}, true>[]],\n TRawInput\n >;\n find<TTableName extends keyof TSchema[\"tables\"] & string, const TBuilderResult>(\n tableName: TTableName,\n builderFn?: (\n builder: Omit<FindBuilder<TSchema[\"tables\"][TTableName]>, \"build\">,\n ) => TBuilderResult,\n ): UnitOfWork<\n TSchema,\n [\n ...TRetrievalResults,\n SelectResult<\n TSchema[\"tables\"][TTableName],\n ExtractJoinOut<TBuilderResult>,\n Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema[\"tables\"][TTableName]>>\n >[],\n ],\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 schema: this.#schema,\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 // Safe: return type is correctly specified in the method signature\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return this as any;\n }\n\n /**\n * Add a find operation with cursor metadata (retrieval phase only)\n */\n findWithCursor<TTableName extends keyof TSchema[\"tables\"] & string, const TBuilderResult>(\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 ) => TBuilderResult,\n ): UnitOfWork<\n TSchema,\n [\n ...TRetrievalResults,\n CursorResult<\n SelectResult<\n TSchema[\"tables\"][TTableName],\n ExtractJoinOut<TBuilderResult>,\n Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema[\"tables\"][TTableName]>>\n >\n >,\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 schema: this.#schema,\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 // Safe: return type is correctly specified in the method signature\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return this as any;\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 schema: this.#schema,\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 schema: this.#schema,\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 schema: this.#schema,\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 using single compiler\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 // Resolve the mutation phase promise to unblock waiting service methods\n this.#mutationPhaseResolve?.();\n\n return {\n success: result.success,\n };\n }\n\n /**\n * Get the retrieval operations (for inspection/debugging)\n */\n getRetrievalOperations(): ReadonlyArray<RetrievalOperation<AnySchema>> {\n return this.#retrievalOps;\n }\n\n /**\n * Get the mutation operations (for inspection/debugging)\n */\n getMutationOperations(): ReadonlyArray<MutationOperation<AnySchema>> {\n return this.#mutationOps;\n }\n\n /**\n * @internal\n * Add a retrieval operation (used by SchemaView)\n */\n addRetrievalOperation(op: RetrievalOperation<AnySchema>): number {\n this.#retrievalOps.push(op);\n return this.#retrievalOps.length - 1;\n }\n\n /**\n * @internal\n * Add a mutation operation (used by SchemaView)\n */\n addMutationOperation(op: MutationOperation<AnySchema>): void {\n this.#mutationOps.push(op);\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<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\n/**\n * A lightweight wrapper around a parent UOW that provides type-safe operations for a different schema.\n * All operations are stored in the parent UOW, but this wrapper ensures the correct schema is used.\n */\nexport class UnitOfWorkSchemaView<\n const TSchema extends AnySchema,\n const TRetrievalResults extends unknown[] = [],\n const TRawInput = unknown,\n> implements IUnitOfWorkBase\n{\n #schema: TSchema;\n #namespace?: string;\n #parent: UnitOfWork<AnySchema, unknown[], TRawInput>;\n #operationIndices: number[] = [];\n\n constructor(\n schema: TSchema,\n namespace: string | undefined,\n parent: UnitOfWork<AnySchema, unknown[], TRawInput>,\n ) {\n this.#schema = schema;\n this.#namespace = namespace;\n this.#parent = parent;\n }\n\n get $results(): Prettify<TRetrievalResults> {\n throw new Error(\"type only\");\n }\n\n get schema(): TSchema {\n return this.#schema;\n }\n\n get name(): string | undefined {\n return this.#parent.name;\n }\n\n get state() {\n return this.#parent.state;\n }\n\n get retrievalPhase(): Promise<TRetrievalResults> {\n // Filter parent's results to only include operations from this view\n return this.#parent.retrievalPhase.then((allResults) => {\n const filteredResults = this.#operationIndices.map((index) => allResults[index]);\n return filteredResults as TRetrievalResults;\n });\n }\n\n get mutationPhase(): Promise<void> {\n return this.#parent.mutationPhase;\n }\n\n getRetrievalOperations() {\n return this.#parent.getRetrievalOperations();\n }\n\n getMutationOperations() {\n return this.#parent.getMutationOperations();\n }\n\n getCreatedIds() {\n return this.#parent.getCreatedIds();\n }\n\n async executeRetrieve(): Promise<unknown[]> {\n return this.#parent.executeRetrieve();\n }\n\n async executeMutations(): Promise<{ success: boolean }> {\n return this.#parent.executeMutations();\n }\n\n find<TTableName extends keyof TSchema[\"tables\"] & string, const TBuilderResult>(\n tableName: TTableName,\n builderFn: (\n builder: Omit<FindBuilder<TSchema[\"tables\"][TTableName]>, \"build\">,\n ) => TBuilderResult,\n ): UnitOfWorkSchemaView<\n TSchema,\n [\n ...TRetrievalResults,\n SelectResult<\n TSchema[\"tables\"][TTableName],\n ExtractJoinOut<TBuilderResult>,\n Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema[\"tables\"][TTableName]>>\n >[],\n ],\n TRawInput\n >;\n find<TTableName extends keyof TSchema[\"tables\"] & string>(\n tableName: TTableName,\n ): UnitOfWorkSchemaView<\n TSchema,\n [...TRetrievalResults, SelectResult<TSchema[\"tables\"][TTableName], {}, true>[]],\n TRawInput\n >;\n find<TTableName extends keyof TSchema[\"tables\"] & string, const TBuilderResult>(\n tableName: TTableName,\n builderFn?: (\n builder: Omit<FindBuilder<TSchema[\"tables\"][TTableName]>, \"build\">,\n ) => TBuilderResult,\n ): UnitOfWorkSchemaView<\n TSchema,\n [\n ...TRetrievalResults,\n SelectResult<\n TSchema[\"tables\"][TTableName],\n ExtractJoinOut<TBuilderResult>,\n Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema[\"tables\"][TTableName]>>\n >[],\n ],\n TRawInput\n > {\n const table = this.#schema.tables[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n const builder = new FindBuilder(tableName, table as TSchema[\"tables\"][TTableName]);\n if (builderFn) {\n builderFn(builder);\n } else {\n builder.whereIndex(\"primary\");\n }\n const { indexName, options, type } = builder.build();\n\n const operationIndex = this.#parent.addRetrievalOperation({\n type,\n schema: this.#schema,\n namespace: this.#namespace,\n table: table as TSchema[\"tables\"][TTableName],\n indexName,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: options as any,\n });\n\n // Track which operation index belongs to this view\n this.#operationIndices.push(operationIndex);\n\n // Safe: return type is correctly specified in the method signature\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return this as any;\n }\n\n findWithCursor<TTableName extends keyof TSchema[\"tables\"] & string, const TBuilderResult>(\n tableName: TTableName,\n builderFn: (\n builder: Omit<FindBuilder<TSchema[\"tables\"][TTableName]>, \"build\">,\n ) => TBuilderResult,\n ): UnitOfWorkSchemaView<\n TSchema,\n [\n ...TRetrievalResults,\n CursorResult<\n SelectResult<\n TSchema[\"tables\"][TTableName],\n ExtractJoinOut<TBuilderResult>,\n Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema[\"tables\"][TTableName]>>\n >\n >,\n ],\n TRawInput\n > {\n const table = this.#schema.tables[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n const builder = new FindBuilder(tableName, table as TSchema[\"tables\"][TTableName]);\n builderFn(builder);\n const { indexName, options, type } = builder.build();\n\n const operationIndex = this.#parent.addRetrievalOperation({\n type,\n schema: this.#schema,\n namespace: this.#namespace,\n table: table as TSchema[\"tables\"][TTableName],\n indexName,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n options: options as any,\n withCursor: true,\n });\n\n // Track which operation index belongs to this view\n this.#operationIndices.push(operationIndex);\n\n // Safe: return type is correctly specified in the method signature\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return this as any;\n }\n\n create<TableName extends keyof TSchema[\"tables\"] & string>(\n tableName: TableName,\n values: TableToInsertValues<TSchema[\"tables\"][TableName]>,\n ): FragnoId {\n const tableSchema = this.#schema.tables[tableName];\n if (!tableSchema) {\n throw new Error(`Table ${tableName} 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.#parent.addMutationOperation({\n type: \"create\",\n schema: this.#schema,\n namespace: this.#namespace,\n table: tableName,\n values: updatedValues,\n generatedExternalId: externalId,\n });\n\n return FragnoId.fromExternal(externalId, 0);\n }\n\n update<TableName extends keyof TSchema[\"tables\"] & string>(\n tableName: TableName,\n id: FragnoId | string,\n builderFn: (\n builder: Omit<UpdateBuilder<TSchema[\"tables\"][TableName]>, \"build\">,\n ) => Omit<UpdateBuilder<TSchema[\"tables\"][TableName]>, \"build\"> | void,\n ): void {\n const builder = new UpdateBuilder<TSchema[\"tables\"][TableName]>(tableName, id);\n builderFn(builder);\n const { id: opId, checkVersion, set } = builder.build();\n\n this.#parent.addMutationOperation({\n type: \"update\",\n schema: this.#schema,\n namespace: this.#namespace,\n table: tableName,\n id: opId,\n checkVersion,\n set,\n });\n }\n\n delete<TableName extends keyof TSchema[\"tables\"] & string>(\n tableName: TableName,\n id: FragnoId | string,\n builderFn?: (builder: Omit<DeleteBuilder, \"build\">) => Omit<DeleteBuilder, \"build\"> | void,\n ): void {\n const builder = new DeleteBuilder(tableName, id);\n builderFn?.(builder);\n const { id: opId, checkVersion } = builder.build();\n\n this.#parent.addMutationOperation({\n type: \"delete\",\n schema: this.#schema,\n namespace: this.#namespace,\n table: tableName,\n id: opId,\n checkVersion,\n });\n }\n\n forSchema<TOtherSchema extends AnySchema>(\n schema: TOtherSchema,\n ): UnitOfWorkSchemaView<TOtherSchema, [], TRawInput> {\n // Delegate to the parent's forSchema to create a new view\n return this.#parent.forSchema(schema);\n }\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.
|
|
3
|
+
"version": "0.1.14",
|
|
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-
|
|
77
|
+
"@fragno-dev/core": "0.1.8",
|
|
78
78
|
"@fragno-private/typescript-config": "0.0.1",
|
|
79
|
-
"@fragno-
|
|
79
|
+
"@fragno-private/vitest-config": "0.0.0"
|
|
80
80
|
},
|
|
81
81
|
"repository": {
|
|
82
82
|
"type": "git",
|
package/src/adapters/adapters.ts
CHANGED
|
@@ -6,6 +6,14 @@ import type { AnySchema } from "../schema/create";
|
|
|
6
6
|
export const fragnoDatabaseAdapterNameFakeSymbol = "$fragno-database-adapter-name" as const;
|
|
7
7
|
export const fragnoDatabaseAdapterVersionFakeSymbol = "$fragno-database-adapter-version" as const;
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Maps logical table names (used by fragment authors) to physical table names (with namespace suffix)
|
|
11
|
+
*/
|
|
12
|
+
export interface TableNameMapper {
|
|
13
|
+
toPhysical(logicalName: string): string;
|
|
14
|
+
toLogical(physicalName: string): string;
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
export interface DatabaseAdapter<TUOWConfig = void> {
|
|
10
18
|
[fragnoDatabaseAdapterNameFakeSymbol]: string;
|
|
11
19
|
[fragnoDatabaseAdapterVersionFakeSymbol]: number;
|
|
@@ -31,6 +39,12 @@ export interface DatabaseAdapter<TUOWConfig = void> {
|
|
|
31
39
|
options?: { path?: string },
|
|
32
40
|
) => SchemaGenerator;
|
|
33
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Creates a table name mapper for the given namespace.
|
|
44
|
+
* Used to convert between logical table names and physical table names.
|
|
45
|
+
*/
|
|
46
|
+
createTableNameMapper: (namespace: string) => TableNameMapper;
|
|
47
|
+
|
|
34
48
|
isConnectionHealthy: () => Promise<boolean>;
|
|
35
49
|
|
|
36
50
|
close: () => Promise<void>;
|
|
@@ -126,7 +126,12 @@ describe("DrizzleAdapter PGLite", () => {
|
|
|
126
126
|
age: 25,
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
expectTypeOf
|
|
129
|
+
expectTypeOf<keyof typeof testSchema.tables>().toEqualTypeOf<
|
|
130
|
+
Parameters<typeof createUow.find>[0]
|
|
131
|
+
>();
|
|
132
|
+
expectTypeOf<keyof typeof testSchema.tables>().toEqualTypeOf<
|
|
133
|
+
"users" | "emails" | "posts" | "comments"
|
|
134
|
+
>();
|
|
130
135
|
|
|
131
136
|
const { success: createSuccess } = await createUow.executeMutations();
|
|
132
137
|
expect(createSuccess).toBe(true);
|
|
@@ -2,7 +2,7 @@ import { drizzle } from "drizzle-orm/libsql";
|
|
|
2
2
|
import { createClient } from "@libsql/client";
|
|
3
3
|
import { DrizzleAdapter } from "./drizzle-adapter";
|
|
4
4
|
import { beforeAll, describe, expect, expectTypeOf, it } from "vitest";
|
|
5
|
-
import { column, idColumn, referenceColumn, schema } from "../../schema/create";
|
|
5
|
+
import { column, idColumn, referenceColumn, schema, type FragnoId } from "../../schema/create";
|
|
6
6
|
import type { DBType } from "./shared";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import { writeAndLoadSchema } from "./test-utils";
|
|
@@ -71,6 +71,30 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
71
71
|
});
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
// Second schema for multi-schema testing
|
|
75
|
+
const schema2 = schema((s) => {
|
|
76
|
+
return s
|
|
77
|
+
.addTable("products", (t) => {
|
|
78
|
+
return t
|
|
79
|
+
.addColumn("id", idColumn())
|
|
80
|
+
.addColumn("name", column("string"))
|
|
81
|
+
.addColumn("price", column("integer"))
|
|
82
|
+
.createIndex("name_idx", ["name"]);
|
|
83
|
+
})
|
|
84
|
+
.addTable("orders", (t) => {
|
|
85
|
+
return t
|
|
86
|
+
.addColumn("id", idColumn())
|
|
87
|
+
.addColumn("product_id", referenceColumn())
|
|
88
|
+
.addColumn("quantity", column("integer"))
|
|
89
|
+
.createIndex("product_orders_idx", ["product_id"]);
|
|
90
|
+
})
|
|
91
|
+
.addReference("product", {
|
|
92
|
+
type: "one",
|
|
93
|
+
from: { table: "orders", column: "product_id" },
|
|
94
|
+
to: { table: "products", column: "id" },
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
74
98
|
let adapter: DrizzleAdapter;
|
|
75
99
|
let db: DBType;
|
|
76
100
|
// let sqliteDb: Database.Database;
|
|
@@ -84,17 +108,28 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
84
108
|
"namespace",
|
|
85
109
|
);
|
|
86
110
|
|
|
111
|
+
// Write second schema to file and dynamically import it
|
|
112
|
+
const { schemaModule: schemaModule2, cleanup: cleanup2 } = await writeAndLoadSchema(
|
|
113
|
+
"drizzle-adapter-sqlite-schema2",
|
|
114
|
+
schema2,
|
|
115
|
+
"sqlite",
|
|
116
|
+
"namespace2",
|
|
117
|
+
);
|
|
118
|
+
|
|
87
119
|
const client = createClient({
|
|
88
120
|
url: "file::memory:?cache=shared",
|
|
89
121
|
});
|
|
90
122
|
|
|
123
|
+
// Merge both schema modules for the db
|
|
124
|
+
const mergedSchema = { ...schemaModule, ...schemaModule2 };
|
|
125
|
+
|
|
91
126
|
db = drizzle(client, {
|
|
92
|
-
schema:
|
|
127
|
+
schema: mergedSchema,
|
|
93
128
|
}) as unknown as DBType;
|
|
94
129
|
|
|
95
|
-
// Generate and run migrations
|
|
130
|
+
// Generate and run migrations for both schemas
|
|
96
131
|
const emptyJson = await generateSQLiteDrizzleJson({});
|
|
97
|
-
const targetJson = await generateSQLiteDrizzleJson(
|
|
132
|
+
const targetJson = await generateSQLiteDrizzleJson(mergedSchema);
|
|
98
133
|
|
|
99
134
|
const migrationStatements = await generateSQLiteMigration(emptyJson, targetJson);
|
|
100
135
|
|
|
@@ -110,6 +145,7 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
110
145
|
return async () => {
|
|
111
146
|
client.close();
|
|
112
147
|
await cleanup();
|
|
148
|
+
await cleanup2();
|
|
113
149
|
};
|
|
114
150
|
}, 12000);
|
|
115
151
|
|
|
@@ -128,7 +164,12 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
128
164
|
age: 30,
|
|
129
165
|
});
|
|
130
166
|
|
|
131
|
-
expectTypeOf
|
|
167
|
+
expectTypeOf<keyof typeof testSchema.tables>().toEqualTypeOf<
|
|
168
|
+
Parameters<typeof createUow.find>[0]
|
|
169
|
+
>();
|
|
170
|
+
expectTypeOf<keyof typeof testSchema.tables>().toEqualTypeOf<
|
|
171
|
+
"users" | "emails" | "posts" | "comments"
|
|
172
|
+
>();
|
|
132
173
|
|
|
133
174
|
const { success: createSuccess } = await createUow.executeMutations();
|
|
134
175
|
expect(createSuccess).toBe(true);
|
|
@@ -636,4 +677,91 @@ describe("DrizzleAdapter SQLite", () => {
|
|
|
636
677
|
expect(localDate).toBeInstanceOf(Date);
|
|
637
678
|
expect(typeof localDate.getTimezoneOffset()).toBe("number");
|
|
638
679
|
});
|
|
680
|
+
|
|
681
|
+
it("should support forSchema for multi-schema queries", async () => {
|
|
682
|
+
const queryEngine1 = adapter.createQueryEngine(testSchema, "namespace");
|
|
683
|
+
const queryEngine2 = adapter.createQueryEngine(schema2, "namespace2");
|
|
684
|
+
|
|
685
|
+
// Create test data in schema1 (users)
|
|
686
|
+
const createUsersUow = queryEngine1.createUnitOfWork("create-users-for-multi-schema");
|
|
687
|
+
createUsersUow.create("users", { name: "Multi Schema User 1", age: 25 });
|
|
688
|
+
createUsersUow.create("users", { name: "Multi Schema User 2", age: 30 });
|
|
689
|
+
const { success: success1 } = await createUsersUow.executeMutations();
|
|
690
|
+
expect(success1).toBe(true);
|
|
691
|
+
|
|
692
|
+
// Create test data in schema2 (products)
|
|
693
|
+
const createProductsUow = queryEngine2.createUnitOfWork("create-products-for-multi-schema");
|
|
694
|
+
createProductsUow.create("products", { name: "Product A", price: 100 });
|
|
695
|
+
createProductsUow.create("products", { name: "Product B", price: 200 });
|
|
696
|
+
const { success: success2 } = await createProductsUow.executeMutations();
|
|
697
|
+
expect(success2).toBe(true);
|
|
698
|
+
|
|
699
|
+
// Now use forSchema to query from both schemas
|
|
700
|
+
const uow = queryEngine1.createUnitOfWork("multi-schema-query");
|
|
701
|
+
|
|
702
|
+
const view1 = uow
|
|
703
|
+
.forSchema(testSchema)
|
|
704
|
+
.find("users", (b) =>
|
|
705
|
+
b
|
|
706
|
+
.whereIndex("name_idx", (eb) => eb("name", "starts with", "Multi Schema User"))
|
|
707
|
+
.select(["id", "name"]),
|
|
708
|
+
)
|
|
709
|
+
.find("users", (b) =>
|
|
710
|
+
b
|
|
711
|
+
.whereIndex("name_idx", (eb) => eb("name", "starts with", "Multi Schema User"))
|
|
712
|
+
.select(["name", "age"]),
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
const view2 = uow
|
|
716
|
+
.forSchema(schema2)
|
|
717
|
+
.find("products", (b) => b.whereIndex("primary").select(["name", "price"]));
|
|
718
|
+
|
|
719
|
+
// Execute the retrieval phase once
|
|
720
|
+
await uow.executeRetrieve();
|
|
721
|
+
|
|
722
|
+
// Get results from view1
|
|
723
|
+
const [users1, users2] = await view1.retrievalPhase;
|
|
724
|
+
const [user1] = users1;
|
|
725
|
+
expectTypeOf(user1).toMatchObjectType<{ id: FragnoId; name: string }>();
|
|
726
|
+
|
|
727
|
+
const [user2] = users2;
|
|
728
|
+
expectTypeOf(user2).toMatchObjectType<{ name: string; age: number | null }>();
|
|
729
|
+
|
|
730
|
+
// Get results from view2
|
|
731
|
+
const [products] = await view2.retrievalPhase;
|
|
732
|
+
const [product1] = products;
|
|
733
|
+
expectTypeOf(product1).toMatchObjectType<{ name: string; price: number }>();
|
|
734
|
+
|
|
735
|
+
// Verify users from schema1
|
|
736
|
+
expect(users1).toHaveLength(2);
|
|
737
|
+
expect(users1[0]).toMatchObject({
|
|
738
|
+
id: expect.any(Object),
|
|
739
|
+
name: "Multi Schema User 1",
|
|
740
|
+
});
|
|
741
|
+
expect(users1[1]).toMatchObject({
|
|
742
|
+
id: expect.any(Object),
|
|
743
|
+
name: "Multi Schema User 2",
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
expect(users2).toHaveLength(2);
|
|
747
|
+
expect(users2[0]).toMatchObject({
|
|
748
|
+
name: "Multi Schema User 1",
|
|
749
|
+
age: 25,
|
|
750
|
+
});
|
|
751
|
+
expect(users2[1]).toMatchObject({
|
|
752
|
+
name: "Multi Schema User 2",
|
|
753
|
+
age: 30,
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
// Verify products from schema2
|
|
757
|
+
expect(products).toHaveLength(2);
|
|
758
|
+
expect(products[0]).toMatchObject({
|
|
759
|
+
name: "Product A",
|
|
760
|
+
price: 100,
|
|
761
|
+
});
|
|
762
|
+
expect(products[1]).toMatchObject({
|
|
763
|
+
name: "Product B",
|
|
764
|
+
price: 200,
|
|
765
|
+
});
|
|
766
|
+
});
|
|
639
767
|
});
|
|
@@ -22,6 +22,7 @@ export interface DrizzleConfig {
|
|
|
22
22
|
export class DrizzleAdapter implements DatabaseAdapter<DrizzleUOWConfig> {
|
|
23
23
|
#connectionPool: ConnectionPool<DBType>;
|
|
24
24
|
#provider: "sqlite" | "mysql" | "postgresql";
|
|
25
|
+
#schemaNamespaceMap = new WeakMap<AnySchema, string>();
|
|
25
26
|
|
|
26
27
|
constructor(config: DrizzleConfig) {
|
|
27
28
|
this.#connectionPool = createDrizzleConnectionPool(
|
|
@@ -42,6 +43,10 @@ export class DrizzleAdapter implements DatabaseAdapter<DrizzleUOWConfig> {
|
|
|
42
43
|
await this.#connectionPool.close();
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
createTableNameMapper(namespace: string) {
|
|
47
|
+
return createTableNameMapper(namespace);
|
|
48
|
+
}
|
|
49
|
+
|
|
45
50
|
get provider(): "sqlite" | "mysql" | "postgresql" {
|
|
46
51
|
return this.#provider;
|
|
47
52
|
}
|
|
@@ -80,9 +85,19 @@ export class DrizzleAdapter implements DatabaseAdapter<DrizzleUOWConfig> {
|
|
|
80
85
|
schema: TSchema,
|
|
81
86
|
namespace: string,
|
|
82
87
|
): AbstractQuery<TSchema, DrizzleUOWConfig> {
|
|
88
|
+
// Register schema-namespace mapping
|
|
89
|
+
this.#schemaNamespaceMap.set(schema, namespace);
|
|
90
|
+
|
|
83
91
|
// Only create mapper if namespace is non-empty
|
|
84
92
|
const mapper = namespace ? createTableNameMapper(namespace) : undefined;
|
|
85
|
-
return fromDrizzle(
|
|
93
|
+
return fromDrizzle(
|
|
94
|
+
schema,
|
|
95
|
+
this.#connectionPool,
|
|
96
|
+
this.#provider,
|
|
97
|
+
mapper,
|
|
98
|
+
undefined,
|
|
99
|
+
this.#schemaNamespaceMap,
|
|
100
|
+
);
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
createSchemaGenerator(
|