@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.
- package/.turbo/turbo-build.log +27 -27
- package/CHANGELOG.md +6 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +1 -1
- package/dist/adapters/drizzle/drizzle-query.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +4 -0
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +2 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +25 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/generate.js +1 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +1 -1
- package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +29 -1
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-compiler.js +2 -1
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/adapters/kysely/migration/execute-base.js +1 -1
- package/dist/mod.d.ts +2 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +2 -1
- package/dist/mod.js.map +1 -1
- package/dist/query/cursor.d.ts +67 -32
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +84 -31
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/query.d.ts +5 -0
- package/dist/query/query.d.ts.map +1 -1
- package/dist/query/unit-of-work.d.ts +14 -4
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +52 -9
- package/dist/query/unit-of-work.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +72 -5
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +6 -4
- package/src/adapters/drizzle/drizzle-query.ts +9 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +19 -3
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +3 -2
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +40 -1
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +102 -4
- package/src/adapters/kysely/kysely-query.ts +50 -1
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +19 -3
- package/src/adapters/kysely/kysely-uow-compiler.ts +3 -2
- package/src/mod.ts +6 -1
- package/src/query/cursor.test.ts +113 -68
- package/src/query/cursor.ts +127 -36
- package/src/query/query.ts +19 -0
- 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
|
|
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":"
|
|
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":"
|
|
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"}
|
package/dist/query/cursor.d.ts
CHANGED
|
@@ -4,71 +4,106 @@ import { SQLProvider } from "../shared/providers.js";
|
|
|
4
4
|
//#region src/query/cursor.d.ts
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Cursor
|
|
7
|
+
* Cursor object containing all information needed for pagination
|
|
8
8
|
*/
|
|
9
|
-
|
|
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
|
-
*
|
|
18
|
+
* Get the index name being used for pagination
|
|
12
19
|
*/
|
|
13
|
-
|
|
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
|
-
*
|
|
30
|
+
* Get the cursor position values
|
|
16
31
|
*/
|
|
17
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
70
|
+
* const cursor = decodeCursor("eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==");
|
|
44
71
|
* ```
|
|
45
72
|
*/
|
|
46
|
-
declare function decodeCursor(cursor: string):
|
|
73
|
+
declare function decodeCursor(cursor: string): Cursor;
|
|
47
74
|
/**
|
|
48
|
-
* Create a cursor from a record and
|
|
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
|
|
53
|
-
* @returns
|
|
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
|
-
*
|
|
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[],
|
|
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
|
|
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
|
-
*
|
|
114
|
+
* cursor,
|
|
80
115
|
* [table.columns.createdAt],
|
|
81
116
|
* "postgresql"
|
|
82
117
|
* );
|
|
83
118
|
* ```
|
|
84
119
|
*/
|
|
85
|
-
declare function serializeCursorValues(
|
|
120
|
+
declare function serializeCursorValues(cursor: Cursor, indexColumns: AnyColumn[], provider: SQLProvider): Record<string, unknown>;
|
|
86
121
|
//#endregion
|
|
87
|
-
export { CursorData, createCursorFromRecord, decodeCursor,
|
|
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;
|
|
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"}
|
package/dist/query/cursor.js
CHANGED
|
@@ -2,34 +2,74 @@ import { serialize } from "../schema/serialize.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/query/cursor.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
42
|
-
|
|
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
|
|
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
|
|
53
|
-
* @returns
|
|
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
|
-
*
|
|
107
|
+
* {
|
|
108
|
+
* indexName: "idx_created",
|
|
109
|
+
* orderDirection: "asc",
|
|
110
|
+
* pageSize: 10
|
|
111
|
+
* }
|
|
61
112
|
* );
|
|
62
113
|
* ```
|
|
63
114
|
*/
|
|
64
|
-
function createCursorFromRecord(record, indexColumns,
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
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
|
-
*
|
|
139
|
+
* cursor,
|
|
87
140
|
* [table.columns.createdAt],
|
|
88
141
|
* "postgresql"
|
|
89
142
|
* );
|
|
90
143
|
* ```
|
|
91
144
|
*/
|
|
92
|
-
function serializeCursorValues(
|
|
145
|
+
function serializeCursorValues(cursor, indexColumns, provider) {
|
|
93
146
|
const serialized = {};
|
|
94
147
|
for (const col of indexColumns) {
|
|
95
|
-
const value =
|
|
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,
|
|
155
|
+
export { Cursor, createCursorFromRecord, decodeCursor, serializeCursorValues };
|
|
103
156
|
//# sourceMappingURL=cursor.js.map
|