@fragno-dev/db 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.turbo/turbo-build.log +27 -27
  2. package/CHANGELOG.md +6 -0
  3. package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
  4. package/dist/adapters/drizzle/drizzle-adapter.js +1 -1
  5. package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
  6. package/dist/adapters/drizzle/drizzle-query.js +4 -0
  7. package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
  8. package/dist/adapters/drizzle/drizzle-uow-compiler.js +2 -1
  9. package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
  10. package/dist/adapters/drizzle/drizzle-uow-decoder.js +25 -1
  11. package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
  12. package/dist/adapters/drizzle/generate.js +1 -1
  13. package/dist/adapters/kysely/kysely-adapter.d.ts +1 -1
  14. package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
  15. package/dist/adapters/kysely/kysely-query.js +29 -1
  16. package/dist/adapters/kysely/kysely-query.js.map +1 -1
  17. package/dist/adapters/kysely/kysely-uow-compiler.js +2 -1
  18. package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
  19. package/dist/adapters/kysely/migration/execute-base.js +1 -1
  20. package/dist/mod.d.ts +2 -1
  21. package/dist/mod.d.ts.map +1 -1
  22. package/dist/mod.js +2 -1
  23. package/dist/mod.js.map +1 -1
  24. package/dist/query/cursor.d.ts +67 -32
  25. package/dist/query/cursor.d.ts.map +1 -1
  26. package/dist/query/cursor.js +84 -31
  27. package/dist/query/cursor.js.map +1 -1
  28. package/dist/query/query.d.ts +5 -0
  29. package/dist/query/query.d.ts.map +1 -1
  30. package/dist/query/unit-of-work.d.ts +14 -4
  31. package/dist/query/unit-of-work.d.ts.map +1 -1
  32. package/dist/query/unit-of-work.js +52 -9
  33. package/dist/query/unit-of-work.js.map +1 -1
  34. package/package.json +3 -3
  35. package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +72 -5
  36. package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +6 -4
  37. package/src/adapters/drizzle/drizzle-query.ts +9 -0
  38. package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +19 -3
  39. package/src/adapters/drizzle/drizzle-uow-compiler.ts +3 -2
  40. package/src/adapters/drizzle/drizzle-uow-decoder.ts +40 -1
  41. package/src/adapters/kysely/kysely-adapter-pglite.test.ts +102 -4
  42. package/src/adapters/kysely/kysely-query.ts +50 -1
  43. package/src/adapters/kysely/kysely-uow-compiler.test.ts +19 -3
  44. package/src/adapters/kysely/kysely-uow-compiler.ts +3 -2
  45. package/src/mod.ts +6 -1
  46. package/src/query/cursor.test.ts +113 -68
  47. package/src/query/cursor.ts +127 -36
  48. package/src/query/query.ts +19 -0
  49. package/src/query/unit-of-work.ts +133 -15
@@ -1 +1 @@
1
- {"version":3,"file":"kysely-query.js","names":["#indexName","#condition","#setValues","executor: UOWExecutor<CompiledQuery, unknown>","decoder: UOWDecoder<T>"],"sources":["../../../src/adapters/kysely/kysely-query.ts"],"sourcesContent":["import type { AbstractQuery, TableToUpdateValues } from \"../../query/query\";\nimport type { AnySchema, AnyTable } from \"../../schema/create\";\nimport type {\n CompiledMutation,\n UOWDecoder,\n UOWExecutor,\n ValidIndexName,\n} from \"../../query/unit-of-work\";\nimport { decodeResult } from \"../../query/result-transform\";\nimport { createKyselyUOWCompiler } from \"./kysely-uow-compiler\";\nimport { executeKyselyRetrievalPhase, executeKyselyMutationPhase } from \"./kysely-uow-executor\";\nimport { UnitOfWork } from \"../../query/unit-of-work\";\nimport type { CompiledQuery, Kysely } from \"kysely\";\nimport type { TableNameMapper } from \"./kysely-shared\";\nimport type { ConnectionPool } from \"../../shared/connection-pool\";\nimport type { SQLProvider } from \"../../shared/providers\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KyselyAny = Kysely<any>;\n\n/**\n * Configuration options for creating a Kysely Unit of Work\n */\nexport interface KyselyUOWConfig {\n /**\n * Optional callback to receive compiled SQL queries for logging/debugging\n * This callback is invoked for each query as it's compiled\n */\n onQuery?: (query: CompiledQuery) => void;\n /**\n * If true, the query will not be executed and the query will be returned. Not respected for UOWs\n * since those have to be manually executed.\n */\n dryRun?: boolean;\n}\n\n/**\n * Special builder for updateMany operations that captures configuration\n */\nclass UpdateManySpecialBuilder<TTable extends AnyTable> {\n #indexName?: string;\n #condition?: unknown;\n #setValues?: TableToUpdateValues<TTable>;\n\n whereIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n condition?: unknown,\n ): this {\n this.#indexName = indexName as string;\n this.#condition = condition;\n return this;\n }\n\n set(values: TableToUpdateValues<TTable>): this {\n this.#setValues = values;\n return this;\n }\n\n getConfig() {\n return {\n indexName: this.#indexName,\n condition: this.#condition,\n setValues: this.#setValues,\n };\n }\n}\n\n/**\n * Creates a Kysely-based query engine for the given schema.\n *\n * This is the main entry point for creating a database query interface using Kysely.\n * It uses a compiler-based architecture where queries are compiled to SQL and then executed,\n * enabling features like SQL snapshot testing.\n *\n * @param schema - The database schema definition\n * @param pool - Connection pool for acquiring database connections\n * @param provider - SQL provider (postgresql, mysql, sqlite, etc.)\n * @param mapper - Optional table name mapper for namespace prefixing\n * @returns An AbstractQuery instance for performing database operations\n *\n * @example\n * ```ts\n * const pool = createSimpleConnectionPool(kysely);\n * const queryEngine = fromKysely(mySchema, pool, 'postgresql');\n *\n * const users = await queryEngine.findMany('users', {\n * where: (b) => b('age', '>', 18),\n * orderBy: [['name', 'asc']]\n * });\n * ```\n */\nexport function fromKysely<T extends AnySchema>(\n schema: T,\n pool: ConnectionPool<KyselyAny>,\n provider: SQLProvider,\n mapper?: TableNameMapper,\n uowConfig?: KyselyUOWConfig,\n): AbstractQuery<T, KyselyUOWConfig> {\n function createUOW(opts: { name?: string; config?: KyselyUOWConfig }) {\n const uowCompiler = createKyselyUOWCompiler(schema, pool, provider, mapper);\n\n const executor: UOWExecutor<CompiledQuery, unknown> = {\n async executeRetrievalPhase(retrievalBatch: CompiledQuery[]) {\n // In dryRun mode, skip execution and return empty results\n if (opts.config?.dryRun) {\n return retrievalBatch.map(() => []);\n }\n\n const conn = await pool.connect();\n try {\n return await executeKyselyRetrievalPhase(conn.db, retrievalBatch);\n } finally {\n await conn.release();\n }\n },\n async executeMutationPhase(mutationBatch: CompiledMutation<CompiledQuery>[]) {\n // In dryRun mode, skip execution and return success with mock internal IDs\n if (opts.config?.dryRun) {\n return {\n success: true,\n createdInternalIds: mutationBatch.map(() => null),\n };\n }\n\n const conn = await pool.connect();\n try {\n return await executeKyselyMutationPhase(conn.db, mutationBatch);\n } finally {\n await conn.release();\n }\n },\n };\n\n // Create a decoder function to transform raw results into application format\n const decoder: UOWDecoder<T> = (rawResults, ops) => {\n if (rawResults.length !== ops.length) {\n throw new Error(\"rawResults and ops must have the same length\");\n }\n\n return rawResults.map((rows, index) => {\n const op = ops[index];\n if (!op) {\n throw new Error(\"op must be defined\");\n }\n\n // Handle count operations differently - return the count number directly\n if (op.type === \"count\") {\n const rowArray = rows as Record<string, unknown>[];\n const firstRow = rowArray[0];\n if (!firstRow) {\n return 0;\n }\n const count = Number(firstRow[\"count\"]);\n if (Number.isNaN(count)) {\n throw new Error(`Unexpected result for count, received: ${count}`);\n }\n return count;\n }\n\n // Each result is an array of rows - decode each row\n const rowArray = rows as Record<string, unknown>[];\n return rowArray.map((row) => decodeResult(row, op.table, provider));\n });\n };\n\n const { onQuery, ...restUowConfig } = opts.config ?? {};\n\n return new UnitOfWork(schema, uowCompiler, executor, decoder, opts.name, {\n ...restUowConfig,\n onQuery: (query) => {\n // CompiledMutation has { query: CompiledQuery, expectedAffectedRows: number | null }\n // CompiledQuery has { query: QueryAST, sql: string, parameters: unknown[] }\n // Check for expectedAffectedRows to distinguish CompiledMutation from CompiledQuery\n const actualQuery =\n query && typeof query === \"object\" && \"expectedAffectedRows\" in query\n ? (query as CompiledMutation<CompiledQuery>).query\n : (query as CompiledQuery);\n\n opts.config?.onQuery?.(actualQuery);\n },\n });\n }\n\n return {\n async find(tableName, builderFn) {\n const uow = createUOW({ config: uowConfig });\n // Safe: builderFn returns a FindBuilder (or void), which matches UnitOfWork signature\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n uow.find(tableName, builderFn as any);\n // executeRetrieve returns an array of results (one per find operation)\n // Since we only have one find, unwrap the first result\n const [result]: unknown[][] = await uow.executeRetrieve();\n return result ?? [];\n },\n\n async findFirst(tableName, builderFn) {\n const uow = createUOW({ config: uowConfig });\n if (builderFn) {\n uow.find(tableName, (b) => {\n builderFn(b);\n return b.pageSize(1);\n });\n } else {\n uow.find(tableName, (b) => b.whereIndex(\"primary\").pageSize(1));\n }\n // executeRetrieve runs an array of `find` operation results, which each return an array of rows\n const [result]: unknown[][] = await uow.executeRetrieve();\n return result?.[0] ?? null;\n },\n\n async create(tableName, values) {\n const uow = createUOW({ config: uowConfig });\n uow.create(tableName, values);\n const { success } = await uow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to create record\");\n }\n\n const createdIds = uow.getCreatedIds();\n const createdId = createdIds[0];\n if (!createdId) {\n throw new Error(\"Failed to get created ID\");\n }\n return createdId;\n },\n\n async createMany(tableName, valuesArray) {\n const uow = createUOW({ config: uowConfig });\n for (const values of valuesArray) {\n uow.create(tableName, values);\n }\n const { success } = await uow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to create records\");\n }\n\n return uow.getCreatedIds();\n },\n\n async update(tableName, id, builderFn) {\n const uow = createUOW({ config: uowConfig });\n uow.update(tableName, id, builderFn);\n const { success } = await uow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to update record (version conflict or record not found)\");\n }\n },\n\n async updateMany(tableName, builderFn) {\n const table = schema.tables[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n const specialBuilder = new UpdateManySpecialBuilder<typeof table>();\n builderFn(specialBuilder);\n\n const { indexName, condition, setValues } = specialBuilder.getConfig();\n\n if (!indexName) {\n throw new Error(\"whereIndex() must be called in updateMany\");\n }\n if (!setValues) {\n throw new Error(\"set() must be called in updateMany\");\n }\n\n const findUow = createUOW({ config: uowConfig });\n findUow.find(tableName, (b) => {\n if (condition) {\n // Safe: condition is captured from whereIndex call with proper typing\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return b.whereIndex(indexName as ValidIndexName<typeof table>, condition as any);\n }\n return b.whereIndex(indexName as ValidIndexName<typeof table>);\n });\n const [records]: unknown[][] = await findUow.executeRetrieve();\n\n if (!records || records.length === 0) {\n return;\n }\n\n const updateUow = createUOW({ config: uowConfig });\n for (const record of records as Array<{ id: unknown }>) {\n updateUow.update(tableName, record.id as string, (b) => b.set(setValues));\n }\n const { success } = await updateUow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to update records (version conflict)\");\n }\n },\n\n async delete(tableName, id, builderFn) {\n const uow = createUOW({ config: uowConfig });\n uow.delete(tableName, id, builderFn);\n const { success } = await uow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to delete record (version conflict or record not found)\");\n }\n },\n\n async deleteMany(tableName, builderFn) {\n const findUow = createUOW({ config: uowConfig });\n findUow.find(tableName, builderFn);\n const [records]: unknown[][] = await findUow.executeRetrieve();\n\n if (!records || records.length === 0) {\n return;\n }\n\n const deleteUow = createUOW({ config: uowConfig });\n for (const record of records as Array<{ id: unknown }>) {\n deleteUow.delete(tableName, record.id as string);\n }\n const { success } = await deleteUow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to delete records (version conflict)\");\n }\n },\n\n createUnitOfWork(name, nestedUowConfig) {\n return createUOW({\n name,\n config: {\n ...uowConfig,\n ...nestedUowConfig,\n },\n });\n },\n } as AbstractQuery<T, KyselyUOWConfig>;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"kysely-query.js","names":["#indexName","#condition","#setValues","executor: UOWExecutor<CompiledQuery, unknown>","decoder: UOWDecoder<T>","cursor: Cursor | undefined","index"],"sources":["../../../src/adapters/kysely/kysely-query.ts"],"sourcesContent":["import type { AbstractQuery, TableToUpdateValues } from \"../../query/query\";\nimport type { AnySchema, AnyTable } from \"../../schema/create\";\nimport type {\n CompiledMutation,\n UOWDecoder,\n UOWExecutor,\n ValidIndexName,\n} from \"../../query/unit-of-work\";\nimport { decodeResult } from \"../../query/result-transform\";\nimport { createKyselyUOWCompiler } from \"./kysely-uow-compiler\";\nimport { executeKyselyRetrievalPhase, executeKyselyMutationPhase } from \"./kysely-uow-executor\";\nimport { UnitOfWork } from \"../../query/unit-of-work\";\nimport type { CompiledQuery, Kysely } from \"kysely\";\nimport type { TableNameMapper } from \"./kysely-shared\";\nimport type { ConnectionPool } from \"../../shared/connection-pool\";\nimport type { SQLProvider } from \"../../shared/providers\";\nimport { createCursorFromRecord, Cursor, type CursorResult } from \"../../query/cursor\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KyselyAny = Kysely<any>;\n\n/**\n * Configuration options for creating a Kysely Unit of Work\n */\nexport interface KyselyUOWConfig {\n /**\n * Optional callback to receive compiled SQL queries for logging/debugging\n * This callback is invoked for each query as it's compiled\n */\n onQuery?: (query: CompiledQuery) => void;\n /**\n * If true, the query will not be executed and the query will be returned. Not respected for UOWs\n * since those have to be manually executed.\n */\n dryRun?: boolean;\n}\n\n/**\n * Special builder for updateMany operations that captures configuration\n */\nclass UpdateManySpecialBuilder<TTable extends AnyTable> {\n #indexName?: string;\n #condition?: unknown;\n #setValues?: TableToUpdateValues<TTable>;\n\n whereIndex<TIndexName extends ValidIndexName<TTable>>(\n indexName: TIndexName,\n condition?: unknown,\n ): this {\n this.#indexName = indexName as string;\n this.#condition = condition;\n return this;\n }\n\n set(values: TableToUpdateValues<TTable>): this {\n this.#setValues = values;\n return this;\n }\n\n getConfig() {\n return {\n indexName: this.#indexName,\n condition: this.#condition,\n setValues: this.#setValues,\n };\n }\n}\n\n/**\n * Creates a Kysely-based query engine for the given schema.\n *\n * This is the main entry point for creating a database query interface using Kysely.\n * It uses a compiler-based architecture where queries are compiled to SQL and then executed,\n * enabling features like SQL snapshot testing.\n *\n * @param schema - The database schema definition\n * @param pool - Connection pool for acquiring database connections\n * @param provider - SQL provider (postgresql, mysql, sqlite, etc.)\n * @param mapper - Optional table name mapper for namespace prefixing\n * @returns An AbstractQuery instance for performing database operations\n *\n * @example\n * ```ts\n * const pool = createSimpleConnectionPool(kysely);\n * const queryEngine = fromKysely(mySchema, pool, 'postgresql');\n *\n * const users = await queryEngine.findMany('users', {\n * where: (b) => b('age', '>', 18),\n * orderBy: [['name', 'asc']]\n * });\n * ```\n */\nexport function fromKysely<T extends AnySchema>(\n schema: T,\n pool: ConnectionPool<KyselyAny>,\n provider: SQLProvider,\n mapper?: TableNameMapper,\n uowConfig?: KyselyUOWConfig,\n): AbstractQuery<T, KyselyUOWConfig> {\n function createUOW(opts: { name?: string; config?: KyselyUOWConfig }) {\n const uowCompiler = createKyselyUOWCompiler(schema, pool, provider, mapper);\n\n const executor: UOWExecutor<CompiledQuery, unknown> = {\n async executeRetrievalPhase(retrievalBatch: CompiledQuery[]) {\n // In dryRun mode, skip execution and return empty results\n if (opts.config?.dryRun) {\n return retrievalBatch.map(() => []);\n }\n\n const conn = await pool.connect();\n try {\n return await executeKyselyRetrievalPhase(conn.db, retrievalBatch);\n } finally {\n await conn.release();\n }\n },\n async executeMutationPhase(mutationBatch: CompiledMutation<CompiledQuery>[]) {\n // In dryRun mode, skip execution and return success with mock internal IDs\n if (opts.config?.dryRun) {\n return {\n success: true,\n createdInternalIds: mutationBatch.map(() => null),\n };\n }\n\n const conn = await pool.connect();\n try {\n return await executeKyselyMutationPhase(conn.db, mutationBatch);\n } finally {\n await conn.release();\n }\n },\n };\n\n // Create a decoder function to transform raw results into application format\n const decoder: UOWDecoder<T> = (rawResults, ops) => {\n if (rawResults.length !== ops.length) {\n throw new Error(\"rawResults and ops must have the same length\");\n }\n\n return rawResults.map((rows, index) => {\n const op = ops[index];\n if (!op) {\n throw new Error(\"op must be defined\");\n }\n\n // Handle count operations differently - return the count number directly\n if (op.type === \"count\") {\n const rowArray = rows as Record<string, unknown>[];\n const firstRow = rowArray[0];\n if (!firstRow) {\n return 0;\n }\n const count = Number(firstRow[\"count\"]);\n if (Number.isNaN(count)) {\n throw new Error(`Unexpected result for count, received: ${count}`);\n }\n return count;\n }\n\n // Each result is an array of rows - decode each row\n const rowArray = rows as Record<string, unknown>[];\n const decodedRows = rowArray.map((row) => decodeResult(row, op.table, provider));\n\n // If cursor generation is requested, wrap in CursorResult\n if (op.withCursor) {\n let cursor: Cursor | undefined;\n\n // Generate cursor from last item if results exist\n if (decodedRows.length > 0 && op.options.orderByIndex && op.options.pageSize) {\n const lastItem = decodedRows[decodedRows.length - 1];\n const indexName = op.options.orderByIndex.indexName;\n\n // Get index columns\n let indexColumns;\n if (indexName === \"_primary\") {\n indexColumns = [op.table.getIdColumn()];\n } else {\n const index = op.table.indexes[indexName];\n if (index) {\n indexColumns = index.columns;\n }\n }\n\n if (indexColumns && lastItem) {\n cursor = createCursorFromRecord(lastItem as Record<string, unknown>, indexColumns, {\n indexName: op.options.orderByIndex.indexName,\n orderDirection: op.options.orderByIndex.direction,\n pageSize: op.options.pageSize,\n });\n }\n }\n\n const result: CursorResult<unknown> = {\n items: decodedRows,\n cursor,\n };\n return result;\n }\n\n return decodedRows;\n });\n };\n\n const { onQuery, ...restUowConfig } = opts.config ?? {};\n\n return new UnitOfWork(schema, uowCompiler, executor, decoder, opts.name, {\n ...restUowConfig,\n onQuery: (query) => {\n // CompiledMutation has { query: CompiledQuery, expectedAffectedRows: number | null }\n // CompiledQuery has { query: QueryAST, sql: string, parameters: unknown[] }\n // Check for expectedAffectedRows to distinguish CompiledMutation from CompiledQuery\n const actualQuery =\n query && typeof query === \"object\" && \"expectedAffectedRows\" in query\n ? (query as CompiledMutation<CompiledQuery>).query\n : (query as CompiledQuery);\n\n opts.config?.onQuery?.(actualQuery);\n },\n });\n }\n\n return {\n async find(tableName, builderFn) {\n const uow = createUOW({ config: uowConfig });\n // Safe: builderFn returns a FindBuilder (or void), which matches UnitOfWork signature\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n uow.find(tableName, builderFn as any);\n // executeRetrieve returns an array of results (one per find operation)\n // Since we only have one find, unwrap the first result\n const [result]: unknown[][] = await uow.executeRetrieve();\n return result ?? [];\n },\n\n async findWithCursor(tableName, builderFn) {\n // Safe: builderFn returns a FindBuilder, which matches UnitOfWork signature\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const uow = createUOW({ config: uowConfig }).findWithCursor(tableName, builderFn as any);\n // executeRetrieve returns an array of results (one per find operation)\n // Since we only have one findWithCursor, unwrap the first result\n const [result] = await uow.executeRetrieve();\n return result as CursorResult<unknown>;\n },\n\n async findFirst(tableName, builderFn) {\n const uow = createUOW({ config: uowConfig });\n if (builderFn) {\n uow.find(tableName, (b) => {\n builderFn(b);\n return b.pageSize(1);\n });\n } else {\n uow.find(tableName, (b) => b.whereIndex(\"primary\").pageSize(1));\n }\n // executeRetrieve runs an array of `find` operation results, which each return an array of rows\n const [result]: unknown[][] = await uow.executeRetrieve();\n return result?.[0] ?? null;\n },\n\n async create(tableName, values) {\n const uow = createUOW({ config: uowConfig });\n uow.create(tableName, values);\n const { success } = await uow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to create record\");\n }\n\n const createdIds = uow.getCreatedIds();\n const createdId = createdIds[0];\n if (!createdId) {\n throw new Error(\"Failed to get created ID\");\n }\n return createdId;\n },\n\n async createMany(tableName, valuesArray) {\n const uow = createUOW({ config: uowConfig });\n for (const values of valuesArray) {\n uow.create(tableName, values);\n }\n const { success } = await uow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to create records\");\n }\n\n return uow.getCreatedIds();\n },\n\n async update(tableName, id, builderFn) {\n const uow = createUOW({ config: uowConfig });\n uow.update(tableName, id, builderFn);\n const { success } = await uow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to update record (version conflict or record not found)\");\n }\n },\n\n async updateMany(tableName, builderFn) {\n const table = schema.tables[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n const specialBuilder = new UpdateManySpecialBuilder<typeof table>();\n builderFn(specialBuilder);\n\n const { indexName, condition, setValues } = specialBuilder.getConfig();\n\n if (!indexName) {\n throw new Error(\"whereIndex() must be called in updateMany\");\n }\n if (!setValues) {\n throw new Error(\"set() must be called in updateMany\");\n }\n\n const findUow = createUOW({ config: uowConfig });\n findUow.find(tableName, (b) => {\n if (condition) {\n // Safe: condition is captured from whereIndex call with proper typing\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return b.whereIndex(indexName as ValidIndexName<typeof table>, condition as any);\n }\n return b.whereIndex(indexName as ValidIndexName<typeof table>);\n });\n const [records]: unknown[][] = await findUow.executeRetrieve();\n\n if (!records || records.length === 0) {\n return;\n }\n\n const updateUow = createUOW({ config: uowConfig });\n for (const record of records as Array<{ id: unknown }>) {\n updateUow.update(tableName, record.id as string, (b) => b.set(setValues));\n }\n const { success } = await updateUow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to update records (version conflict)\");\n }\n },\n\n async delete(tableName, id, builderFn) {\n const uow = createUOW({ config: uowConfig });\n uow.delete(tableName, id, builderFn);\n const { success } = await uow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to delete record (version conflict or record not found)\");\n }\n },\n\n async deleteMany(tableName, builderFn) {\n const findUow = createUOW({ config: uowConfig });\n findUow.find(tableName, builderFn);\n const [records]: unknown[][] = await findUow.executeRetrieve();\n\n if (!records || records.length === 0) {\n return;\n }\n\n const deleteUow = createUOW({ config: uowConfig });\n for (const record of records as Array<{ id: unknown }>) {\n deleteUow.delete(tableName, record.id as string);\n }\n const { success } = await deleteUow.executeMutations();\n if (!success) {\n throw new Error(\"Failed to delete records (version conflict)\");\n }\n },\n\n createUnitOfWork(name, nestedUowConfig) {\n return createUOW({\n name,\n config: {\n ...uowConfig,\n ...nestedUowConfig,\n },\n });\n },\n } as AbstractQuery<T, KyselyUOWConfig>;\n}\n"],"mappings":"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"}
@@ -43,7 +43,8 @@ function createKyselyUOWCompiler(schema, pool, provider, mapper) {
43
43
  if (indexColumns.length > 0) orderBy = indexColumns.map((col) => [col, orderDirection]);
44
44
  let cursorCondition;
45
45
  if ((after || before) && indexColumns.length > 0) {
46
- const serializedValues = serializeCursorValues(decodeCursor(after || before), indexColumns, provider);
46
+ const cursor = after || before;
47
+ const serializedValues = serializeCursorValues(typeof cursor === "string" ? decodeCursor(cursor) : cursor, indexColumns, provider);
47
48
  const isAfter = !!after;
48
49
  const useGreaterThan = isAfter && orderDirection === "asc" || !isAfter && orderDirection === "desc";
49
50
  if (indexColumns.length === 1) {
@@ -1 +1 @@
1
- {"version":3,"file":"kysely-uow-compiler.js","names":["indexColumns: AnyColumn[]","orderDirection: \"asc\" | \"desc\"","orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined","cursorCondition: Condition | undefined","combinedWhere: Condition | undefined"],"sources":["../../../src/adapters/kysely/kysely-uow-compiler.ts"],"sourcesContent":["import type { CompiledQuery, Kysely } from \"kysely\";\nimport type { AnyColumn, AnySchema, FragnoId } from \"../../schema/create\";\nimport type {\n CompiledMutation,\n MutationOperation,\n RetrievalOperation,\n UOWCompiler,\n} from \"../../query/unit-of-work\";\nimport { createKyselyQueryCompiler } from \"./kysely-query-compiler\";\nimport { createKyselyQueryBuilder } from \"./kysely-query-builder\";\nimport { buildCondition, type Condition } from \"../../query/condition-builder\";\nimport { decodeCursor, serializeCursorValues } from \"../../query/cursor\";\nimport type { AnySelectClause } from \"../../query/query\";\nimport type { TableNameMapper } from \"./kysely-shared\";\nimport type { ConnectionPool } from \"../../shared/connection-pool\";\nimport type { SQLProvider } from \"../../shared/providers\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KyselyAny = Kysely<any>;\n\n/**\n * Create a Kysely-specific Unit of Work compiler\n *\n * This compiler translates UOW operations into Kysely CompiledQuery objects\n * that can be executed as a batch/transaction.\n *\n * @param schema - The database schema\n * @param pool - Connection pool for acquiring database connections\n * @param provider - SQL provider (postgresql, mysql, sqlite, etc.)\n * @param mapper - Optional table name mapper for namespace prefixing\n * @returns A UOWCompiler instance for Kysely\n */\nexport function createKyselyUOWCompiler<TSchema extends AnySchema>(\n schema: TSchema,\n pool: ConnectionPool<KyselyAny>,\n provider: SQLProvider,\n mapper?: TableNameMapper,\n): UOWCompiler<TSchema, CompiledQuery> {\n const queryCompiler = createKyselyQueryCompiler(schema, pool, provider, mapper);\n // Get kysely instance for query building (compilation doesn't execute, just builds SQL)\n const kysely = pool.getDatabaseSync();\n const queryBuilder = createKyselyQueryBuilder(kysely, provider, mapper);\n\n function toTable(name: unknown) {\n const table = schema.tables[name as string];\n if (!table) {\n throw new Error(`Invalid table name ${name}.`);\n }\n return table;\n }\n\n return {\n compileRetrievalOperation(op: RetrievalOperation<TSchema>): CompiledQuery | null {\n switch (op.type) {\n case \"count\": {\n return queryCompiler.count(op.table.name, {\n where: op.options.where,\n });\n }\n\n case \"find\": {\n // Map UOW FindOptions to query compiler's FindManyOptions\n const {\n useIndex: _useIndex,\n orderByIndex,\n joins: join,\n after,\n before,\n pageSize,\n ...findManyOptions\n } = op.options;\n\n // Get index columns for ordering and cursor pagination\n let indexColumns: AnyColumn[] = [];\n let orderDirection: \"asc\" | \"desc\" = \"asc\";\n\n if (orderByIndex) {\n const index = op.table.indexes[orderByIndex.indexName];\n orderDirection = orderByIndex.direction;\n\n if (!index) {\n // If _primary index doesn't exist, fall back to internal ID column\n // (which is the actual primary key and maintains insertion order)\n if (orderByIndex.indexName === \"_primary\") {\n indexColumns = [op.table.getIdColumn()];\n } else {\n throw new Error(\n `Index \"${orderByIndex.indexName}\" not found on table \"${op.table.name}\"`,\n );\n }\n } else {\n // Order by all columns in the index with the specified direction\n indexColumns = index.columns;\n }\n }\n\n // Convert orderByIndex to orderBy format\n let orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined;\n if (indexColumns.length > 0) {\n orderBy = indexColumns.map((col) => [col, orderDirection]);\n }\n\n // Handle cursor pagination - build a cursor condition\n let cursorCondition: Condition | undefined;\n\n if ((after || before) && indexColumns.length > 0) {\n const cursor = after || before;\n const cursorData = decodeCursor(cursor!);\n const serializedValues = serializeCursorValues(cursorData, indexColumns, provider);\n\n // Build tuple comparison for cursor pagination\n // For \"after\" with \"asc\": (col1, col2, ...) > (val1, val2, ...)\n // For \"before\" with \"desc\": reverse the comparison\n const isAfter = !!after;\n const useGreaterThan =\n (isAfter && orderDirection === \"asc\") || (!isAfter && orderDirection === \"desc\");\n\n if (indexColumns.length === 1) {\n // Simple single-column case\n const col = indexColumns[0]!;\n const val = serializedValues[col.ormName];\n const operator = useGreaterThan ? \">\" : \"<\";\n cursorCondition = {\n type: \"compare\",\n a: col,\n operator,\n b: val,\n };\n } else {\n // Multi-column tuple comparison - not yet supported for Kysely\n throw new Error(\n \"Multi-column cursor pagination is not yet supported in Kysely Unit of Work implementation\",\n );\n }\n }\n\n // Combine user where clause with cursor condition\n let combinedWhere: Condition | undefined;\n if (findManyOptions.where) {\n const whereResult = buildCondition(op.table.columns, findManyOptions.where);\n if (whereResult === true) {\n combinedWhere = undefined;\n } else if (whereResult === false) {\n return null;\n } else {\n combinedWhere = whereResult;\n }\n }\n\n if (cursorCondition) {\n if (combinedWhere) {\n combinedWhere = {\n type: \"and\",\n items: [combinedWhere, cursorCondition],\n };\n } else {\n combinedWhere = cursorCondition;\n }\n }\n\n // When we have joins or need to bypass buildFindOptions, use queryBuilder directly\n if (join && join.length > 0) {\n return queryBuilder.findMany(op.table, {\n // Safe cast: select from UOW matches SimplifyFindOptions requirement\n select: (findManyOptions.select ?? true) as AnySelectClause,\n where: combinedWhere,\n orderBy,\n limit: pageSize,\n join,\n });\n }\n\n return queryCompiler.findMany(op.table.name, {\n ...findManyOptions,\n where: combinedWhere ? () => combinedWhere! : undefined,\n orderBy: orderBy?.map(([col, dir]) => [col.ormName, dir]),\n limit: pageSize,\n });\n }\n }\n },\n\n compileMutationOperation(\n op: MutationOperation<TSchema>,\n ): CompiledMutation<CompiledQuery> | null {\n switch (op.type) {\n case \"create\":\n // queryCompiler.create() calls encodeValues() which handles runtime defaults\n return {\n query: queryCompiler.create(op.table, op.values),\n expectedAffectedRows: null, // creates don't need affected row checks\n };\n\n case \"update\": {\n const table = toTable(op.table);\n const idColumn = table.getIdColumn();\n const versionColumn = table.getVersionColumn();\n\n const externalId = typeof op.id === \"string\" ? op.id : op.id.externalId;\n const versionToCheck = getVersionToCheck(op.id, op.checkVersion);\n\n // Build WHERE clause that filters by ID and optionally by version\n const whereClause =\n versionToCheck !== undefined\n ? () =>\n buildCondition(table.columns, (eb) =>\n eb.and(\n eb(idColumn.ormName, \"=\", externalId),\n eb(versionColumn.ormName, \"=\", versionToCheck),\n ),\n )\n : () => buildCondition(table.columns, (eb) => eb(idColumn.ormName, \"=\", externalId));\n\n const query = queryCompiler.updateMany(op.table, {\n where: whereClause,\n set: op.set,\n });\n\n return query\n ? {\n query,\n expectedAffectedRows: op.checkVersion ? 1 : null,\n }\n : null;\n }\n\n case \"delete\": {\n const table = toTable(op.table);\n const idColumn = table.getIdColumn();\n const versionColumn = table.getVersionColumn();\n\n // Extract external ID based on whether op.id is FragnoId or string\n const externalId = typeof op.id === \"string\" ? op.id : op.id.externalId;\n const versionToCheck = getVersionToCheck(op.id, op.checkVersion);\n\n // Build WHERE clause that filters by ID and optionally by version\n const whereClause =\n versionToCheck !== undefined\n ? () =>\n buildCondition(table.columns, (eb) =>\n eb.and(\n eb(idColumn.ormName, \"=\", externalId),\n eb(versionColumn.ormName, \"=\", versionToCheck),\n ),\n )\n : () => buildCondition(table.columns, (eb) => eb(idColumn.ormName, \"=\", externalId));\n\n const query = queryCompiler.deleteMany(op.table, {\n where: whereClause,\n });\n\n return query\n ? {\n query,\n expectedAffectedRows: op.checkVersion ? 1 : null,\n }\n : null;\n }\n }\n },\n };\n}\n\n/**\n * Get the version to check for a given ID and checkVersion flag.\n * @returns The version to check or undefined if no check is required.\n * @throws Error if the ID is a string and checkVersion is true.\n */\nfunction getVersionToCheck(id: FragnoId | string, checkVersion: boolean): number | undefined {\n if (!checkVersion) {\n return undefined;\n }\n\n if (typeof id === \"string\") {\n throw new Error(\n `Cannot use checkVersion with a string ID. Version checking requires a FragnoId with version information.`,\n );\n }\n\n return id.version;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"kysely-uow-compiler.js","names":["indexColumns: AnyColumn[]","orderDirection: \"asc\" | \"desc\"","orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined","cursorCondition: Condition | undefined","combinedWhere: Condition | undefined"],"sources":["../../../src/adapters/kysely/kysely-uow-compiler.ts"],"sourcesContent":["import type { CompiledQuery, Kysely } from \"kysely\";\nimport type { AnyColumn, AnySchema, FragnoId } from \"../../schema/create\";\nimport type {\n CompiledMutation,\n MutationOperation,\n RetrievalOperation,\n UOWCompiler,\n} from \"../../query/unit-of-work\";\nimport { createKyselyQueryCompiler } from \"./kysely-query-compiler\";\nimport { createKyselyQueryBuilder } from \"./kysely-query-builder\";\nimport { buildCondition, type Condition } from \"../../query/condition-builder\";\nimport { decodeCursor, serializeCursorValues } from \"../../query/cursor\";\nimport type { AnySelectClause } from \"../../query/query\";\nimport type { TableNameMapper } from \"./kysely-shared\";\nimport type { ConnectionPool } from \"../../shared/connection-pool\";\nimport type { SQLProvider } from \"../../shared/providers\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype KyselyAny = Kysely<any>;\n\n/**\n * Create a Kysely-specific Unit of Work compiler\n *\n * This compiler translates UOW operations into Kysely CompiledQuery objects\n * that can be executed as a batch/transaction.\n *\n * @param schema - The database schema\n * @param pool - Connection pool for acquiring database connections\n * @param provider - SQL provider (postgresql, mysql, sqlite, etc.)\n * @param mapper - Optional table name mapper for namespace prefixing\n * @returns A UOWCompiler instance for Kysely\n */\nexport function createKyselyUOWCompiler<TSchema extends AnySchema>(\n schema: TSchema,\n pool: ConnectionPool<KyselyAny>,\n provider: SQLProvider,\n mapper?: TableNameMapper,\n): UOWCompiler<TSchema, CompiledQuery> {\n const queryCompiler = createKyselyQueryCompiler(schema, pool, provider, mapper);\n // Get kysely instance for query building (compilation doesn't execute, just builds SQL)\n const kysely = pool.getDatabaseSync();\n const queryBuilder = createKyselyQueryBuilder(kysely, provider, mapper);\n\n function toTable(name: unknown) {\n const table = schema.tables[name as string];\n if (!table) {\n throw new Error(`Invalid table name ${name}.`);\n }\n return table;\n }\n\n return {\n compileRetrievalOperation(op: RetrievalOperation<TSchema>): CompiledQuery | null {\n switch (op.type) {\n case \"count\": {\n return queryCompiler.count(op.table.name, {\n where: op.options.where,\n });\n }\n\n case \"find\": {\n // Map UOW FindOptions to query compiler's FindManyOptions\n const {\n useIndex: _useIndex,\n orderByIndex,\n joins: join,\n after,\n before,\n pageSize,\n ...findManyOptions\n } = op.options;\n\n // Get index columns for ordering and cursor pagination\n let indexColumns: AnyColumn[] = [];\n let orderDirection: \"asc\" | \"desc\" = \"asc\";\n\n if (orderByIndex) {\n const index = op.table.indexes[orderByIndex.indexName];\n orderDirection = orderByIndex.direction;\n\n if (!index) {\n // If _primary index doesn't exist, fall back to internal ID column\n // (which is the actual primary key and maintains insertion order)\n if (orderByIndex.indexName === \"_primary\") {\n indexColumns = [op.table.getIdColumn()];\n } else {\n throw new Error(\n `Index \"${orderByIndex.indexName}\" not found on table \"${op.table.name}\"`,\n );\n }\n } else {\n // Order by all columns in the index with the specified direction\n indexColumns = index.columns;\n }\n }\n\n // Convert orderByIndex to orderBy format\n let orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined;\n if (indexColumns.length > 0) {\n orderBy = indexColumns.map((col) => [col, orderDirection]);\n }\n\n // Handle cursor pagination - build a cursor condition\n let cursorCondition: Condition | undefined;\n\n if ((after || before) && indexColumns.length > 0) {\n const cursor = after || before;\n // Decode cursor if it's a string, otherwise use it as-is\n const cursorObj = typeof cursor === \"string\" ? decodeCursor(cursor!) : cursor!;\n const serializedValues = serializeCursorValues(cursorObj, indexColumns, provider);\n\n // Build tuple comparison for cursor pagination\n // For \"after\" with \"asc\": (col1, col2, ...) > (val1, val2, ...)\n // For \"before\" with \"desc\": reverse the comparison\n const isAfter = !!after;\n const useGreaterThan =\n (isAfter && orderDirection === \"asc\") || (!isAfter && orderDirection === \"desc\");\n\n if (indexColumns.length === 1) {\n // Simple single-column case\n const col = indexColumns[0]!;\n const val = serializedValues[col.ormName];\n const operator = useGreaterThan ? \">\" : \"<\";\n cursorCondition = {\n type: \"compare\",\n a: col,\n operator,\n b: val,\n };\n } else {\n // Multi-column tuple comparison - not yet supported for Kysely\n throw new Error(\n \"Multi-column cursor pagination is not yet supported in Kysely Unit of Work implementation\",\n );\n }\n }\n\n // Combine user where clause with cursor condition\n let combinedWhere: Condition | undefined;\n if (findManyOptions.where) {\n const whereResult = buildCondition(op.table.columns, findManyOptions.where);\n if (whereResult === true) {\n combinedWhere = undefined;\n } else if (whereResult === false) {\n return null;\n } else {\n combinedWhere = whereResult;\n }\n }\n\n if (cursorCondition) {\n if (combinedWhere) {\n combinedWhere = {\n type: \"and\",\n items: [combinedWhere, cursorCondition],\n };\n } else {\n combinedWhere = cursorCondition;\n }\n }\n\n // When we have joins or need to bypass buildFindOptions, use queryBuilder directly\n if (join && join.length > 0) {\n return queryBuilder.findMany(op.table, {\n // Safe cast: select from UOW matches SimplifyFindOptions requirement\n select: (findManyOptions.select ?? true) as AnySelectClause,\n where: combinedWhere,\n orderBy,\n limit: pageSize,\n join,\n });\n }\n\n return queryCompiler.findMany(op.table.name, {\n ...findManyOptions,\n where: combinedWhere ? () => combinedWhere! : undefined,\n orderBy: orderBy?.map(([col, dir]) => [col.ormName, dir]),\n limit: pageSize,\n });\n }\n }\n },\n\n compileMutationOperation(\n op: MutationOperation<TSchema>,\n ): CompiledMutation<CompiledQuery> | null {\n switch (op.type) {\n case \"create\":\n // queryCompiler.create() calls encodeValues() which handles runtime defaults\n return {\n query: queryCompiler.create(op.table, op.values),\n expectedAffectedRows: null, // creates don't need affected row checks\n };\n\n case \"update\": {\n const table = toTable(op.table);\n const idColumn = table.getIdColumn();\n const versionColumn = table.getVersionColumn();\n\n const externalId = typeof op.id === \"string\" ? op.id : op.id.externalId;\n const versionToCheck = getVersionToCheck(op.id, op.checkVersion);\n\n // Build WHERE clause that filters by ID and optionally by version\n const whereClause =\n versionToCheck !== undefined\n ? () =>\n buildCondition(table.columns, (eb) =>\n eb.and(\n eb(idColumn.ormName, \"=\", externalId),\n eb(versionColumn.ormName, \"=\", versionToCheck),\n ),\n )\n : () => buildCondition(table.columns, (eb) => eb(idColumn.ormName, \"=\", externalId));\n\n const query = queryCompiler.updateMany(op.table, {\n where: whereClause,\n set: op.set,\n });\n\n return query\n ? {\n query,\n expectedAffectedRows: op.checkVersion ? 1 : null,\n }\n : null;\n }\n\n case \"delete\": {\n const table = toTable(op.table);\n const idColumn = table.getIdColumn();\n const versionColumn = table.getVersionColumn();\n\n // Extract external ID based on whether op.id is FragnoId or string\n const externalId = typeof op.id === \"string\" ? op.id : op.id.externalId;\n const versionToCheck = getVersionToCheck(op.id, op.checkVersion);\n\n // Build WHERE clause that filters by ID and optionally by version\n const whereClause =\n versionToCheck !== undefined\n ? () =>\n buildCondition(table.columns, (eb) =>\n eb.and(\n eb(idColumn.ormName, \"=\", externalId),\n eb(versionColumn.ormName, \"=\", versionToCheck),\n ),\n )\n : () => buildCondition(table.columns, (eb) => eb(idColumn.ormName, \"=\", externalId));\n\n const query = queryCompiler.deleteMany(op.table, {\n where: whereClause,\n });\n\n return query\n ? {\n query,\n expectedAffectedRows: op.checkVersion ? 1 : null,\n }\n : null;\n }\n }\n },\n };\n}\n\n/**\n * Get the version to check for a given ID and checkVersion flag.\n * @returns The version to check or undefined if no check is required.\n * @throws Error if the ID is a string and checkVersion is true.\n */\nfunction getVersionToCheck(id: FragnoId | string, checkVersion: boolean): number | undefined {\n if (!checkVersion) {\n return undefined;\n }\n\n if (typeof id === \"string\") {\n throw new Error(\n `Cannot use checkVersion with a string ID. Version checking requires a FragnoId with version information.`,\n );\n }\n\n return id.version;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { SETTINGS_TABLE_NAME } from "../../../shared/settings-schema.js";
2
1
  import { schemaToDBType } from "../../../schema/serialize.js";
2
+ import { SETTINGS_TABLE_NAME } from "../../../shared/settings-schema.js";
3
3
  import { sql } from "kysely";
4
4
 
5
5
  //#region src/adapters/kysely/migration/execute-base.ts
package/dist/mod.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AnySchema } from "./schema/create.js";
2
+ import { Cursor, CursorData, CursorResult, decodeCursor } from "./query/cursor.js";
2
3
  import { AbstractQuery } from "./query/query.js";
3
4
  import { DatabaseAdapter } from "./adapters/adapters.js";
4
5
  import { DatabaseFragmentBuilder, DatabaseFragmentContext, FragnoPublicConfigWithDatabase, defineFragmentWithDatabase } from "./fragment.js";
@@ -46,5 +47,5 @@ declare class FragnoDatabase<const T extends AnySchema, TUOWConfig = void> {
46
47
  }
47
48
  declare function defineFragnoDatabase<const TSchema extends AnySchema>(options: CreateFragnoDatabaseDefinitionOptions<TSchema>): FragnoDatabaseDefinition<TSchema>;
48
49
  //#endregion
49
- export { CreateFragnoDatabaseDefinitionOptions, type DatabaseAdapter, DatabaseFragmentBuilder, type DatabaseFragmentContext, FragnoDatabase, FragnoDatabaseDefinition, type FragnoPublicConfigWithDatabase, defineFragmentWithDatabase, defineFragnoDatabase, fragnoDatabaseFakeSymbol, fragnoDatabaseLibraryVersion, isFragnoDatabase };
50
+ export { CreateFragnoDatabaseDefinitionOptions, Cursor, type CursorData, type CursorResult, type DatabaseAdapter, DatabaseFragmentBuilder, type DatabaseFragmentContext, FragnoDatabase, FragnoDatabaseDefinition, type FragnoPublicConfigWithDatabase, decodeCursor, defineFragmentWithDatabase, defineFragnoDatabase, fragnoDatabaseFakeSymbol, fragnoDatabaseLibraryVersion, isFragnoDatabase };
50
51
  //# sourceMappingURL=mod.d.ts.map
package/dist/mod.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;;cAMa;cACA;AADA,UAGI,qCAHkD,CAAA,UAGF,SAHE,CAAA,CAAA;EACtD,SAAA,EAAA,MAAA;EAEI,MAAA,EAEP,CAFO;AAKjB;AAoBa,iBApBG,gBAAA,CAoBqB,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IApBsB,cAoBtB,CApBqC,SAoBrC,CAAA;;;;;;AAoBA,cApBxB,wBAoBwB,CAAA,gBApBiB,SAoBjB,CAAA,CAAA;EAA6C,CAAA,OAAA;EAAG,WAAA,CAAA,OAAA,EAhB9D,qCAgB8D,CAhBxB,CAgBwB,CAAA;EAAlB,IAAA,SAAA,CAAA,CAAA,EAAA,MAAA;EAAc,IAAA,MAAA,CAAA,CAAA,EAPrE,CAOqE;EAapE;;;EAKmE,MAAA,CAAA,aAAA,IAAA,CAAA,CAAA,OAAA,EAlB3C,eAkB2C,CAlB3B,UAkB2B,CAAA,CAAA,EAlBb,cAkBa,CAlBE,CAkBF,EAlBK,UAkBL,CAAA;;;;;;AAUhD,cAfnB,cAemB,CAAA,gBAfY,SAeZ,EAAA,aAAA,IAAA,CAAA,CAAA;EAAR,CAAA,OAAA;EAYC,WAAA,CAAA,OAAA,EAAA;IAgBb,SAAA,EAAA,MAAA;IAIqB,MAAA,EA1CmB,CA0CnB;IAAhB,OAAA,EA1C+C,eA0C/C,CA1C+D,UA0C/D,CAAA;EAAe,CAAA;EAKhB,KAzCT,wBAAA,GAyC6B,EAAA,OAzCO,wBAyCP;EAAuB,YAAA,CAAA,CAAA,EArCnC,OAqCmC,CArC3B,aAqC2B,CArCb,CAqCa,EArCV,UAqCU,CAAA,CAAA;EACV,aAAA,CAAA,CAAA,EA1BxB,OA0BwB,CAAA,OAAA,CAAA;EAAtC,IAAA,SAAA,CAAA,CAAA,EAAA,MAAA;EACiB,IAAA,MAAA,CAAA,CAAA,EAXhB,CAWgB;EAAzB,IAAA,OAAA,CAAA,CAAA,EAPc,eAOd,CAP8B,UAO9B,CAAA;;iBAFa,2CAA2C,oBAChD,sCAAsC,WAC9C,yBAAyB"}
1
+ {"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;;;AASa,cAAA,wBAAsD,EAAA,kBAAA;AACtD,cAAA,4BAA6C,EAAA,KAAA;AAEzC,UAAA,qCAAqC,CAAA,UAAW,SAEtD,CAAA,CAAA;EAGK,SAAA,EAAA,MAAA;EAoBH,MAAA,EAvBH,CAuBG;;AAIgD,iBAxB7C,gBAAA,CAwB6C,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IAxBF,cAwBE,CAxBa,SAwBb,CAAA;;;;;;AAgBwB,cApBxE,wBAoBwE,CAAA,gBApB/B,SAoB+B,CAAA,CAAA;EAAlB,CAAA,OAAA;EAAc,WAAA,CAAA,OAAA,EAhB1D,qCAgB0D,CAhBpB,CAgBoB,CAAA;EAapE,IAAA,SAAA,CAAA,CAAA,EAAc,MAAA;EAAiB,IAAA,MAAA,CAAA,CAAA,EApBhC,CAoBgC;EAKQ;;;EAMT,MAAA,CAAA,aAAA,IAAA,CAAA,CAAA,OAAA,EAxBN,eAwBM,CAxBU,UAwBV,CAAA,CAAA,EAxBwB,cAwBxB,CAxBuC,CAwBvC,EAxB0C,UAwB1C,CAAA;;;;;;AAgBlB,cA3BZ,cA2BY,CAAA,gBA3BmB,SA2BnB,EAAA,aAAA,IAAA,CAAA,CAAA;EAgBb,CAAA,OAAA;EAIqB,WAAA,CAAA,OAAA,EAAA;IAAhB,SAAA,EAAA,MAAA;IAAe,MAAA,EA1CoB,CA0CpB;IAKhB,OAAA,EA/CgD,eA+C5B,CA/C4C,UA+C5C,CAAA;EAAuB,CAAA;EACV,KA1C1C,wBAAA,GA0C0C,EAAA,OA1CN,wBA0CM;EAAtC,YAAA,CAAA,CAAA,EAtCa,OAsCb,CAtCqB,aAsCrB,CAtCmC,CAsCnC,EAtCsC,UAsCtC,CAAA,CAAA;EACiB,aAAA,CAAA,CAAA,EA3BH,OA2BG,CAAA,OAAA,CAAA;EAAzB,IAAA,SAAA,CAAA,CAAA,EAAA,MAAA;EAAwB,IAAA,MAAA,CAAA,CAAA,EAXf,CAWe;iBAPV,gBAAgB;;iBAKjB,2CAA2C,oBAChD,sCAAsC,WAC9C,yBAAyB"}
package/dist/mod.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { DatabaseFragmentBuilder, defineFragmentWithDatabase } from "./fragment.js";
2
+ import { Cursor, decodeCursor } from "./query/cursor.js";
2
3
 
3
4
  //#region src/mod.ts
4
5
  const fragnoDatabaseFakeSymbol = "$fragno-database";
@@ -79,5 +80,5 @@ function defineFragnoDatabase(options) {
79
80
  }
80
81
 
81
82
  //#endregion
82
- export { DatabaseFragmentBuilder, FragnoDatabase, FragnoDatabaseDefinition, defineFragmentWithDatabase, defineFragnoDatabase, fragnoDatabaseFakeSymbol, fragnoDatabaseLibraryVersion, isFragnoDatabase };
83
+ export { Cursor, DatabaseFragmentBuilder, FragnoDatabase, FragnoDatabaseDefinition, decodeCursor, defineFragmentWithDatabase, defineFragnoDatabase, fragnoDatabaseFakeSymbol, fragnoDatabaseLibraryVersion, isFragnoDatabase };
83
84
  //# sourceMappingURL=mod.js.map
package/dist/mod.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.js","names":["#namespace","#schema","#adapter"],"sources":["../src/mod.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"./adapters/adapters\";\nimport type { AnySchema } from \"./schema/create\";\nimport type { AbstractQuery } from \"./query/query\";\n\nexport type { DatabaseAdapter };\n\nexport const fragnoDatabaseFakeSymbol = \"$fragno-database\" as const;\nexport const fragnoDatabaseLibraryVersion = \"0.1\" as const;\n\nexport interface CreateFragnoDatabaseDefinitionOptions<T extends AnySchema> {\n namespace: string;\n schema: T;\n}\n\nexport function isFragnoDatabase(value: unknown): value is FragnoDatabase<AnySchema> {\n if (value instanceof FragnoDatabase) {\n return true;\n }\n\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n return (\n fragnoDatabaseFakeSymbol in value &&\n value[fragnoDatabaseFakeSymbol] === fragnoDatabaseFakeSymbol\n );\n}\n\n/**\n * Definition of a Fragno database schema and namespace.\n * Created by library authors using defineFragnoDatabase().\n * Apps instantiate it by calling .create(adapter).\n */\nexport class FragnoDatabaseDefinition<const T extends AnySchema> {\n #namespace: string;\n #schema: T;\n\n constructor(options: CreateFragnoDatabaseDefinitionOptions<T>) {\n this.#namespace = options.namespace;\n this.#schema = options.schema;\n }\n\n get namespace() {\n return this.#namespace;\n }\n\n get schema() {\n return this.#schema;\n }\n\n /**\n * Creates a FragnoDatabase instance by binding an adapter to this definition.\n */\n create<TUOWConfig = void>(adapter: DatabaseAdapter<TUOWConfig>): FragnoDatabase<T, TUOWConfig> {\n return new FragnoDatabase({\n namespace: this.#namespace,\n schema: this.#schema,\n adapter,\n });\n }\n}\n\n/**\n * A Fragno database instance with a bound adapter.\n * Created from a FragnoDatabaseDefinition by calling .create(adapter).\n */\nexport class FragnoDatabase<const T extends AnySchema, TUOWConfig = void> {\n #namespace: string;\n #schema: T;\n #adapter: DatabaseAdapter<TUOWConfig>;\n\n constructor(options: { namespace: string; schema: T; adapter: DatabaseAdapter<TUOWConfig> }) {\n this.#namespace = options.namespace;\n this.#schema = options.schema;\n this.#adapter = options.adapter;\n }\n\n get [fragnoDatabaseFakeSymbol](): typeof fragnoDatabaseFakeSymbol {\n return fragnoDatabaseFakeSymbol;\n }\n\n async createClient(): Promise<AbstractQuery<T, TUOWConfig>> {\n const dbVersion = await this.#adapter.getSchemaVersion(this.#namespace);\n if (dbVersion !== this.#schema.version.toString()) {\n throw new Error(\n `Database is not at expected version. Did you forget to run migrations?` +\n ` Current version: ${dbVersion}, Expected version: ${this.#schema.version}`,\n );\n }\n\n return this.#adapter.createQueryEngine(this.#schema, this.#namespace);\n }\n\n async runMigrations(): Promise<boolean> {\n if (!this.#adapter.createMigrationEngine) {\n throw new Error(\"Migration engine not supported for this adapter.\");\n }\n\n const migrator = this.#adapter.createMigrationEngine(this.#schema, this.#namespace);\n const preparedMigration = await migrator.prepareMigration();\n await preparedMigration.execute();\n\n return preparedMigration.operations.length > 0;\n }\n\n get namespace() {\n return this.#namespace;\n }\n\n get schema() {\n return this.#schema;\n }\n\n get adapter(): DatabaseAdapter<TUOWConfig> {\n return this.#adapter;\n }\n}\n\nexport function defineFragnoDatabase<const TSchema extends AnySchema>(\n options: CreateFragnoDatabaseDefinitionOptions<TSchema>,\n): FragnoDatabaseDefinition<TSchema> {\n return new FragnoDatabaseDefinition(options);\n}\n\nexport {\n defineFragmentWithDatabase,\n DatabaseFragmentBuilder,\n type FragnoPublicConfigWithDatabase,\n type DatabaseFragmentContext,\n} from \"./fragment\";\n"],"mappings":";;;AAMA,MAAa,2BAA2B;AACxC,MAAa,+BAA+B;AAO5C,SAAgB,iBAAiB,OAAoD;AACnF,KAAI,iBAAiB,eACnB,QAAO;AAGT,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;AAGT,QACE,4BAA4B,SAC5B,MAAM,8BAA8B;;;;;;;AASxC,IAAa,2BAAb,MAAiE;CAC/D;CACA;CAEA,YAAY,SAAmD;AAC7D,QAAKA,YAAa,QAAQ;AAC1B,QAAKC,SAAU,QAAQ;;CAGzB,IAAI,YAAY;AACd,SAAO,MAAKD;;CAGd,IAAI,SAAS;AACX,SAAO,MAAKC;;;;;CAMd,OAA0B,SAAqE;AAC7F,SAAO,IAAI,eAAe;GACxB,WAAW,MAAKD;GAChB,QAAQ,MAAKC;GACb;GACD,CAAC;;;;;;;AAQN,IAAa,iBAAb,MAA0E;CACxE;CACA;CACA;CAEA,YAAY,SAAiF;AAC3F,QAAKD,YAAa,QAAQ;AAC1B,QAAKC,SAAU,QAAQ;AACvB,QAAKC,UAAW,QAAQ;;CAG1B,KAAK,4BAA6D;AAChE,SAAO;;CAGT,MAAM,eAAsD;EAC1D,MAAM,YAAY,MAAM,MAAKA,QAAS,iBAAiB,MAAKF,UAAW;AACvE,MAAI,cAAc,MAAKC,OAAQ,QAAQ,UAAU,CAC/C,OAAM,IAAI,MACR,2FACuB,UAAU,sBAAsB,MAAKA,OAAQ,UACrE;AAGH,SAAO,MAAKC,QAAS,kBAAkB,MAAKD,QAAS,MAAKD,UAAW;;CAGvE,MAAM,gBAAkC;AACtC,MAAI,CAAC,MAAKE,QAAS,sBACjB,OAAM,IAAI,MAAM,mDAAmD;EAIrE,MAAM,oBAAoB,MADT,MAAKA,QAAS,sBAAsB,MAAKD,QAAS,MAAKD,UAAW,CAC1C,kBAAkB;AAC3D,QAAM,kBAAkB,SAAS;AAEjC,SAAO,kBAAkB,WAAW,SAAS;;CAG/C,IAAI,YAAY;AACd,SAAO,MAAKA;;CAGd,IAAI,SAAS;AACX,SAAO,MAAKC;;CAGd,IAAI,UAAuC;AACzC,SAAO,MAAKC;;;AAIhB,SAAgB,qBACd,SACmC;AACnC,QAAO,IAAI,yBAAyB,QAAQ"}
1
+ {"version":3,"file":"mod.js","names":["#namespace","#schema","#adapter"],"sources":["../src/mod.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"./adapters/adapters\";\nimport type { AnySchema } from \"./schema/create\";\nimport type { AbstractQuery } from \"./query/query\";\nimport type { CursorResult } from \"./query/cursor\";\nimport { Cursor } from \"./query/cursor\";\n\nexport type { DatabaseAdapter, CursorResult };\nexport { Cursor };\n\nexport const fragnoDatabaseFakeSymbol = \"$fragno-database\" as const;\nexport const fragnoDatabaseLibraryVersion = \"0.1\" as const;\n\nexport interface CreateFragnoDatabaseDefinitionOptions<T extends AnySchema> {\n namespace: string;\n schema: T;\n}\n\nexport function isFragnoDatabase(value: unknown): value is FragnoDatabase<AnySchema> {\n if (value instanceof FragnoDatabase) {\n return true;\n }\n\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n return (\n fragnoDatabaseFakeSymbol in value &&\n value[fragnoDatabaseFakeSymbol] === fragnoDatabaseFakeSymbol\n );\n}\n\n/**\n * Definition of a Fragno database schema and namespace.\n * Created by library authors using defineFragnoDatabase().\n * Apps instantiate it by calling .create(adapter).\n */\nexport class FragnoDatabaseDefinition<const T extends AnySchema> {\n #namespace: string;\n #schema: T;\n\n constructor(options: CreateFragnoDatabaseDefinitionOptions<T>) {\n this.#namespace = options.namespace;\n this.#schema = options.schema;\n }\n\n get namespace() {\n return this.#namespace;\n }\n\n get schema() {\n return this.#schema;\n }\n\n /**\n * Creates a FragnoDatabase instance by binding an adapter to this definition.\n */\n create<TUOWConfig = void>(adapter: DatabaseAdapter<TUOWConfig>): FragnoDatabase<T, TUOWConfig> {\n return new FragnoDatabase({\n namespace: this.#namespace,\n schema: this.#schema,\n adapter,\n });\n }\n}\n\n/**\n * A Fragno database instance with a bound adapter.\n * Created from a FragnoDatabaseDefinition by calling .create(adapter).\n */\nexport class FragnoDatabase<const T extends AnySchema, TUOWConfig = void> {\n #namespace: string;\n #schema: T;\n #adapter: DatabaseAdapter<TUOWConfig>;\n\n constructor(options: { namespace: string; schema: T; adapter: DatabaseAdapter<TUOWConfig> }) {\n this.#namespace = options.namespace;\n this.#schema = options.schema;\n this.#adapter = options.adapter;\n }\n\n get [fragnoDatabaseFakeSymbol](): typeof fragnoDatabaseFakeSymbol {\n return fragnoDatabaseFakeSymbol;\n }\n\n async createClient(): Promise<AbstractQuery<T, TUOWConfig>> {\n const dbVersion = await this.#adapter.getSchemaVersion(this.#namespace);\n if (dbVersion !== this.#schema.version.toString()) {\n throw new Error(\n `Database is not at expected version. Did you forget to run migrations?` +\n ` Current version: ${dbVersion}, Expected version: ${this.#schema.version}`,\n );\n }\n\n return this.#adapter.createQueryEngine(this.#schema, this.#namespace);\n }\n\n async runMigrations(): Promise<boolean> {\n if (!this.#adapter.createMigrationEngine) {\n throw new Error(\"Migration engine not supported for this adapter.\");\n }\n\n const migrator = this.#adapter.createMigrationEngine(this.#schema, this.#namespace);\n const preparedMigration = await migrator.prepareMigration();\n await preparedMigration.execute();\n\n return preparedMigration.operations.length > 0;\n }\n\n get namespace() {\n return this.#namespace;\n }\n\n get schema() {\n return this.#schema;\n }\n\n get adapter(): DatabaseAdapter<TUOWConfig> {\n return this.#adapter;\n }\n}\n\nexport function defineFragnoDatabase<const TSchema extends AnySchema>(\n options: CreateFragnoDatabaseDefinitionOptions<TSchema>,\n): FragnoDatabaseDefinition<TSchema> {\n return new FragnoDatabaseDefinition(options);\n}\n\nexport {\n defineFragmentWithDatabase,\n DatabaseFragmentBuilder,\n type FragnoPublicConfigWithDatabase,\n type DatabaseFragmentContext,\n} from \"./fragment\";\n\nexport { decodeCursor, type CursorData } from \"./query/cursor\";\n"],"mappings":";;;;AASA,MAAa,2BAA2B;AACxC,MAAa,+BAA+B;AAO5C,SAAgB,iBAAiB,OAAoD;AACnF,KAAI,iBAAiB,eACnB,QAAO;AAGT,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;AAGT,QACE,4BAA4B,SAC5B,MAAM,8BAA8B;;;;;;;AASxC,IAAa,2BAAb,MAAiE;CAC/D;CACA;CAEA,YAAY,SAAmD;AAC7D,QAAKA,YAAa,QAAQ;AAC1B,QAAKC,SAAU,QAAQ;;CAGzB,IAAI,YAAY;AACd,SAAO,MAAKD;;CAGd,IAAI,SAAS;AACX,SAAO,MAAKC;;;;;CAMd,OAA0B,SAAqE;AAC7F,SAAO,IAAI,eAAe;GACxB,WAAW,MAAKD;GAChB,QAAQ,MAAKC;GACb;GACD,CAAC;;;;;;;AAQN,IAAa,iBAAb,MAA0E;CACxE;CACA;CACA;CAEA,YAAY,SAAiF;AAC3F,QAAKD,YAAa,QAAQ;AAC1B,QAAKC,SAAU,QAAQ;AACvB,QAAKC,UAAW,QAAQ;;CAG1B,KAAK,4BAA6D;AAChE,SAAO;;CAGT,MAAM,eAAsD;EAC1D,MAAM,YAAY,MAAM,MAAKA,QAAS,iBAAiB,MAAKF,UAAW;AACvE,MAAI,cAAc,MAAKC,OAAQ,QAAQ,UAAU,CAC/C,OAAM,IAAI,MACR,2FACuB,UAAU,sBAAsB,MAAKA,OAAQ,UACrE;AAGH,SAAO,MAAKC,QAAS,kBAAkB,MAAKD,QAAS,MAAKD,UAAW;;CAGvE,MAAM,gBAAkC;AACtC,MAAI,CAAC,MAAKE,QAAS,sBACjB,OAAM,IAAI,MAAM,mDAAmD;EAIrE,MAAM,oBAAoB,MADT,MAAKA,QAAS,sBAAsB,MAAKD,QAAS,MAAKD,UAAW,CAC1C,kBAAkB;AAC3D,QAAM,kBAAkB,SAAS;AAEjC,SAAO,kBAAkB,WAAW,SAAS;;CAG/C,IAAI,YAAY;AACd,SAAO,MAAKA;;CAGd,IAAI,SAAS;AACX,SAAO,MAAKC;;CAGd,IAAI,UAAuC;AACzC,SAAO,MAAKC;;;AAIhB,SAAgB,qBACd,SACmC;AACnC,QAAO,IAAI,yBAAyB,QAAQ"}
@@ -4,71 +4,106 @@ import { SQLProvider } from "../shared/providers.js";
4
4
  //#region src/query/cursor.d.ts
5
5
 
6
6
  /**
7
- * Cursor data structure containing index values and pagination direction
7
+ * Cursor object containing all information needed for pagination
8
8
  */
9
- interface CursorData {
9
+ declare class Cursor {
10
+ #private;
11
+ constructor(data: {
12
+ indexName: string;
13
+ orderDirection: "asc" | "desc";
14
+ pageSize: number;
15
+ indexValues: Record<string, unknown>;
16
+ });
10
17
  /**
11
- * Values for each column in the index, keyed by column ORM name
18
+ * Get the index name being used for pagination
12
19
  */
13
- indexValues: Record<string, unknown>;
20
+ get indexName(): string;
21
+ /**
22
+ * Get the ordering direction
23
+ */
24
+ get orderDirection(): "asc" | "desc";
25
+ /**
26
+ * Get the page size
27
+ */
28
+ get pageSize(): number;
14
29
  /**
15
- * Direction of pagination
30
+ * Get the cursor position values
16
31
  */
17
- direction: "forward" | "backward";
32
+ get indexValues(): Record<string, unknown>;
33
+ /**
34
+ * Encode cursor to an opaque base64 string (safe to send to client)
35
+ */
36
+ encode(): string;
18
37
  }
19
38
  /**
20
- * Encode cursor data to a base64 string
21
- *
22
- * @param data - The cursor data to encode
23
- * @returns Base64-encoded cursor string
24
- *
25
- * @example
26
- * ```ts
27
- * const cursor = encodeCursor({
28
- * indexValues: { id: "abc123", createdAt: 1234567890 },
29
- * direction: "forward"
30
- * });
31
- * ```
39
+ * Result of a cursor-based query containing items and pagination cursor
32
40
  */
33
- declare function encodeCursor(data: CursorData): string;
41
+ interface CursorResult<T> {
42
+ /**
43
+ * The query results
44
+ */
45
+ items: T[];
46
+ /**
47
+ * Cursor to fetch the next page (undefined if no more results)
48
+ */
49
+ cursor?: Cursor;
50
+ }
51
+ /**
52
+ * Cursor data structure for serialization
53
+ */
54
+ interface CursorData {
55
+ v: number;
56
+ indexName: string;
57
+ orderDirection: "asc" | "desc";
58
+ pageSize: number;
59
+ indexValues: Record<string, unknown>;
60
+ }
34
61
  /**
35
- * Decode a base64 cursor string back to cursor data
62
+ * Decode a base64 cursor string back to a Cursor object
36
63
  *
37
64
  * @param cursor - The base64-encoded cursor string
38
- * @returns Decoded cursor data
65
+ * @returns Decoded Cursor object
39
66
  * @throws Error if cursor is invalid or malformed
40
67
  *
41
68
  * @example
42
69
  * ```ts
43
- * const data = decodeCursor("eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==");
70
+ * const cursor = decodeCursor("eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==");
44
71
  * ```
45
72
  */
46
- declare function decodeCursor(cursor: string): CursorData;
73
+ declare function decodeCursor(cursor: string): Cursor;
47
74
  /**
48
- * Create a cursor from a record and index columns
75
+ * Create a cursor from a record and pagination metadata
49
76
  *
50
77
  * @param record - The database record
51
78
  * @param indexColumns - The columns that make up the index
52
- * @param direction - The pagination direction
53
- * @returns Encoded cursor string
79
+ * @param metadata - Pagination metadata (index name, order direction, page size)
80
+ * @returns Cursor object
54
81
  *
55
82
  * @example
56
83
  * ```ts
57
84
  * const cursor = createCursorFromRecord(
58
85
  * { id: "abc", name: "Alice", createdAt: 123 },
59
86
  * [table.columns.createdAt, table.columns.id],
60
- * "forward"
87
+ * {
88
+ * indexName: "idx_created",
89
+ * orderDirection: "asc",
90
+ * pageSize: 10
91
+ * }
61
92
  * );
62
93
  * ```
63
94
  */
64
- declare function createCursorFromRecord(record: Record<string, unknown>, indexColumns: AnyColumn[], direction: "forward" | "backward"): string;
95
+ declare function createCursorFromRecord(record: Record<string, unknown>, indexColumns: AnyColumn[], metadata: {
96
+ indexName: string;
97
+ orderDirection: "asc" | "desc";
98
+ pageSize: number;
99
+ }): Cursor;
65
100
  /**
66
101
  * Serialize cursor values for database queries
67
102
  *
68
103
  * Converts cursor values (which are in application format) to database format
69
104
  * using the column serialization rules.
70
105
  *
71
- * @param cursorData - The decoded cursor data
106
+ * @param cursor - The cursor object
72
107
  * @param indexColumns - The columns that make up the index
73
108
  * @param provider - The SQL provider
74
109
  * @returns Serialized values ready for database queries
@@ -76,13 +111,13 @@ declare function createCursorFromRecord(record: Record<string, unknown>, indexCo
76
111
  * @example
77
112
  * ```ts
78
113
  * const serialized = serializeCursorValues(
79
- * cursorData,
114
+ * cursor,
80
115
  * [table.columns.createdAt],
81
116
  * "postgresql"
82
117
  * );
83
118
  * ```
84
119
  */
85
- declare function serializeCursorValues(cursorData: CursorData, indexColumns: AnyColumn[], provider: SQLProvider): Record<string, unknown>;
120
+ declare function serializeCursorValues(cursor: Cursor, indexColumns: AnyColumn[], provider: SQLProvider): Record<string, unknown>;
86
121
  //#endregion
87
- export { CursorData, createCursorFromRecord, decodeCursor, encodeCursor, serializeCursorValues };
122
+ export { Cursor, CursorData, CursorResult, createCursorFromRecord, decodeCursor, serializeCursorValues };
88
123
  //# sourceMappingURL=cursor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.d.ts","names":[],"sources":["../../src/query/cursor.ts"],"sourcesContent":[],"mappings":";;;;;;;AAOA;AAyBgB,UAzBC,UAAA,CAyBW;EAqBZ;AA4ChB;AAkCA;EACc,WAAA,EAzHC,MAyHD,CAAA,MAAA,EAAA,OAAA,CAAA;EACE;;;EAEP,SAAA,EAAA,SAAA,GAAA,UAAA;;;;;;;;;;;;;;;;iBAvGO,YAAA,OAAmB;;;;;;;;;;;;;iBAqBnB,YAAA,kBAA8B;;;;;;;;;;;;;;;;;;iBA4C9B,sBAAA,SACN,uCACM;;;;;;;;;;;;;;;;;;;;;iBAgCA,qBAAA,aACF,0BACE,uBACJ,cACT"}
1
+ {"version":3,"file":"cursor.d.ts","names":[],"sources":["../../src/query/cursor.ts"],"sourcesContent":[],"mappings":";;;;;;;AAOA;AAgEiB,cAhEJ,MAAA,CAgEgB;EAcZ,CAAA,OAAA;EAgCD,WAAA,CAAA,IAAY,EAAA;IA8DZ,SAAA,EAAA,MAAA;IACN,cAAA,EAAA,KAAA,GAAA,MAAA;IACM,QAAA,EAAA,MAAA;IAMb,WAAA,EA1Kc,MA0Kd,CAAA,MAAA,EAAA,OAAA,CAAA;EAAM,CAAA;EAmCO;;;EAGJ,IAAA,SAAA,CAAA,CAAA,EAAA,MAAA;EACT;;;;;;;;;;;qBAjLkB;;;;;;;;;UAsBJ;;;;SAIR;;;;WAIE;;;;;UAMM,UAAA;;;;;eAKF;;;;;;;;;;;;;;iBA2BC,YAAA,kBAA8B;;;;;;;;;;;;;;;;;;;;;;iBA8D9B,sBAAA,SACN,uCACM;;;;IAMb;;;;;;;;;;;;;;;;;;;;;iBAmCa,qBAAA,SACN,sBACM,uBACJ,cACT"}
@@ -2,34 +2,74 @@ import { serialize } from "../schema/serialize.js";
2
2
 
3
3
  //#region src/query/cursor.ts
4
4
  /**
5
- * Encode cursor data to a base64 string
6
- *
7
- * @param data - The cursor data to encode
8
- * @returns Base64-encoded cursor string
9
- *
10
- * @example
11
- * ```ts
12
- * const cursor = encodeCursor({
13
- * indexValues: { id: "abc123", createdAt: 1234567890 },
14
- * direction: "forward"
15
- * });
16
- * ```
5
+ * Cursor object containing all information needed for pagination
6
+ */
7
+ var Cursor = class {
8
+ #indexName;
9
+ #orderDirection;
10
+ #pageSize;
11
+ #indexValues;
12
+ constructor(data) {
13
+ this.#indexName = data.indexName;
14
+ this.#orderDirection = data.orderDirection;
15
+ this.#pageSize = data.pageSize;
16
+ this.#indexValues = data.indexValues;
17
+ }
18
+ /**
19
+ * Get the index name being used for pagination
20
+ */
21
+ get indexName() {
22
+ return this.#indexName;
23
+ }
24
+ /**
25
+ * Get the ordering direction
26
+ */
27
+ get orderDirection() {
28
+ return this.#orderDirection;
29
+ }
30
+ /**
31
+ * Get the page size
32
+ */
33
+ get pageSize() {
34
+ return this.#pageSize;
35
+ }
36
+ /**
37
+ * Get the cursor position values
38
+ */
39
+ get indexValues() {
40
+ return this.#indexValues;
41
+ }
42
+ /**
43
+ * Encode cursor to an opaque base64 string (safe to send to client)
44
+ */
45
+ encode() {
46
+ return encodeCursorData({
47
+ v: 1,
48
+ indexName: this.#indexName,
49
+ orderDirection: this.#orderDirection,
50
+ pageSize: this.#pageSize,
51
+ indexValues: this.#indexValues
52
+ });
53
+ }
54
+ };
55
+ /**
56
+ * Encode cursor data to a base64 string (internal)
17
57
  */
18
- function encodeCursor(data) {
58
+ function encodeCursorData(data) {
19
59
  const json = JSON.stringify(data);
20
60
  if (typeof Buffer !== "undefined") return Buffer.from(json, "utf-8").toString("base64");
21
61
  return btoa(json);
22
62
  }
23
63
  /**
24
- * Decode a base64 cursor string back to cursor data
64
+ * Decode a base64 cursor string back to a Cursor object
25
65
  *
26
66
  * @param cursor - The base64-encoded cursor string
27
- * @returns Decoded cursor data
67
+ * @returns Decoded Cursor object
28
68
  * @throws Error if cursor is invalid or malformed
29
69
  *
30
70
  * @example
31
71
  * ```ts
32
- * const data = decodeCursor("eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==");
72
+ * const cursor = decodeCursor("eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==");
33
73
  * ```
34
74
  */
35
75
  function decodeCursor(cursor) {
@@ -38,35 +78,48 @@ function decodeCursor(cursor) {
38
78
  if (typeof Buffer !== "undefined") json = Buffer.from(cursor, "base64").toString("utf-8");
39
79
  else json = atob(cursor);
40
80
  const data = JSON.parse(json);
41
- if (!data || typeof data !== "object" || !data.indexValues || typeof data.indexValues !== "object" || data.direction !== "forward" && data.direction !== "backward") throw new Error("Invalid cursor structure");
42
- return data;
81
+ if (!data || typeof data !== "object" || !data.indexValues || typeof data.indexValues !== "object" || typeof data.pageSize !== "number" || !data.indexName || !data.orderDirection || data.orderDirection !== "asc" && data.orderDirection !== "desc") throw new Error("Invalid cursor structure");
82
+ const version = typeof data.v === "number" ? data.v : 0;
83
+ if (version !== 1) throw new Error(`Unsupported cursor version: ${version}. Only v1 is supported.`);
84
+ return new Cursor({
85
+ indexName: data.indexName,
86
+ orderDirection: data.orderDirection,
87
+ pageSize: data.pageSize,
88
+ indexValues: data.indexValues
89
+ });
43
90
  } catch (error) {
44
91
  throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : "malformed data"}`);
45
92
  }
46
93
  }
47
94
  /**
48
- * Create a cursor from a record and index columns
95
+ * Create a cursor from a record and pagination metadata
49
96
  *
50
97
  * @param record - The database record
51
98
  * @param indexColumns - The columns that make up the index
52
- * @param direction - The pagination direction
53
- * @returns Encoded cursor string
99
+ * @param metadata - Pagination metadata (index name, order direction, page size)
100
+ * @returns Cursor object
54
101
  *
55
102
  * @example
56
103
  * ```ts
57
104
  * const cursor = createCursorFromRecord(
58
105
  * { id: "abc", name: "Alice", createdAt: 123 },
59
106
  * [table.columns.createdAt, table.columns.id],
60
- * "forward"
107
+ * {
108
+ * indexName: "idx_created",
109
+ * orderDirection: "asc",
110
+ * pageSize: 10
111
+ * }
61
112
  * );
62
113
  * ```
63
114
  */
64
- function createCursorFromRecord(record, indexColumns, direction) {
115
+ function createCursorFromRecord(record, indexColumns, metadata) {
65
116
  const indexValues = {};
66
117
  for (const col of indexColumns) indexValues[col.ormName] = record[col.ormName];
67
- return encodeCursor({
68
- indexValues,
69
- direction
118
+ return new Cursor({
119
+ indexName: metadata.indexName,
120
+ orderDirection: metadata.orderDirection,
121
+ pageSize: metadata.pageSize,
122
+ indexValues
70
123
  });
71
124
  }
72
125
  /**
@@ -75,7 +128,7 @@ function createCursorFromRecord(record, indexColumns, direction) {
75
128
  * Converts cursor values (which are in application format) to database format
76
129
  * using the column serialization rules.
77
130
  *
78
- * @param cursorData - The decoded cursor data
131
+ * @param cursor - The cursor object
79
132
  * @param indexColumns - The columns that make up the index
80
133
  * @param provider - The SQL provider
81
134
  * @returns Serialized values ready for database queries
@@ -83,21 +136,21 @@ function createCursorFromRecord(record, indexColumns, direction) {
83
136
  * @example
84
137
  * ```ts
85
138
  * const serialized = serializeCursorValues(
86
- * cursorData,
139
+ * cursor,
87
140
  * [table.columns.createdAt],
88
141
  * "postgresql"
89
142
  * );
90
143
  * ```
91
144
  */
92
- function serializeCursorValues(cursorData, indexColumns, provider) {
145
+ function serializeCursorValues(cursor, indexColumns, provider) {
93
146
  const serialized = {};
94
147
  for (const col of indexColumns) {
95
- const value = cursorData.indexValues[col.ormName];
148
+ const value = cursor.indexValues[col.ormName];
96
149
  if (value !== void 0) serialized[col.ormName] = serialize(value, col, provider);
97
150
  }
98
151
  return serialized;
99
152
  }
100
153
 
101
154
  //#endregion
102
- export { createCursorFromRecord, decodeCursor, encodeCursor, serializeCursorValues };
155
+ export { Cursor, createCursorFromRecord, decodeCursor, serializeCursorValues };
103
156
  //# sourceMappingURL=cursor.js.map