@fragno-dev/db 0.2.2 → 0.4.1
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 +404 -175
- package/CHANGELOG.md +109 -0
- package/README.md +54 -9
- package/dist/adapters/adapters.d.ts +23 -21
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/generic-sql/driver-config.d.ts +16 -1
- package/dist/adapters/generic-sql/driver-config.d.ts.map +1 -1
- package/dist/adapters/generic-sql/driver-config.js +23 -1
- package/dist/adapters/generic-sql/driver-config.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts +24 -9
- package/dist/adapters/generic-sql/generic-sql-adapter.d.ts.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-adapter.js +60 -22
- package/dist/adapters/generic-sql/generic-sql-adapter.js.map +1 -1
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js +169 -3
- package/dist/adapters/generic-sql/generic-sql-uow-executor.js.map +1 -1
- package/dist/adapters/generic-sql/migration/cold-kysely.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/mysql.js +25 -6
- package/dist/adapters/generic-sql/migration/dialect/mysql.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/postgres.js +7 -6
- package/dist/adapters/generic-sql/migration/dialect/postgres.js.map +1 -1
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js +193 -16
- package/dist/adapters/generic-sql/migration/dialect/sqlite.js.map +1 -1
- package/dist/adapters/generic-sql/migration/executor.d.ts.map +1 -1
- package/dist/adapters/generic-sql/migration/executor.js +30 -3
- package/dist/adapters/generic-sql/migration/executor.js.map +1 -1
- package/dist/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -1
- package/dist/adapters/generic-sql/migration/prepared-migrations.js +9 -9
- package/dist/adapters/generic-sql/migration/prepared-migrations.js.map +1 -1
- package/dist/adapters/generic-sql/migration/sql-generator.js +75 -52
- package/dist/adapters/generic-sql/migration/sql-generator.js.map +1 -1
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js +7 -6
- package/dist/adapters/generic-sql/query/create-sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/cursor-utils.js +42 -4
- package/dist/adapters/generic-sql/query/cursor-utils.js.map +1 -1
- package/dist/adapters/generic-sql/query/db-now-sql.js +27 -0
- package/dist/adapters/generic-sql/query/db-now-sql.js.map +1 -0
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js +32 -21
- package/dist/adapters/generic-sql/query/generic-sql-uow-operation-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/select-builder.js +5 -3
- package/dist/adapters/generic-sql/query/select-builder.js.map +1 -1
- package/dist/adapters/generic-sql/query/sql-query-compiler.js +49 -18
- package/dist/adapters/generic-sql/query/sql-query-compiler.js.map +1 -1
- package/dist/adapters/generic-sql/query/where-builder.js +43 -29
- package/dist/adapters/generic-sql/query/where-builder.js.map +1 -1
- package/dist/adapters/generic-sql/sqlite-storage.d.ts +13 -0
- package/dist/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
- package/dist/adapters/generic-sql/sqlite-storage.js +15 -0
- package/dist/adapters/generic-sql/sqlite-storage.js.map +1 -0
- package/dist/adapters/generic-sql/uow-decoder.js +6 -2
- package/dist/adapters/generic-sql/uow-decoder.js.map +1 -1
- package/dist/adapters/generic-sql/uow-encoder.js +27 -8
- package/dist/adapters/generic-sql/uow-encoder.js.map +1 -1
- package/dist/adapters/in-memory/condition-evaluator.js +135 -0
- package/dist/adapters/in-memory/condition-evaluator.js.map +1 -0
- package/dist/adapters/in-memory/errors.d.ts +13 -0
- package/dist/adapters/in-memory/errors.d.ts.map +1 -0
- package/dist/adapters/in-memory/errors.js +23 -0
- package/dist/adapters/in-memory/errors.js.map +1 -0
- package/dist/adapters/in-memory/in-memory-adapter.d.ts +27 -0
- package/dist/adapters/in-memory/in-memory-adapter.d.ts.map +1 -0
- package/dist/adapters/in-memory/in-memory-adapter.js +196 -0
- package/dist/adapters/in-memory/in-memory-adapter.js.map +1 -0
- package/dist/adapters/in-memory/in-memory-uow.js +871 -0
- package/dist/adapters/in-memory/in-memory-uow.js.map +1 -0
- package/dist/adapters/in-memory/index.d.ts +4 -0
- package/dist/adapters/in-memory/index.js +4 -0
- package/dist/adapters/in-memory/options.d.ts +30 -0
- package/dist/adapters/in-memory/options.d.ts.map +1 -0
- package/dist/adapters/in-memory/options.js +62 -0
- package/dist/adapters/in-memory/options.js.map +1 -0
- package/dist/adapters/in-memory/reference-resolution.js +26 -0
- package/dist/adapters/in-memory/reference-resolution.js.map +1 -0
- package/dist/adapters/in-memory/sorted-array-index.js +129 -0
- package/dist/adapters/in-memory/sorted-array-index.js.map +1 -0
- package/dist/adapters/in-memory/store.js +71 -0
- package/dist/adapters/in-memory/store.js.map +1 -0
- package/dist/adapters/in-memory/value-comparison.js +28 -0
- package/dist/adapters/in-memory/value-comparison.js.map +1 -0
- package/dist/adapters/shared/from-unit-of-work-compiler.js +51 -24
- package/dist/adapters/shared/from-unit-of-work-compiler.js.map +1 -1
- package/dist/adapters/shared/uow-operation-compiler.js +11 -11
- package/dist/adapters/shared/uow-operation-compiler.js.map +1 -1
- package/dist/adapters/sql/index.d.ts +5 -0
- package/dist/adapters/sql/index.js +4 -0
- package/dist/browser/adapters/adapters.d.ts +61 -0
- package/dist/browser/adapters/adapters.d.ts.map +1 -0
- package/dist/browser/adapters/generic-sql/migration/executor.d.ts +15 -0
- package/dist/browser/adapters/generic-sql/migration/executor.d.ts.map +1 -0
- package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts +66 -0
- package/dist/browser/adapters/generic-sql/migration/prepared-migrations.d.ts.map +1 -0
- package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts +11 -0
- package/dist/browser/adapters/generic-sql/sqlite-storage.d.ts.map +1 -0
- package/dist/browser/adapters/in-memory/in-memory-adapter.d.ts +5 -0
- package/dist/browser/adapters/in-memory/index.d.ts +2 -0
- package/dist/browser/adapters/in-memory/options.d.ts +1 -0
- package/dist/browser/db-fragment-definition-builder.d.ts +237 -0
- package/dist/browser/db-fragment-definition-builder.d.ts.map +1 -0
- package/dist/browser/durable-hooks.d.ts +3 -0
- package/dist/browser/fragments/internal-fragment.d.ts +317 -0
- package/dist/browser/fragments/internal-fragment.d.ts.map +1 -0
- package/dist/browser/fragments/internal-fragment.schema.d.ts +1 -0
- package/dist/browser/hooks/durable-hooks-logger.d.ts +10 -0
- package/dist/browser/hooks/durable-hooks-logger.d.ts.map +1 -0
- package/dist/browser/hooks/hooks.d.ts +146 -0
- package/dist/browser/hooks/hooks.d.ts.map +1 -0
- package/dist/browser/id.js +1 -0
- package/dist/browser/internal/adapter-registry.d.ts +4 -0
- package/dist/browser/internal/outbox-state.d.ts +2 -0
- package/dist/browser/mod.d.ts +15 -0
- package/dist/browser/mod.d.ts.map +1 -0
- package/dist/browser/mod.js +17 -0
- package/dist/browser/mod.js.map +1 -0
- package/dist/browser/mod2.d.ts +48 -0
- package/dist/browser/mod2.d.ts.map +1 -0
- package/dist/browser/naming/sql-naming.d.ts +19 -0
- package/dist/browser/naming/sql-naming.d.ts.map +1 -0
- package/dist/browser/outbox/outbox.d.ts +21 -0
- package/dist/browser/outbox/outbox.d.ts.map +1 -0
- package/dist/browser/query/column-defaults.js +1 -0
- package/dist/browser/query/condition-builder.d.ts +44 -0
- package/dist/browser/query/condition-builder.d.ts.map +1 -0
- package/dist/browser/query/condition-builder.js +97 -0
- package/dist/browser/query/condition-builder.js.map +1 -0
- package/dist/browser/query/cursor.d.ts +105 -0
- package/dist/browser/query/cursor.d.ts.map +1 -0
- package/dist/browser/query/cursor.js +150 -0
- package/dist/browser/query/cursor.js.map +1 -0
- package/dist/browser/query/db-now.d.ts +22 -0
- package/dist/browser/query/db-now.d.ts.map +1 -0
- package/dist/browser/query/db-now.js +33 -0
- package/dist/browser/query/db-now.js.map +1 -0
- package/dist/browser/query/orm/orm.d.ts +18 -0
- package/dist/browser/query/orm/orm.d.ts.map +1 -0
- package/dist/browser/query/simple-query-interface.d.ts +108 -0
- package/dist/browser/query/simple-query-interface.d.ts.map +1 -0
- package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts +423 -0
- package/dist/browser/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -0
- package/dist/browser/query/unit-of-work/execute-unit-of-work.js +507 -0
- package/dist/browser/query/unit-of-work/execute-unit-of-work.js.map +1 -0
- package/dist/browser/query/unit-of-work/retry-policy.d.ts +23 -0
- package/dist/browser/query/unit-of-work/retry-policy.d.ts.map +1 -0
- package/dist/browser/query/unit-of-work/retry-policy.js +40 -0
- package/dist/browser/query/unit-of-work/retry-policy.js.map +1 -0
- package/dist/browser/query/unit-of-work/unit-of-work.d.ts +703 -0
- package/dist/browser/query/unit-of-work/unit-of-work.d.ts.map +1 -0
- package/dist/browser/query/unit-of-work/unit-of-work.js +1206 -0
- package/dist/browser/query/unit-of-work/unit-of-work.js.map +1 -0
- package/dist/browser/query/value-encoding.js +38 -0
- package/dist/browser/query/value-encoding.js.map +1 -0
- package/dist/browser/schema/create.d.ts +326 -0
- package/dist/browser/schema/create.d.ts.map +1 -0
- package/dist/browser/schema/create.js +89 -0
- package/dist/browser/schema/create.js.map +1 -0
- package/dist/browser/schema/generate-id.js +28 -0
- package/dist/browser/schema/generate-id.js.map +1 -0
- package/dist/browser/shared/providers.d.ts +6 -0
- package/dist/browser/shared/providers.d.ts.map +1 -0
- package/dist/browser/sql-driver/connection/connection-provider.d.ts +13 -0
- package/dist/browser/sql-driver/connection/connection-provider.d.ts.map +1 -0
- package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts +7 -0
- package/dist/browser/sql-driver/dialect-adapter/dialect-adapter.d.ts.map +1 -0
- package/dist/browser/sql-driver/driver/runtime-driver.d.ts +23 -0
- package/dist/browser/sql-driver/driver/runtime-driver.d.ts.map +1 -0
- package/dist/browser/sql-driver/query-executor/plugin.d.ts +17 -0
- package/dist/browser/sql-driver/query-executor/plugin.d.ts.map +1 -0
- package/dist/browser/sql-driver/query-executor/query-executor.d.ts +36 -0
- package/dist/browser/sql-driver/query-executor/query-executor.d.ts.map +1 -0
- package/dist/browser/sql-driver/sql-driver-adapter.d.ts +29 -0
- package/dist/browser/sql-driver/sql-driver-adapter.d.ts.map +1 -0
- package/dist/browser/sql-driver/sql-driver.d.ts +38 -0
- package/dist/browser/sql-driver/sql-driver.d.ts.map +1 -0
- package/dist/browser/sync/commands.d.ts +15 -0
- package/dist/browser/sync/commands.d.ts.map +1 -0
- package/dist/browser/sync/commands.js +27 -0
- package/dist/browser/sync/commands.js.map +1 -0
- package/dist/browser/sync/types.d.ts +63 -0
- package/dist/browser/sync/types.d.ts.map +1 -0
- package/dist/browser/util/types.d.ts +8 -0
- package/dist/browser/util/types.d.ts.map +1 -0
- package/dist/browser/with-database.d.ts +29 -0
- package/dist/browser/with-database.d.ts.map +1 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +5 -0
- package/dist/db-fragment-definition-builder.d.ts +101 -33
- package/dist/db-fragment-definition-builder.d.ts.map +1 -1
- package/dist/db-fragment-definition-builder.js +450 -60
- package/dist/db-fragment-definition-builder.js.map +1 -1
- package/dist/dispatchers/cloudflare-do/dispatcher.d.ts +20 -0
- package/dist/dispatchers/cloudflare-do/dispatcher.d.ts.map +1 -0
- package/dist/dispatchers/cloudflare-do/dispatcher.js +147 -0
- package/dist/dispatchers/cloudflare-do/dispatcher.js.map +1 -0
- package/dist/dispatchers/cloudflare-do/index.d.ts +11 -0
- package/dist/dispatchers/cloudflare-do/index.d.ts.map +1 -0
- package/dist/dispatchers/cloudflare-do/index.js +31 -0
- package/dist/dispatchers/cloudflare-do/index.js.map +1 -0
- package/dist/dispatchers/node/dispatcher.d.ts +14 -0
- package/dist/dispatchers/node/dispatcher.d.ts.map +1 -0
- package/dist/dispatchers/node/dispatcher.js +80 -0
- package/dist/dispatchers/node/dispatcher.js.map +1 -0
- package/dist/dispatchers/node/index.d.ts +12 -0
- package/dist/dispatchers/node/index.d.ts.map +1 -0
- package/dist/dispatchers/node/index.js +27 -0
- package/dist/dispatchers/node/index.js.map +1 -0
- package/dist/durable-hooks.d.ts +31 -0
- package/dist/durable-hooks.d.ts.map +1 -0
- package/dist/durable-hooks.js +23 -0
- package/dist/durable-hooks.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +186 -8
- package/dist/fragments/internal-fragment.d.ts.map +1 -1
- package/dist/fragments/internal-fragment.js +203 -38
- package/dist/fragments/internal-fragment.js.map +1 -1
- package/dist/fragments/internal-fragment.routes.js +164 -0
- package/dist/fragments/internal-fragment.routes.js.map +1 -0
- package/dist/fragments/internal-fragment.schema.d.ts +15 -0
- package/dist/fragments/internal-fragment.schema.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.schema.js +39 -0
- package/dist/fragments/internal-fragment.schema.js.map +1 -0
- package/dist/hooks/durable-hooks-logger.d.ts +10 -0
- package/dist/hooks/durable-hooks-logger.d.ts.map +1 -0
- package/dist/hooks/durable-hooks-logger.js +75 -0
- package/dist/hooks/durable-hooks-logger.js.map +1 -0
- package/dist/hooks/durable-hooks-processor.d.ts +1 -0
- package/dist/hooks/durable-hooks-processor.js +80 -0
- package/dist/hooks/durable-hooks-processor.js.map +1 -0
- package/dist/hooks/durable-hooks-runtime.js +44 -0
- package/dist/hooks/durable-hooks-runtime.js.map +1 -0
- package/dist/hooks/hooks.d.ts +100 -1
- package/dist/hooks/hooks.d.ts.map +1 -1
- package/dist/hooks/hooks.js +254 -27
- package/dist/hooks/hooks.js.map +1 -1
- package/dist/id.d.ts +2 -2
- package/dist/id.js +2 -2
- package/dist/internal/adapter-registry.d.ts +11 -0
- package/dist/internal/adapter-registry.d.ts.map +1 -0
- package/dist/internal/adapter-registry.js +135 -0
- package/dist/internal/adapter-registry.js.map +1 -0
- package/dist/internal/outbox-state.d.ts +2 -0
- package/dist/internal/outbox-state.js +26 -0
- package/dist/internal/outbox-state.js.map +1 -0
- package/dist/migration-engine/auto-from-schema.d.ts +33 -0
- package/dist/migration-engine/auto-from-schema.d.ts.map +1 -0
- package/dist/migration-engine/auto-from-schema.js +223 -37
- package/dist/migration-engine/auto-from-schema.js.map +1 -1
- package/dist/migration-engine/generation-engine.d.ts +16 -10
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +86 -35
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/migration-engine/shared.d.ts +113 -0
- package/dist/migration-engine/shared.d.ts.map +1 -0
- package/dist/migration-engine/shared.js.map +1 -1
- package/dist/mod.d.ts +20 -12
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +18 -12
- package/dist/mod.js.map +1 -1
- package/dist/naming/sql-naming.d.ts +19 -0
- package/dist/naming/sql-naming.d.ts.map +1 -0
- package/dist/naming/sql-naming.js +116 -0
- package/dist/naming/sql-naming.js.map +1 -0
- package/dist/outbox/outbox-builder.js +156 -0
- package/dist/outbox/outbox-builder.js.map +1 -0
- package/dist/outbox/outbox.d.ts +54 -0
- package/dist/outbox/outbox.d.ts.map +1 -0
- package/dist/outbox/outbox.js +37 -0
- package/dist/outbox/outbox.js.map +1 -0
- package/dist/query/column-defaults.js +20 -4
- package/dist/query/column-defaults.js.map +1 -1
- package/dist/query/condition-builder.d.ts +7 -1
- package/dist/query/condition-builder.d.ts.map +1 -1
- package/dist/query/condition-builder.js +5 -1
- package/dist/query/condition-builder.js.map +1 -1
- package/dist/query/cursor-client.d.ts +105 -0
- package/dist/query/cursor-client.d.ts.map +1 -0
- package/dist/query/cursor-client.js +165 -0
- package/dist/query/cursor-client.js.map +1 -0
- package/dist/query/cursor.d.ts +3 -1
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +51 -14
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/db-now.d.ts +22 -0
- package/dist/query/db-now.d.ts.map +1 -0
- package/dist/query/db-now.js +35 -0
- package/dist/query/db-now.js.map +1 -0
- package/dist/query/orm/orm.js.map +1 -1
- package/dist/query/serialize/create-sql-serializer.js +5 -4
- package/dist/query/serialize/create-sql-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/mysql-serializer.js +12 -6
- package/dist/query/serialize/dialect/mysql-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/postgres-serializer.js +25 -7
- package/dist/query/serialize/dialect/postgres-serializer.js.map +1 -1
- package/dist/query/serialize/dialect/sqlite-serializer.js +60 -12
- package/dist/query/serialize/dialect/sqlite-serializer.js.map +1 -1
- package/dist/query/serialize/sql-serializer.js +2 -2
- package/dist/query/serialize/sql-serializer.js.map +1 -1
- package/dist/query/simple-query-interface.d.ts +13 -4
- package/dist/query/simple-query-interface.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts +37 -2
- package/dist/query/unit-of-work/execute-unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/execute-unit-of-work.js +50 -24
- package/dist/query/unit-of-work/execute-unit-of-work.js.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.d.ts +92 -30
- package/dist/query/unit-of-work/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work/unit-of-work.js +136 -11
- package/dist/query/unit-of-work/unit-of-work.js.map +1 -1
- package/dist/query/value-decoding.js +16 -6
- package/dist/query/value-decoding.js.map +1 -1
- package/dist/query/value-encoding.js +29 -9
- package/dist/query/value-encoding.js.map +1 -1
- package/dist/schema/create.d.ts +103 -35
- package/dist/schema/create.d.ts.map +1 -1
- package/dist/schema/create.js +172 -58
- package/dist/schema/create.js.map +1 -1
- package/dist/schema/generate-id.js +2 -2
- package/dist/schema/generate-id.js.map +1 -1
- package/dist/schema/type-conversion/create-sql-type-mapper.js +4 -3
- package/dist/schema/type-conversion/create-sql-type-mapper.js.map +1 -1
- package/dist/schema/type-conversion/dialect/sqlite.js +9 -0
- package/dist/schema/type-conversion/dialect/sqlite.js.map +1 -1
- package/dist/schema/validator.d.ts +10 -0
- package/dist/schema/validator.d.ts.map +1 -0
- package/dist/schema/validator.js +123 -0
- package/dist/schema/validator.js.map +1 -0
- package/dist/schema-output/drizzle.d.ts +30 -0
- package/dist/schema-output/drizzle.d.ts.map +1 -0
- package/dist/{adapters/drizzle/generate.js → schema-output/drizzle.js} +88 -60
- package/dist/schema-output/drizzle.js.map +1 -0
- package/dist/schema-output/prisma.d.ts +17 -0
- package/dist/schema-output/prisma.d.ts.map +1 -0
- package/dist/schema-output/prisma.js +307 -0
- package/dist/schema-output/prisma.js.map +1 -0
- package/dist/sql-driver/dialects/durable-object-dialect.js +3 -9
- package/dist/sql-driver/dialects/durable-object-dialect.js.map +1 -1
- package/dist/sql-driver/query-executor/default-query-executor.js.map +1 -1
- package/dist/sql-driver/query-executor/query-executor-base.js.map +1 -1
- package/dist/sql-driver/sql-driver-adapter.js.map +1 -1
- package/dist/sql-driver/sql.js.map +1 -1
- package/dist/sync/commands.d.ts +15 -0
- package/dist/sync/commands.d.ts.map +1 -0
- package/dist/sync/commands.js +27 -0
- package/dist/sync/commands.js.map +1 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.js +4 -0
- package/dist/sync/read-tracking.d.ts +25 -0
- package/dist/sync/read-tracking.d.ts.map +1 -0
- package/dist/sync/read-tracking.js +148 -0
- package/dist/sync/read-tracking.js.map +1 -0
- package/dist/sync/submit.js +213 -0
- package/dist/sync/submit.js.map +1 -0
- package/dist/sync/types.d.ts +63 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/util/default-database-adapter.js +66 -0
- package/dist/util/default-database-adapter.js.map +1 -0
- package/dist/with-database.d.ts +3 -6
- package/dist/with-database.d.ts.map +1 -1
- package/dist/with-database.js +8 -7
- package/dist/with-database.js.map +1 -1
- package/package.json +62 -55
- package/src/adapters/adapters.ts +33 -26
- package/src/adapters/drizzle/migrate-drizzle.test.ts +99 -41
- package/src/adapters/drizzle/migration-parity-drizzle-kit.test.ts +601 -0
- package/src/adapters/drizzle/test-utils.ts +13 -8
- package/src/adapters/generic-sql/driver-config.ts +38 -0
- package/src/adapters/generic-sql/generic-sql-adapter.test.ts +10 -8
- package/src/adapters/generic-sql/generic-sql-adapter.ts +117 -34
- package/src/adapters/generic-sql/generic-sql-uow-executor.test.ts +55 -0
- package/src/adapters/generic-sql/generic-sql-uow-executor.ts +297 -3
- package/src/adapters/generic-sql/migration/adapter-migration-parity.test.ts +120 -0
- package/src/adapters/generic-sql/migration/cold-kysely.ts +1 -0
- package/src/adapters/generic-sql/migration/dialect/mysql.test.ts +27 -8
- package/src/adapters/generic-sql/migration/dialect/mysql.ts +47 -8
- package/src/adapters/generic-sql/migration/dialect/postgres.test.ts +28 -9
- package/src/adapters/generic-sql/migration/dialect/postgres.ts +9 -4
- package/src/adapters/generic-sql/migration/dialect/sqlite.test.ts +839 -8
- package/src/adapters/generic-sql/migration/dialect/sqlite.ts +396 -53
- package/src/adapters/generic-sql/migration/executor.test.ts +52 -0
- package/src/adapters/generic-sql/migration/executor.ts +47 -4
- package/src/adapters/generic-sql/migration/prepared-migrations.test.ts +238 -46
- package/src/adapters/generic-sql/migration/prepared-migrations.ts +21 -13
- package/src/adapters/generic-sql/migration/sql-generator.ts +145 -66
- package/src/adapters/generic-sql/query/create-sql-query-compiler.ts +11 -8
- package/src/adapters/generic-sql/query/cursor-utils.test.ts +272 -0
- package/src/adapters/generic-sql/query/cursor-utils.ts +42 -7
- package/src/adapters/generic-sql/query/db-now-sql.ts +49 -0
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.test.ts +171 -35
- package/src/adapters/generic-sql/query/generic-sql-uow-operation-compiler.ts +53 -40
- package/src/adapters/generic-sql/query/select-builder.test.ts +16 -11
- package/src/adapters/generic-sql/query/select-builder.ts +7 -3
- package/src/adapters/generic-sql/query/sql-query-compiler.test.ts +75 -6
- package/src/adapters/generic-sql/query/sql-query-compiler.ts +129 -24
- package/src/adapters/generic-sql/query/where-builder.test.ts +96 -20
- package/src/adapters/generic-sql/query/where-builder.ts +112 -41
- package/src/adapters/{kysely/kysely-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-migrations.test.ts} +11 -20
- package/src/adapters/generic-sql/sql-adapter-pglite-pagination.test.ts +851 -0
- package/src/adapters/{drizzle/drizzle-adapter-pglite.test.ts → generic-sql/sql-adapter-pglite-queries.test.ts} +18 -15
- package/src/adapters/generic-sql/{test/generic-drizzle-adapter-sqlite3.test.ts → sql-adapter-sqlite3-driver.test.ts} +282 -14
- package/src/adapters/{drizzle/drizzle-adapter-sqlite3.test.ts → generic-sql/sql-adapter-sqlite3-uow.test.ts} +129 -12
- package/src/adapters/{kysely/kysely-adapter-sqlocal.test.ts → generic-sql/sql-adapter-sqlocal.test.ts} +9 -7
- package/src/adapters/generic-sql/sqlite-storage.ts +20 -0
- package/src/adapters/generic-sql/uow-decoder.test.ts +5 -4
- package/src/adapters/generic-sql/uow-decoder.ts +23 -5
- package/src/adapters/generic-sql/uow-encoder.test.ts +36 -3
- package/src/adapters/generic-sql/uow-encoder.ts +48 -13
- package/src/adapters/in-memory/condition-evaluator.test.ts +194 -0
- package/src/adapters/in-memory/condition-evaluator.ts +280 -0
- package/src/adapters/in-memory/errors.ts +20 -0
- package/src/adapters/in-memory/in-memory-adapter.ts +388 -0
- package/src/adapters/in-memory/in-memory-uow.mutations.test.ts +344 -0
- package/src/adapters/in-memory/in-memory-uow.retrieval.test.ts +255 -0
- package/src/adapters/in-memory/in-memory-uow.ts +1724 -0
- package/src/adapters/in-memory/index.ts +3 -0
- package/src/adapters/in-memory/options.test.ts +42 -0
- package/src/adapters/in-memory/options.ts +91 -0
- package/src/adapters/in-memory/outbox.test.ts +361 -0
- package/src/adapters/in-memory/reference-resolution.test.ts +51 -0
- package/src/adapters/in-memory/reference-resolution.ts +67 -0
- package/src/adapters/in-memory/sorted-array-index.test.ts +124 -0
- package/src/adapters/in-memory/sorted-array-index.ts +228 -0
- package/src/adapters/in-memory/store.test.ts +69 -0
- package/src/adapters/in-memory/store.ts +145 -0
- package/src/adapters/in-memory/value-comparison.ts +53 -0
- package/src/adapters/in-memory/value-normalization.test.ts +58 -0
- package/src/adapters/prisma/prisma-adapter-sqlite3.test.ts +1207 -0
- package/src/adapters/shared/from-unit-of-work-compiler.ts +159 -47
- package/src/adapters/shared/uow-operation-compiler.ts +28 -18
- package/src/adapters/sql/index.ts +12 -0
- package/src/browser/mod.ts +64 -0
- package/src/client.ts +19 -0
- package/src/db-fragment-definition-builder.test.ts +845 -53
- package/src/db-fragment-definition-builder.ts +911 -95
- package/src/db-fragment-instantiator.test.ts +210 -94
- package/src/db-fragment-integration.test.ts +17 -12
- package/src/dispatchers/cloudflare-do/dispatcher.ts +204 -0
- package/src/dispatchers/cloudflare-do/index.test.ts +206 -0
- package/src/dispatchers/cloudflare-do/index.ts +63 -0
- package/src/dispatchers/node/dispatcher.ts +112 -0
- package/src/dispatchers/node/index.test.ts +120 -0
- package/src/dispatchers/node/index.ts +50 -0
- package/src/durable-hooks.test.ts +80 -0
- package/src/durable-hooks.ts +67 -0
- package/src/fragments/internal-fragment.routes.test.ts +570 -0
- package/src/fragments/internal-fragment.routes.ts +334 -0
- package/src/fragments/internal-fragment.schema.ts +95 -0
- package/src/fragments/internal-fragment.test.ts +505 -83
- package/src/fragments/internal-fragment.ts +453 -70
- package/src/hooks/durable-hooks-logger.ts +126 -0
- package/src/hooks/durable-hooks-processor.pglite.test.ts +87 -0
- package/src/hooks/durable-hooks-processor.test.ts +282 -0
- package/src/hooks/durable-hooks-processor.ts +173 -0
- package/src/hooks/durable-hooks-runtime.test.ts +65 -0
- package/src/hooks/durable-hooks-runtime.ts +81 -0
- package/src/hooks/hooks.test.ts +455 -34
- package/src/hooks/hooks.ts +501 -34
- package/src/id.test.ts +34 -0
- package/src/id.ts +1 -3
- package/src/internal/adapter-registry.test.ts +93 -0
- package/src/internal/adapter-registry.ts +239 -0
- package/src/internal/outbox-state.ts +43 -0
- package/src/migration-engine/auto-from-schema.test.ts +107 -14
- package/src/migration-engine/auto-from-schema.ts +365 -44
- package/src/migration-engine/create.test.ts +4 -3
- package/src/migration-engine/create.ts +1 -1
- package/src/migration-engine/generation-engine.test.ts +292 -110
- package/src/migration-engine/generation-engine.ts +117 -66
- package/src/migration-engine/shared.ts +14 -0
- package/src/mod.ts +95 -39
- package/src/naming/sql-naming.ts +181 -0
- package/src/outbox/outbox-builder.ts +241 -0
- package/src/outbox/outbox.test.ts +424 -0
- package/src/outbox/outbox.ts +139 -0
- package/src/query/column-defaults.ts +42 -4
- package/src/query/condition-builder.test.ts +18 -3
- package/src/query/condition-builder.ts +7 -0
- package/src/query/cursor-client.test.ts +70 -0
- package/src/query/cursor-client.ts +263 -0
- package/src/query/cursor.test.ts +119 -20
- package/src/query/cursor.ts +88 -27
- package/src/query/db-now.ts +73 -0
- package/src/query/orm/orm.ts +2 -2
- package/src/query/query-type.test.ts +4 -3
- package/src/query/serialize/create-sql-serializer.ts +10 -5
- package/src/query/serialize/dialect/mysql-serializer.ts +13 -5
- package/src/query/serialize/dialect/postgres-serializer.ts +35 -5
- package/src/query/serialize/dialect/sqlite-serializer.test.ts +90 -3
- package/src/query/serialize/dialect/sqlite-serializer.ts +108 -12
- package/src/query/serialize/sql-serializer.ts +4 -4
- package/src/query/simple-query-interface.ts +15 -4
- package/src/query/unit-of-work/execute-unit-of-work.test.ts +372 -10
- package/src/query/unit-of-work/execute-unit-of-work.ts +87 -27
- package/src/query/unit-of-work/retry-policy.test.ts +1 -0
- package/src/query/unit-of-work/tx-builder.test.ts +73 -1
- package/src/query/unit-of-work/unit-of-work-coordinator.test.ts +17 -16
- package/src/query/unit-of-work/unit-of-work-types.test.ts +42 -12
- package/src/query/unit-of-work/unit-of-work.test.ts +196 -39
- package/src/query/unit-of-work/unit-of-work.ts +309 -38
- package/src/query/value-decoding.test.ts +63 -4
- package/src/query/value-decoding.ts +32 -6
- package/src/query/value-encoding.test.ts +86 -2
- package/src/query/value-encoding.ts +56 -6
- package/src/schema/create.test.ts +293 -47
- package/src/schema/create.ts +406 -70
- package/src/schema/generate-id.test.ts +3 -2
- package/src/schema/generate-id.ts +2 -2
- package/src/schema/serialize.test.ts +18 -5
- package/src/schema/type-conversion/create-sql-type-mapper.ts +8 -3
- package/src/schema/type-conversion/dialect/sqlite.ts +18 -0
- package/src/schema/type-conversion/type-mapping.test.ts +26 -1
- package/src/schema/validator.test.ts +199 -0
- package/src/schema/validator.ts +232 -0
- package/src/{adapters/drizzle/generate.test.ts → schema-output/drizzle.test.ts} +232 -129
- package/src/{adapters/drizzle/generate.ts → schema-output/drizzle.ts} +155 -99
- package/src/schema-output/prisma.test.ts +694 -0
- package/src/schema-output/prisma.ts +593 -0
- package/src/sql-driver/better-sqlite3.test.ts +5 -3
- package/src/sql-driver/dialects/durable-object-dialect.ts +3 -8
- package/src/sql-driver/query-executor/default-query-executor.ts +1 -1
- package/src/sql-driver/query-executor/query-executor-base.ts +1 -1
- package/src/sql-driver/query-executor/query-executor.ts +1 -1
- package/src/sql-driver/sql-driver-adapter.ts +2 -2
- package/src/sql-driver/sql.ts +2 -1
- package/src/sql-driver/sqlocal.test.ts +4 -2
- package/src/sync/commands.test.ts +39 -0
- package/src/sync/commands.ts +51 -0
- package/src/sync/conflict-checker.test.ts +450 -0
- package/src/sync/conflict-checker.ts +248 -0
- package/src/sync/index.ts +14 -0
- package/src/sync/plan.ts +9 -0
- package/src/sync/read-tracking.test.ts +177 -0
- package/src/sync/read-tracking.ts +287 -0
- package/src/sync/submit.test.ts +205 -0
- package/src/sync/submit.ts +328 -0
- package/src/sync/types.ts +80 -0
- package/src/util/default-database-adapter.ts +119 -0
- package/src/with-database.ts +20 -31
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +38 -24
- package/vitest.config.ts +1 -0
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +0 -20
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +0 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +0 -27
- package/dist/adapters/drizzle/drizzle-adapter.js.map +0 -1
- package/dist/adapters/drizzle/generate.d.ts +0 -30
- package/dist/adapters/drizzle/generate.d.ts.map +0 -1
- package/dist/adapters/drizzle/generate.js.map +0 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +0 -19
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +0 -1
- package/dist/adapters/kysely/kysely-adapter.js +0 -17
- package/dist/adapters/kysely/kysely-adapter.js.map +0 -1
- package/dist/adapters/shared/table-name-mapper.d.ts +0 -12
- package/dist/adapters/shared/table-name-mapper.d.ts.map +0 -1
- package/dist/adapters/shared/table-name-mapper.js +0 -43
- package/dist/adapters/shared/table-name-mapper.js.map +0 -1
- package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js +0 -165
- package/dist/node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.js.map +0 -1
- package/dist/packages/fragno/dist/api/bind-services.js +0 -20
- package/dist/packages/fragno/dist/api/bind-services.js.map +0 -1
- package/dist/packages/fragno/dist/api/error.js +0 -48
- package/dist/packages/fragno/dist/api/error.js.map +0 -1
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +0 -320
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +0 -1
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +0 -525
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +0 -1
- package/dist/packages/fragno/dist/api/fragno-response.js +0 -73
- package/dist/packages/fragno/dist/api/fragno-response.js.map +0 -1
- package/dist/packages/fragno/dist/api/internal/response-stream.js +0 -81
- package/dist/packages/fragno/dist/api/internal/response-stream.js.map +0 -1
- package/dist/packages/fragno/dist/api/internal/route.js +0 -10
- package/dist/packages/fragno/dist/api/internal/route.js.map +0 -1
- package/dist/packages/fragno/dist/api/mutable-request-state.js +0 -97
- package/dist/packages/fragno/dist/api/mutable-request-state.js.map +0 -1
- package/dist/packages/fragno/dist/api/request-context-storage.js +0 -43
- package/dist/packages/fragno/dist/api/request-context-storage.js.map +0 -1
- package/dist/packages/fragno/dist/api/request-input-context.js +0 -118
- package/dist/packages/fragno/dist/api/request-input-context.js.map +0 -1
- package/dist/packages/fragno/dist/api/request-middleware.js +0 -83
- package/dist/packages/fragno/dist/api/request-middleware.js.map +0 -1
- package/dist/packages/fragno/dist/api/request-output-context.js +0 -119
- package/dist/packages/fragno/dist/api/request-output-context.js.map +0 -1
- package/dist/packages/fragno/dist/api/route.js +0 -17
- package/dist/packages/fragno/dist/api/route.js.map +0 -1
- package/dist/packages/fragno/dist/internal/symbols.js +0 -10
- package/dist/packages/fragno/dist/internal/symbols.js.map +0 -1
- package/dist/schema-generator/schema-generator.d.ts +0 -15
- package/dist/schema-generator/schema-generator.d.ts.map +0 -1
- package/src/adapters/drizzle/drizzle-adapter.ts +0 -39
- package/src/adapters/kysely/kysely-adapter.ts +0 -27
- package/src/adapters/shared/table-name-mapper.ts +0 -50
- package/src/schema-generator/schema-generator.ts +0 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"condition-builder.js","names":["builder: ConditionBuilder<Columns>"],"sources":["../../src/query/condition-builder.ts"],"sourcesContent":["import type { AnyColumn, FragnoId, IdColumn } from \"../schema/create\";\n\nexport type ConditionType = \"compare\" | \"and\" | \"or\" | \"not\";\n\nexport type Condition =\n | {\n type: \"compare\";\n a: AnyColumn;\n operator: Operator;\n b: AnyColumn | unknown | null;\n }\n | {\n type: \"or\" | \"and\";\n items: Condition[];\n }\n | {\n type: \"not\";\n item: Condition;\n };\n\n// TODO: we temporarily dropped support for comparing against another column, because Prisma ORM still have problems with it.\n\n/**\n * Helper type that allows FragnoId for ID columns and reference columns (bigint).\n * Used in ConditionBuilder to accept FragnoId values in where conditions.\n */\ntype AcceptsFragnoId<T extends AnyColumn> = T extends IdColumn\n ? T[\"$in\"] | FragnoId\n : T[\"$in\"] extends bigint\n ? T[\"$in\"] | FragnoId\n : T[\"$in\"];\n\nexport type ConditionBuilder<Columns extends Record<string, AnyColumn>> = {\n <ColName extends keyof Columns>(\n a: ColName,\n operator: (typeof valueOperators)[number] | (typeof stringOperators)[number],\n b: AcceptsFragnoId<Columns[ColName]> | null,\n ): Condition;\n\n <ColName extends keyof Columns>(\n a: ColName,\n operator: (typeof arrayOperators)[number],\n b: AcceptsFragnoId<Columns[ColName]>[],\n ): Condition;\n\n /**\n * Boolean values\n */\n <ColName extends keyof Columns>(a: ColName): Condition;\n\n and: (...v: (Condition | boolean)[]) => Condition | boolean;\n or: (...v: (Condition | boolean)[]) => Condition | boolean;\n not: (v: Condition | boolean) => Condition | boolean;\n\n isNull: (a: keyof Columns) => Condition;\n isNotNull: (a: keyof Columns) => Condition;\n};\n\n// replacement for `like` (Prisma doesn't support `like`)\nconst stringOperators = [\n \"contains\",\n \"starts with\",\n \"ends with\",\n\n \"not contains\",\n \"not starts with\",\n \"not ends with\",\n // excluded `regexp` since MSSQL doesn't support it, may re-consider\n] as const;\n\nconst arrayOperators = [\"in\", \"not in\"] as const;\n\nconst valueOperators = [\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"is\", \"is not\"] as const;\n\n// JSON specific operators are not included, some databases don't support them\n// `match` requires additional extensions & configurations on SQLite and PostgreSQL\n// MySQL & SQLite requires workarounds to support `ilike`\nexport const operators = [...valueOperators, ...arrayOperators, ...stringOperators] as const;\n\nexport type Operator = (typeof operators)[number];\n\nexport function createBuilder<Columns extends Record<string, AnyColumn>>(\n columns: Columns,\n): ConditionBuilder<Columns> {\n function col(name: keyof Columns) {\n const out = columns[name];\n if (!out) {\n throw new Error(`Invalid column name ${String(name)}`);\n }\n\n return out;\n }\n\n const builder: ConditionBuilder<Columns> = (...args: [string, Operator, unknown] | [string]) => {\n if (args.length === 3) {\n const [a, operator, b] = args;\n\n if (!operators.includes(operator)) {\n throw new Error(`Unsupported operator: ${operator}`);\n }\n\n return {\n type: \"compare\",\n a: col(a),\n b,\n operator,\n };\n }\n\n return {\n type: \"compare\",\n a: col(args[0]),\n operator: \"=\",\n b: true,\n };\n };\n\n builder.isNull = (a) => builder(a, \"is\", null);\n builder.isNotNull = (a) => builder(a, \"is not\", null);\n builder.not = (condition) => {\n if (typeof condition === \"boolean\") {\n return !condition;\n }\n\n return {\n type: \"not\",\n item: condition,\n };\n };\n\n builder.or = (...conditions) => {\n const out = {\n type: \"or\",\n items: [] as Condition[],\n } as const;\n\n for (const item of conditions) {\n if (item === true) {\n return true;\n }\n if (item === false) {\n continue;\n }\n\n out.items.push(item);\n }\n\n if (out.items.length === 0) {\n return false;\n }\n return out;\n };\n\n builder.and = (...conditions) => {\n const out = {\n type: \"and\",\n items: [] as Condition[],\n } as const;\n\n for (const item of conditions) {\n if (item === true) {\n continue;\n }\n if (item === false) {\n return false;\n }\n\n out.items.push(item);\n }\n\n if (out.items.length === 0) {\n return true;\n }\n return out;\n };\n\n return builder;\n}\n\nexport function buildCondition<T, Columns extends Record<string, AnyColumn>>(\n columns: Columns,\n input: (builder: ConditionBuilder<Columns>) => T,\n): T {\n return input(createBuilder(columns));\n}\n\n/**\n * Create a ConditionBuilder that only allows comparisons on indexed columns.\n * Used in Unit of Work to ensure queries can leverage indexes for optimal performance.\n *\n * @param columns - The full set of columns from the table\n * @param indexedColumnNames - Set of column names that are part of indexes\n * @returns A ConditionBuilder restricted to indexed columns only\n *\n * @example\n * ```ts\n * const builder = createIndexedBuilder(\n * table.columns,\n * new Set([\"id\", \"userId\", \"createdAt\"])\n * );\n * const condition = builder(\"userId\", \"=\", \"123\");\n * ```\n */\nexport function createIndexedBuilder<Columns extends Record<string, AnyColumn>>(\n columns: Columns,\n indexedColumnNames: Set<string>,\n): ConditionBuilder<Columns> {\n function col(name: keyof Columns) {\n const columnName = String(name);\n\n if (!indexedColumnNames.has(columnName)) {\n throw new Error(\n `Column \"${columnName}\" is not indexed. Only indexed columns can be used in Unit of Work queries. ` +\n `Available indexed columns: ${Array.from(indexedColumnNames).join(\", \")}`,\n );\n }\n\n const out = columns[name];\n if (!out) {\n throw new Error(`Invalid column name ${columnName}`);\n }\n\n return out;\n }\n\n const builder: ConditionBuilder<Columns> = (...args: [string, Operator, unknown] | [string]) => {\n if (args.length === 3) {\n const [a, operator, b] = args;\n\n if (!operators.includes(operator)) {\n throw new Error(`Unsupported operator: ${operator}`);\n }\n\n return {\n type: \"compare\",\n a: col(a),\n b,\n operator,\n };\n }\n\n return {\n type: \"compare\",\n a: col(args[0]),\n operator: \"=\",\n b: true,\n };\n };\n\n builder.isNull = (a) => builder(a, \"is\", null);\n builder.isNotNull = (a) => builder(a, \"is not\", null);\n builder.not = (condition) => {\n if (typeof condition === \"boolean\") {\n return !condition;\n }\n\n return {\n type: \"not\",\n item: condition,\n };\n };\n\n builder.or = (...conditions) => {\n const out = {\n type: \"or\",\n items: [] as Condition[],\n } as const;\n\n for (const item of conditions) {\n if (item === true) {\n return true;\n }\n if (item === false) {\n continue;\n }\n\n out.items.push(item);\n }\n\n if (out.items.length === 0) {\n return false;\n }\n return out;\n };\n\n builder.and = (...conditions) => {\n const out = {\n type: \"and\",\n items: [] as Condition[],\n } as const;\n\n for (const item of conditions) {\n if (item === true) {\n continue;\n }\n if (item === false) {\n return false;\n }\n\n out.items.push(item);\n }\n\n if (out.items.length === 0) {\n return true;\n }\n return out;\n };\n\n return builder;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"condition-builder.js","names":["builder: ConditionBuilder<Columns>"],"sources":["../../src/query/condition-builder.ts"],"sourcesContent":["import type { AnyColumn, FragnoId, IdColumn } from \"../schema/create\";\nimport { dbInterval, dbNow, type DbInterval, type DbIntervalInput, type DbNow } from \"./db-now\";\n\nexport type ConditionType = \"compare\" | \"and\" | \"or\" | \"not\";\n\nexport type Condition =\n | {\n type: \"compare\";\n a: AnyColumn;\n operator: Operator;\n b: AnyColumn | unknown | null;\n }\n | {\n type: \"or\" | \"and\";\n items: Condition[];\n }\n | {\n type: \"not\";\n item: Condition;\n };\n\n// TODO: we temporarily dropped support for comparing against another column, because Prisma ORM still have problems with it.\n\n/**\n * Helper type that allows FragnoId for ID columns and reference columns (bigint).\n * Used in ConditionBuilder to accept FragnoId values in where conditions.\n */\ntype AcceptsFragnoId<T extends AnyColumn> = T extends IdColumn\n ? T[\"$in\"] | FragnoId\n : T[\"$in\"] extends bigint\n ? T[\"$in\"] | FragnoId\n : T[\"$in\"];\n\nexport type ConditionBuilder<Columns extends Record<string, AnyColumn>> = {\n <ColName extends keyof Columns>(\n a: ColName,\n operator: (typeof valueOperators)[number] | (typeof stringOperators)[number],\n b: AcceptsFragnoId<Columns[ColName]> | null,\n ): Condition;\n\n <ColName extends keyof Columns>(\n a: ColName,\n operator: (typeof arrayOperators)[number],\n b: AcceptsFragnoId<Columns[ColName]>[],\n ): Condition;\n\n /**\n * Boolean values\n */\n <ColName extends keyof Columns>(a: ColName): Condition;\n\n and: (...v: (Condition | boolean)[]) => Condition | boolean;\n or: (...v: (Condition | boolean)[]) => Condition | boolean;\n not: (v: Condition | boolean) => Condition | boolean;\n\n isNull: (a: keyof Columns) => Condition;\n isNotNull: (a: keyof Columns) => Condition;\n now: () => DbNow;\n interval: (input: DbIntervalInput) => DbInterval;\n};\n\n// replacement for `like` (Prisma doesn't support `like`)\nconst stringOperators = [\n \"contains\",\n \"starts with\",\n \"ends with\",\n\n \"not contains\",\n \"not starts with\",\n \"not ends with\",\n // excluded `regexp` since MSSQL doesn't support it, may re-consider\n] as const;\n\nconst arrayOperators = [\"in\", \"not in\"] as const;\n\nconst valueOperators = [\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"is\", \"is not\"] as const;\n\n// JSON specific operators are not included, some databases don't support them\n// `match` requires additional extensions & configurations on SQLite and PostgreSQL\n// MySQL & SQLite requires workarounds to support `ilike`\nexport const operators = [...valueOperators, ...arrayOperators, ...stringOperators] as const;\n\nexport type Operator = (typeof operators)[number];\n\nexport function createBuilder<Columns extends Record<string, AnyColumn>>(\n columns: Columns,\n): ConditionBuilder<Columns> {\n function col(name: keyof Columns) {\n const out = columns[name];\n if (!out) {\n throw new Error(`Invalid column name ${String(name)}`);\n }\n\n return out;\n }\n\n const builder: ConditionBuilder<Columns> = (...args: [string, Operator, unknown] | [string]) => {\n if (args.length === 3) {\n const [a, operator, b] = args;\n\n if (!operators.includes(operator)) {\n throw new Error(`Unsupported operator: ${operator}`);\n }\n\n return {\n type: \"compare\",\n a: col(a),\n b,\n operator,\n };\n }\n\n return {\n type: \"compare\",\n a: col(args[0]),\n operator: \"=\",\n b: true,\n };\n };\n\n builder.isNull = (a) => builder(a, \"is\", null);\n builder.isNotNull = (a) => builder(a, \"is not\", null);\n builder.now = () => dbNow();\n builder.interval = (input) => dbInterval(input);\n builder.not = (condition) => {\n if (typeof condition === \"boolean\") {\n return !condition;\n }\n\n return {\n type: \"not\",\n item: condition,\n };\n };\n\n builder.or = (...conditions) => {\n const out = {\n type: \"or\",\n items: [] as Condition[],\n } as const;\n\n for (const item of conditions) {\n if (item === true) {\n return true;\n }\n if (item === false) {\n continue;\n }\n\n out.items.push(item);\n }\n\n if (out.items.length === 0) {\n return false;\n }\n return out;\n };\n\n builder.and = (...conditions) => {\n const out = {\n type: \"and\",\n items: [] as Condition[],\n } as const;\n\n for (const item of conditions) {\n if (item === true) {\n continue;\n }\n if (item === false) {\n return false;\n }\n\n out.items.push(item);\n }\n\n if (out.items.length === 0) {\n return true;\n }\n return out;\n };\n\n return builder;\n}\n\nexport function buildCondition<T, Columns extends Record<string, AnyColumn>>(\n columns: Columns,\n input: (builder: ConditionBuilder<Columns>) => T,\n): T {\n return input(createBuilder(columns));\n}\n\n/**\n * Create a ConditionBuilder that only allows comparisons on indexed columns.\n * Used in Unit of Work to ensure queries can leverage indexes for optimal performance.\n *\n * @param columns - The full set of columns from the table\n * @param indexedColumnNames - Set of column names that are part of indexes\n * @returns A ConditionBuilder restricted to indexed columns only\n *\n * @example\n * ```ts\n * const builder = createIndexedBuilder(\n * table.columns,\n * new Set([\"id\", \"userId\", \"createdAt\"])\n * );\n * const condition = builder(\"userId\", \"=\", \"123\");\n * ```\n */\nexport function createIndexedBuilder<Columns extends Record<string, AnyColumn>>(\n columns: Columns,\n indexedColumnNames: Set<string>,\n): ConditionBuilder<Columns> {\n function col(name: keyof Columns) {\n const columnName = String(name);\n\n if (!indexedColumnNames.has(columnName)) {\n throw new Error(\n `Column \"${columnName}\" is not indexed. Only indexed columns can be used in Unit of Work queries. ` +\n `Available indexed columns: ${Array.from(indexedColumnNames).join(\", \")}`,\n );\n }\n\n const out = columns[name];\n if (!out) {\n throw new Error(`Invalid column name ${columnName}`);\n }\n\n return out;\n }\n\n const builder: ConditionBuilder<Columns> = (...args: [string, Operator, unknown] | [string]) => {\n if (args.length === 3) {\n const [a, operator, b] = args;\n\n if (!operators.includes(operator)) {\n throw new Error(`Unsupported operator: ${operator}`);\n }\n\n return {\n type: \"compare\",\n a: col(a),\n b,\n operator,\n };\n }\n\n return {\n type: \"compare\",\n a: col(args[0]),\n operator: \"=\",\n b: true,\n };\n };\n\n builder.isNull = (a) => builder(a, \"is\", null);\n builder.isNotNull = (a) => builder(a, \"is not\", null);\n builder.now = () => dbNow();\n builder.interval = (input) => dbInterval(input);\n builder.not = (condition) => {\n if (typeof condition === \"boolean\") {\n return !condition;\n }\n\n return {\n type: \"not\",\n item: condition,\n };\n };\n\n builder.or = (...conditions) => {\n const out = {\n type: \"or\",\n items: [] as Condition[],\n } as const;\n\n for (const item of conditions) {\n if (item === true) {\n return true;\n }\n if (item === false) {\n continue;\n }\n\n out.items.push(item);\n }\n\n if (out.items.length === 0) {\n return false;\n }\n return out;\n };\n\n builder.and = (...conditions) => {\n const out = {\n type: \"and\",\n items: [] as Condition[],\n } as const;\n\n for (const item of conditions) {\n if (item === true) {\n continue;\n }\n if (item === false) {\n return false;\n }\n\n out.items.push(item);\n }\n\n if (out.items.length === 0) {\n return true;\n }\n return out;\n };\n\n return builder;\n}\n"],"mappings":";;;AA8DA,MAAM,kBAAkB;CACtB;CACA;CACA;CAEA;CACA;CACA;CAED;AAED,MAAM,iBAAiB,CAAC,MAAM,SAAS;AAEvC,MAAM,iBAAiB;CAAC;CAAK;CAAM;CAAK;CAAM;CAAK;CAAM;CAAM;CAAS;AAKxE,MAAa,YAAY;CAAC,GAAG;CAAgB,GAAG;CAAgB,GAAG;CAAgB;AAInF,SAAgB,cACd,SAC2B;CAC3B,SAAS,IAAI,MAAqB;EAChC,MAAM,MAAM,QAAQ;AACpB,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,uBAAuB,OAAO,KAAK,GAAG;AAGxD,SAAO;;CAGT,MAAMA,WAAsC,GAAG,SAAiD;AAC9F,MAAI,KAAK,WAAW,GAAG;GACrB,MAAM,CAAC,GAAG,UAAU,KAAK;AAEzB,OAAI,CAAC,UAAU,SAAS,SAAS,CAC/B,OAAM,IAAI,MAAM,yBAAyB,WAAW;AAGtD,UAAO;IACL,MAAM;IACN,GAAG,IAAI,EAAE;IACT;IACA;IACD;;AAGH,SAAO;GACL,MAAM;GACN,GAAG,IAAI,KAAK,GAAG;GACf,UAAU;GACV,GAAG;GACJ;;AAGH,SAAQ,UAAU,MAAM,QAAQ,GAAG,MAAM,KAAK;AAC9C,SAAQ,aAAa,MAAM,QAAQ,GAAG,UAAU,KAAK;AACrD,SAAQ,YAAY,OAAO;AAC3B,SAAQ,YAAY,UAAU,WAAW,MAAM;AAC/C,SAAQ,OAAO,cAAc;AAC3B,MAAI,OAAO,cAAc,UACvB,QAAO,CAAC;AAGV,SAAO;GACL,MAAM;GACN,MAAM;GACP;;AAGH,SAAQ,MAAM,GAAG,eAAe;EAC9B,MAAM,MAAM;GACV,MAAM;GACN,OAAO,EAAE;GACV;AAED,OAAK,MAAM,QAAQ,YAAY;AAC7B,OAAI,SAAS,KACX,QAAO;AAET,OAAI,SAAS,MACX;AAGF,OAAI,MAAM,KAAK,KAAK;;AAGtB,MAAI,IAAI,MAAM,WAAW,EACvB,QAAO;AAET,SAAO;;AAGT,SAAQ,OAAO,GAAG,eAAe;EAC/B,MAAM,MAAM;GACV,MAAM;GACN,OAAO,EAAE;GACV;AAED,OAAK,MAAM,QAAQ,YAAY;AAC7B,OAAI,SAAS,KACX;AAEF,OAAI,SAAS,MACX,QAAO;AAGT,OAAI,MAAM,KAAK,KAAK;;AAGtB,MAAI,IAAI,MAAM,WAAW,EACvB,QAAO;AAET,SAAO;;AAGT,QAAO;;AAGT,SAAgB,eACd,SACA,OACG;AACH,QAAO,MAAM,cAAc,QAAQ,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { AnyColumn } from "../schema/create.js";
|
|
2
|
+
|
|
3
|
+
//#region src/query/cursor-client.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cursor object containing all information needed for pagination
|
|
7
|
+
*/
|
|
8
|
+
declare class Cursor {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(data: {
|
|
11
|
+
indexName: string;
|
|
12
|
+
orderDirection: "asc" | "desc";
|
|
13
|
+
pageSize: number;
|
|
14
|
+
indexValues: Record<string, unknown>;
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Get the index name being used for pagination
|
|
18
|
+
*/
|
|
19
|
+
get indexName(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Get the ordering direction
|
|
22
|
+
*/
|
|
23
|
+
get orderDirection(): "asc" | "desc";
|
|
24
|
+
/**
|
|
25
|
+
* Get the page size
|
|
26
|
+
*/
|
|
27
|
+
get pageSize(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Get the cursor position values
|
|
30
|
+
*/
|
|
31
|
+
get indexValues(): Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* Encode cursor to an opaque base64 string (safe to send to client)
|
|
34
|
+
*/
|
|
35
|
+
encode(): string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Result of a cursor-based query containing items and pagination cursor
|
|
39
|
+
*/
|
|
40
|
+
interface CursorResult<T> {
|
|
41
|
+
/**
|
|
42
|
+
* The query results
|
|
43
|
+
*/
|
|
44
|
+
items: T[];
|
|
45
|
+
/**
|
|
46
|
+
* Cursor to fetch the next page (undefined if no more results)
|
|
47
|
+
*/
|
|
48
|
+
cursor?: Cursor;
|
|
49
|
+
/**
|
|
50
|
+
* Whether there are more results available after this page
|
|
51
|
+
*/
|
|
52
|
+
hasNextPage: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Cursor data structure for serialization
|
|
56
|
+
*/
|
|
57
|
+
interface CursorData {
|
|
58
|
+
v: number;
|
|
59
|
+
indexName: string;
|
|
60
|
+
orderDirection: "asc" | "desc";
|
|
61
|
+
pageSize: number;
|
|
62
|
+
indexValues: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Decode a base64 cursor string back to a Cursor object
|
|
66
|
+
*
|
|
67
|
+
* @param cursor - The base64-encoded cursor string
|
|
68
|
+
* @returns Decoded Cursor object
|
|
69
|
+
* @throws Error if cursor is invalid or malformed
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const cursor = decodeCursor("eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==");
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
declare function decodeCursor(cursor: string): Cursor;
|
|
77
|
+
/**
|
|
78
|
+
* Create a cursor from a record and pagination metadata
|
|
79
|
+
*
|
|
80
|
+
* @param record - The database record
|
|
81
|
+
* @param indexColumns - The columns that make up the index
|
|
82
|
+
* @param metadata - Pagination metadata (index name, order direction, page size)
|
|
83
|
+
* @returns Cursor object
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const cursor = createCursorFromRecord(
|
|
88
|
+
* { id: "abc", name: "Alice", createdAt: 123 },
|
|
89
|
+
* [table.columns.createdAt, table.columns.id],
|
|
90
|
+
* {
|
|
91
|
+
* indexName: "idx_created",
|
|
92
|
+
* orderDirection: "asc",
|
|
93
|
+
* pageSize: 10
|
|
94
|
+
* }
|
|
95
|
+
* );
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
declare function createCursorFromRecord(record: Record<string, unknown>, indexColumns: AnyColumn[], metadata: {
|
|
99
|
+
indexName: string;
|
|
100
|
+
orderDirection: "asc" | "desc";
|
|
101
|
+
pageSize: number;
|
|
102
|
+
}): Cursor;
|
|
103
|
+
//#endregion
|
|
104
|
+
export { Cursor, CursorData, CursorResult, createCursorFromRecord, decodeCursor };
|
|
105
|
+
//# sourceMappingURL=cursor-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-client.d.ts","names":[],"sources":["../../src/query/cursor-client.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AAiEiB,cAjEJ,MAAA,CAiEgB;EAkBZ,CAAA,OAAA;EAkED,WAAA,CAAA,IAAY,EAAA;IA+DZ,SAAA,EAAA,MAAA;IACN,cAAA,EAAA,KAAA,GAAA,MAAA;IACM,QAAA,EAAA,MAAA;IAMb,WAAA,EAlNc,MAkNd,CAAA,MAAA,EAAA,OAAA,CAAA;EAAM,CAAA;;;;;;;;;;;;;;;;qBAlLY;;;;;;;;;UAuBJ;;;;SAIR;;;;WAIE;;;;;;;;;UAUM,UAAA;;;;;eAKF;;;;;;;;;;;;;;iBA6DC,YAAA,kBAA8B;;;;;;;;;;;;;;;;;;;;;;iBA+D9B,sBAAA,SACN,uCACM;;;;IAMb"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
//#region src/query/cursor-client.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cursor object containing all information needed for pagination
|
|
4
|
+
*/
|
|
5
|
+
var Cursor = class {
|
|
6
|
+
#indexName;
|
|
7
|
+
#orderDirection;
|
|
8
|
+
#pageSize;
|
|
9
|
+
#indexValues;
|
|
10
|
+
constructor(data) {
|
|
11
|
+
this.#indexName = data.indexName;
|
|
12
|
+
this.#orderDirection = data.orderDirection;
|
|
13
|
+
this.#pageSize = data.pageSize;
|
|
14
|
+
this.#indexValues = data.indexValues;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get the index name being used for pagination
|
|
18
|
+
*/
|
|
19
|
+
get indexName() {
|
|
20
|
+
return this.#indexName;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get the ordering direction
|
|
24
|
+
*/
|
|
25
|
+
get orderDirection() {
|
|
26
|
+
return this.#orderDirection;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the page size
|
|
30
|
+
*/
|
|
31
|
+
get pageSize() {
|
|
32
|
+
return this.#pageSize;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the cursor position values
|
|
36
|
+
*/
|
|
37
|
+
get indexValues() {
|
|
38
|
+
return this.#indexValues;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Encode cursor to an opaque base64 string (safe to send to client)
|
|
42
|
+
*/
|
|
43
|
+
encode() {
|
|
44
|
+
assertSerializableIndexValues(this.#indexValues);
|
|
45
|
+
return encodeCursorData({
|
|
46
|
+
v: 1,
|
|
47
|
+
indexName: this.#indexName,
|
|
48
|
+
orderDirection: this.#orderDirection,
|
|
49
|
+
pageSize: this.#pageSize,
|
|
50
|
+
indexValues: this.#indexValues
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const getBase64Helpers = () => {
|
|
55
|
+
const { btoa, atob, TextEncoder, TextDecoder } = globalThis;
|
|
56
|
+
if (!btoa || !atob) throw new Error("Base64 helpers (btoa/atob) are not available in this environment.");
|
|
57
|
+
if (!TextEncoder || !TextDecoder) throw new Error("TextEncoder/TextDecoder are required for cursor encoding.");
|
|
58
|
+
const encodeBase64 = (input) => {
|
|
59
|
+
const bytes = new TextEncoder().encode(input);
|
|
60
|
+
let binary = "";
|
|
61
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
62
|
+
return btoa(binary);
|
|
63
|
+
};
|
|
64
|
+
const decodeBase64 = (input) => {
|
|
65
|
+
const binary = atob(input);
|
|
66
|
+
const bytes = new Uint8Array(binary.length);
|
|
67
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
68
|
+
return new TextDecoder().decode(bytes);
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
encodeBase64,
|
|
72
|
+
decodeBase64
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Encode cursor data to a base64 string (internal)
|
|
77
|
+
*/
|
|
78
|
+
function encodeCursorData(data) {
|
|
79
|
+
let json;
|
|
80
|
+
try {
|
|
81
|
+
json = JSON.stringify(data);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : "malformed data"}`);
|
|
84
|
+
}
|
|
85
|
+
return getBase64Helpers().encodeBase64(json);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Decode a base64 cursor string back to a Cursor object
|
|
89
|
+
*
|
|
90
|
+
* @param cursor - The base64-encoded cursor string
|
|
91
|
+
* @returns Decoded Cursor object
|
|
92
|
+
* @throws Error if cursor is invalid or malformed
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const cursor = decodeCursor("eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==");
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function decodeCursor(cursor) {
|
|
100
|
+
try {
|
|
101
|
+
const json = getBase64Helpers().decodeBase64(cursor);
|
|
102
|
+
const data = JSON.parse(json);
|
|
103
|
+
const record = data;
|
|
104
|
+
if (!isPlainObject(data) || !isPlainObject(record["indexValues"]) || typeof record["indexName"] !== "string" || record["indexName"].length === 0 || typeof record["orderDirection"] !== "string" || record["orderDirection"] !== "asc" && record["orderDirection"] !== "desc" || typeof record["pageSize"] !== "number" || !Number.isFinite(record["pageSize"]) || !Number.isInteger(record["pageSize"]) || record["pageSize"] <= 0) throw new Error("Invalid cursor structure");
|
|
105
|
+
if (typeof record["v"] !== "number") throw new Error("Unsupported cursor version: missing. Only v1 is supported.");
|
|
106
|
+
const version = record["v"];
|
|
107
|
+
if (version !== 1) throw new Error(`Unsupported cursor version: ${version}. Only v1 is supported.`);
|
|
108
|
+
return new Cursor({
|
|
109
|
+
indexName: record["indexName"],
|
|
110
|
+
orderDirection: record["orderDirection"],
|
|
111
|
+
pageSize: record["pageSize"],
|
|
112
|
+
indexValues: record["indexValues"]
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : "malformed data"}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Create a cursor from a record and pagination metadata
|
|
120
|
+
*
|
|
121
|
+
* @param record - The database record
|
|
122
|
+
* @param indexColumns - The columns that make up the index
|
|
123
|
+
* @param metadata - Pagination metadata (index name, order direction, page size)
|
|
124
|
+
* @returns Cursor object
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* const cursor = createCursorFromRecord(
|
|
129
|
+
* { id: "abc", name: "Alice", createdAt: 123 },
|
|
130
|
+
* [table.columns.createdAt, table.columns.id],
|
|
131
|
+
* {
|
|
132
|
+
* indexName: "idx_created",
|
|
133
|
+
* orderDirection: "asc",
|
|
134
|
+
* pageSize: 10
|
|
135
|
+
* }
|
|
136
|
+
* );
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
function createCursorFromRecord(record, indexColumns, metadata) {
|
|
140
|
+
const indexValues = {};
|
|
141
|
+
for (const col of indexColumns) {
|
|
142
|
+
const value = record[col.name];
|
|
143
|
+
if (value === void 0) throw new Error(`Record is missing value for index column "${col.name}".`);
|
|
144
|
+
indexValues[col.name] = value;
|
|
145
|
+
}
|
|
146
|
+
return new Cursor({
|
|
147
|
+
indexName: metadata.indexName,
|
|
148
|
+
orderDirection: metadata.orderDirection,
|
|
149
|
+
pageSize: metadata.pageSize,
|
|
150
|
+
indexValues
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
154
|
+
const assertSerializableIndexValues = (values) => {
|
|
155
|
+
for (const [key, value] of Object.entries(values)) {
|
|
156
|
+
if (value === void 0) throw new Error(`Cursor index value "${key}" is undefined.`);
|
|
157
|
+
if (typeof value === "number" && !Number.isFinite(value)) throw new Error(`Cursor index value "${key}" must be a finite number.`);
|
|
158
|
+
if (typeof value === "bigint") throw new Error(`Cursor index value "${key}" must not be a BigInt.`);
|
|
159
|
+
if (typeof value === "function" || typeof value === "symbol") throw new Error(`Cursor index value "${key}" is not JSON-serializable.`);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
export { Cursor, createCursorFromRecord, decodeCursor };
|
|
165
|
+
//# sourceMappingURL=cursor-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-client.js","names":["#indexName","#orderDirection","#pageSize","#indexValues","json: string","indexValues: Record<string, unknown>"],"sources":["../../src/query/cursor-client.ts"],"sourcesContent":["import type { AnyColumn } from \"../schema/create\";\n\n/**\n * Cursor object containing all information needed for pagination\n */\nexport class Cursor {\n readonly #indexName: string;\n readonly #orderDirection: \"asc\" | \"desc\";\n readonly #pageSize: number;\n readonly #indexValues: Record<string, unknown>;\n\n constructor(data: {\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n indexValues: Record<string, unknown>;\n }) {\n this.#indexName = data.indexName;\n this.#orderDirection = data.orderDirection;\n this.#pageSize = data.pageSize;\n this.#indexValues = data.indexValues;\n }\n\n /**\n * Get the index name being used for pagination\n */\n get indexName(): string {\n return this.#indexName;\n }\n\n /**\n * Get the ordering direction\n */\n get orderDirection(): \"asc\" | \"desc\" {\n return this.#orderDirection;\n }\n\n /**\n * Get the page size\n */\n get pageSize(): number {\n return this.#pageSize;\n }\n\n /**\n * Get the cursor position values\n */\n get indexValues(): Record<string, unknown> {\n return this.#indexValues;\n }\n\n /**\n * Encode cursor to an opaque base64 string (safe to send to client)\n */\n encode(): string {\n assertSerializableIndexValues(this.#indexValues);\n const data: CursorData = {\n v: 1,\n indexName: this.#indexName,\n orderDirection: this.#orderDirection,\n pageSize: this.#pageSize,\n indexValues: this.#indexValues,\n };\n return encodeCursorData(data);\n }\n}\n\n/**\n * Result of a cursor-based query containing items and pagination cursor\n */\nexport interface CursorResult<T> {\n /**\n * The query results\n */\n items: T[];\n /**\n * Cursor to fetch the next page (undefined if no more results)\n */\n cursor?: Cursor;\n /**\n * Whether there are more results available after this page\n */\n hasNextPage: boolean;\n}\n\n/**\n * Cursor data structure for serialization\n */\nexport interface CursorData {\n v: number; // version\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n indexValues: Record<string, unknown>;\n}\n\nconst getBase64Helpers = () => {\n const { btoa, atob, TextEncoder, TextDecoder } = globalThis;\n\n if (!btoa || !atob) {\n throw new Error(\"Base64 helpers (btoa/atob) are not available in this environment.\");\n }\n\n if (!TextEncoder || !TextDecoder) {\n throw new Error(\"TextEncoder/TextDecoder are required for cursor encoding.\");\n }\n\n const encodeBase64 = (input: string): string => {\n const bytes = new TextEncoder().encode(input);\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n };\n\n const decodeBase64 = (input: string): string => {\n const binary = atob(input);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder().decode(bytes);\n };\n\n return { encodeBase64, decodeBase64 };\n};\n\n/**\n * Encode cursor data to a base64 string (internal)\n */\nfunction encodeCursorData(data: CursorData): string {\n let json: string;\n try {\n json = JSON.stringify(data);\n } catch (error) {\n throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : \"malformed data\"}`);\n }\n\n return getBase64Helpers().encodeBase64(json);\n}\n\n/**\n * Decode a base64 cursor string back to a Cursor object\n *\n * @param cursor - The base64-encoded cursor string\n * @returns Decoded Cursor object\n * @throws Error if cursor is invalid or malformed\n *\n * @example\n * ```ts\n * const cursor = decodeCursor(\"eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==\");\n * ```\n */\nexport function decodeCursor(cursor: string): Cursor {\n try {\n const json = getBase64Helpers().decodeBase64(cursor);\n const data = JSON.parse(json);\n const record = data as Record<string, unknown>;\n\n // Validate structure\n if (\n !isPlainObject(data) ||\n !isPlainObject(record[\"indexValues\"]) ||\n typeof record[\"indexName\"] !== \"string\" ||\n record[\"indexName\"].length === 0 ||\n typeof record[\"orderDirection\"] !== \"string\" ||\n (record[\"orderDirection\"] !== \"asc\" && record[\"orderDirection\"] !== \"desc\") ||\n typeof record[\"pageSize\"] !== \"number\" ||\n !Number.isFinite(record[\"pageSize\"]) ||\n !Number.isInteger(record[\"pageSize\"]) ||\n record[\"pageSize\"] <= 0\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n\n // Only support v1\n if (typeof record[\"v\"] !== \"number\") {\n throw new Error(\"Unsupported cursor version: missing. Only v1 is supported.\");\n }\n const version = record[\"v\"];\n if (version !== 1) {\n throw new Error(`Unsupported cursor version: ${version}. Only v1 is supported.`);\n }\n\n return new Cursor({\n indexName: record[\"indexName\"],\n orderDirection: record[\"orderDirection\"],\n pageSize: record[\"pageSize\"],\n indexValues: record[\"indexValues\"],\n });\n } catch (error) {\n throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : \"malformed data\"}`);\n }\n}\n\n/**\n * Create a cursor from a record and pagination metadata\n *\n * @param record - The database record\n * @param indexColumns - The columns that make up the index\n * @param metadata - Pagination metadata (index name, order direction, page size)\n * @returns Cursor object\n *\n * @example\n * ```ts\n * const cursor = createCursorFromRecord(\n * { id: \"abc\", name: \"Alice\", createdAt: 123 },\n * [table.columns.createdAt, table.columns.id],\n * {\n * indexName: \"idx_created\",\n * orderDirection: \"asc\",\n * pageSize: 10\n * }\n * );\n * ```\n */\nexport function createCursorFromRecord(\n record: Record<string, unknown>,\n indexColumns: AnyColumn[],\n metadata: {\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n },\n): Cursor {\n const indexValues: Record<string, unknown> = {};\n\n for (const col of indexColumns) {\n const value = record[col.name];\n if (value === undefined) {\n throw new Error(`Record is missing value for index column \"${col.name}\".`);\n }\n indexValues[col.name] = value;\n }\n\n return new Cursor({\n indexName: metadata.indexName,\n orderDirection: metadata.orderDirection,\n pageSize: metadata.pageSize,\n indexValues,\n });\n}\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst assertSerializableIndexValues = (values: Record<string, unknown>): void => {\n for (const [key, value] of Object.entries(values)) {\n if (value === undefined) {\n throw new Error(`Cursor index value \"${key}\" is undefined.`);\n }\n if (typeof value === \"number\" && !Number.isFinite(value)) {\n throw new Error(`Cursor index value \"${key}\" must be a finite number.`);\n }\n if (typeof value === \"bigint\") {\n throw new Error(`Cursor index value \"${key}\" must not be a BigInt.`);\n }\n if (typeof value === \"function\" || typeof value === \"symbol\") {\n throw new Error(`Cursor index value \"${key}\" is not JSON-serializable.`);\n }\n }\n};\n"],"mappings":"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"}
|
package/dist/query/cursor.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AnyColumn } from "../schema/create.js";
|
|
2
2
|
import { DriverConfig } from "../adapters/generic-sql/driver-config.js";
|
|
3
|
+
import { SQLiteStorageMode } from "../adapters/generic-sql/sqlite-storage.js";
|
|
3
4
|
|
|
4
5
|
//#region src/query/cursor.d.ts
|
|
5
6
|
|
|
@@ -114,6 +115,7 @@ declare function createCursorFromRecord(record: Record<string, unknown>, indexCo
|
|
|
114
115
|
* @param cursor - The cursor object
|
|
115
116
|
* @param indexColumns - The columns that make up the index
|
|
116
117
|
* @param driverConfig - The driver configuration
|
|
118
|
+
* @param sqliteStorageMode - Optional SQLite storage mode override
|
|
117
119
|
* @returns Serialized values ready for database queries
|
|
118
120
|
*
|
|
119
121
|
* @example
|
|
@@ -125,7 +127,7 @@ declare function createCursorFromRecord(record: Record<string, unknown>, indexCo
|
|
|
125
127
|
* );
|
|
126
128
|
* ```
|
|
127
129
|
*/
|
|
128
|
-
declare function serializeCursorValues(cursor: Cursor, indexColumns: AnyColumn[], driverConfig: DriverConfig): Record<string, unknown>;
|
|
130
|
+
declare function serializeCursorValues(cursor: Cursor, indexColumns: AnyColumn[], driverConfig: DriverConfig, sqliteStorageMode?: SQLiteStorageMode): Record<string, unknown>;
|
|
129
131
|
//#endregion
|
|
130
132
|
export { Cursor, CursorData, CursorResult, createCursorFromRecord, decodeCursor, serializeCursorValues };
|
|
131
133
|
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.d.ts","names":[],"sources":["../../src/query/cursor.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","names":[],"sources":["../../src/query/cursor.ts"],"sourcesContent":[],"mappings":";;;;;;;;AASA;AAiEiB,cAjEJ,MAAA,CAiEgB;EAkBZ,CAAA,OAAA;EAqCD,WAAA,CAAA,IAAY,EAAA;IAoEZ,SAAA,EAAA,MAAA;IACN,cAAA,EAAA,KAAA,GAAA,MAAA;IACM,QAAA,EAAA,MAAA;IAMb,WAAA,EA1Lc,MA0Ld,CAAA,MAAA,EAAA,OAAA,CAAA;EAAM,CAAA;EA+CO;;;EAGA,IAAA,SAAA,CAAA,CAAA,EAAA,MAAA;EACM;;;;;;;;;;;qBA7MD;;;;;;;;;UAuBJ;;;;SAIR;;;;WAIE;;;;;;;;;UAUM,UAAA;;;;;eAKF;;;;;;;;;;;;;;iBAgCC,YAAA,kBAA8B;;;;;;;;;;;;;;;;;;;;;;iBAoE9B,sBAAA,SACN,uCACM;;;;IAMb;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+Ca,qBAAA,SACN,sBACM,2BACA,kCACM,oBACnB"}
|
package/dist/query/cursor.js
CHANGED
|
@@ -44,6 +44,7 @@ var Cursor = class {
|
|
|
44
44
|
* Encode cursor to an opaque base64 string (safe to send to client)
|
|
45
45
|
*/
|
|
46
46
|
encode() {
|
|
47
|
+
assertSerializableIndexValues(this.#indexValues);
|
|
47
48
|
return encodeCursorData({
|
|
48
49
|
v: 1,
|
|
49
50
|
indexName: this.#indexName,
|
|
@@ -57,7 +58,12 @@ var Cursor = class {
|
|
|
57
58
|
* Encode cursor data to a base64 string (internal)
|
|
58
59
|
*/
|
|
59
60
|
function encodeCursorData(data) {
|
|
60
|
-
|
|
61
|
+
let json;
|
|
62
|
+
try {
|
|
63
|
+
json = JSON.stringify(data);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : "malformed data"}`);
|
|
66
|
+
}
|
|
61
67
|
if (typeof Buffer !== "undefined") return Buffer.from(json, "utf-8").toString("base64");
|
|
62
68
|
return btoa(json);
|
|
63
69
|
}
|
|
@@ -79,14 +85,16 @@ function decodeCursor(cursor) {
|
|
|
79
85
|
if (typeof Buffer !== "undefined") json = Buffer.from(cursor, "base64").toString("utf-8");
|
|
80
86
|
else json = atob(cursor);
|
|
81
87
|
const data = JSON.parse(json);
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
const record = data;
|
|
89
|
+
if (!isPlainObject(data) || !isPlainObject(record["indexValues"]) || typeof record["indexName"] !== "string" || record["indexName"].length === 0 || typeof record["orderDirection"] !== "string" || record["orderDirection"] !== "asc" && record["orderDirection"] !== "desc" || typeof record["pageSize"] !== "number" || !Number.isFinite(record["pageSize"]) || !Number.isInteger(record["pageSize"]) || record["pageSize"] <= 0) throw new Error("Invalid cursor structure");
|
|
90
|
+
if (typeof record["v"] !== "number") throw new Error("Unsupported cursor version: missing. Only v1 is supported.");
|
|
91
|
+
const version = record["v"];
|
|
84
92
|
if (version !== 1) throw new Error(`Unsupported cursor version: ${version}. Only v1 is supported.`);
|
|
85
93
|
return new Cursor({
|
|
86
|
-
indexName:
|
|
87
|
-
orderDirection:
|
|
88
|
-
pageSize:
|
|
89
|
-
indexValues:
|
|
94
|
+
indexName: record["indexName"],
|
|
95
|
+
orderDirection: record["orderDirection"],
|
|
96
|
+
pageSize: record["pageSize"],
|
|
97
|
+
indexValues: record["indexValues"]
|
|
90
98
|
});
|
|
91
99
|
} catch (error) {
|
|
92
100
|
throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : "malformed data"}`);
|
|
@@ -115,7 +123,12 @@ function decodeCursor(cursor) {
|
|
|
115
123
|
*/
|
|
116
124
|
function createCursorFromRecord(record, indexColumns, metadata) {
|
|
117
125
|
const indexValues = {};
|
|
118
|
-
for (const col of indexColumns)
|
|
126
|
+
for (const col of indexColumns) {
|
|
127
|
+
const value = record[col.name];
|
|
128
|
+
if (value === void 0) throw new Error(`Record is missing value for index column "${col.name}".`);
|
|
129
|
+
const resolved = resolveFragnoIdValue(value, col);
|
|
130
|
+
indexValues[col.name] = typeof resolved === "bigint" ? resolved.toString() : resolved;
|
|
131
|
+
}
|
|
119
132
|
return new Cursor({
|
|
120
133
|
indexName: metadata.indexName,
|
|
121
134
|
orderDirection: metadata.orderDirection,
|
|
@@ -136,6 +149,7 @@ function createCursorFromRecord(record, indexColumns, metadata) {
|
|
|
136
149
|
* @param cursor - The cursor object
|
|
137
150
|
* @param indexColumns - The columns that make up the index
|
|
138
151
|
* @param driverConfig - The driver configuration
|
|
152
|
+
* @param sqliteStorageMode - Optional SQLite storage mode override
|
|
139
153
|
* @returns Serialized values ready for database queries
|
|
140
154
|
*
|
|
141
155
|
* @example
|
|
@@ -147,18 +161,41 @@ function createCursorFromRecord(record, indexColumns, metadata) {
|
|
|
147
161
|
* );
|
|
148
162
|
* ```
|
|
149
163
|
*/
|
|
150
|
-
function serializeCursorValues(cursor, indexColumns, driverConfig) {
|
|
151
|
-
const serializer = createSQLSerializer(driverConfig);
|
|
164
|
+
function serializeCursorValues(cursor, indexColumns, driverConfig, sqliteStorageMode) {
|
|
165
|
+
const serializer = createSQLSerializer(driverConfig, sqliteStorageMode);
|
|
152
166
|
const serialized = {};
|
|
167
|
+
const missingColumns = [];
|
|
153
168
|
for (const col of indexColumns) {
|
|
154
|
-
const value = cursor.indexValues[col.
|
|
155
|
-
if (value
|
|
156
|
-
|
|
157
|
-
|
|
169
|
+
const value = cursor.indexValues[col.name];
|
|
170
|
+
if (value === void 0) {
|
|
171
|
+
missingColumns.push(col.name);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const resolvedDirect = resolveFragnoIdValue(value, col);
|
|
175
|
+
if (resolvedDirect !== value) {
|
|
176
|
+
serialized[col.name] = serializer.serialize(resolvedDirect, col);
|
|
177
|
+
continue;
|
|
158
178
|
}
|
|
179
|
+
const resolved = resolveFragnoIdValue(serializer.deserialize(value, col), col);
|
|
180
|
+
serialized[col.name] = serializer.serialize(resolved, col);
|
|
181
|
+
}
|
|
182
|
+
if (missingColumns.length > 0) {
|
|
183
|
+
const suffix = cursor.indexName ? ` for index "${cursor.indexName}"` : "";
|
|
184
|
+
const columns = missingColumns.map((name) => `"${name}"`).join(", ");
|
|
185
|
+
const plural = missingColumns.length === 1 ? "" : "s";
|
|
186
|
+
throw new Error(`Cursor is missing values for index column${plural} ${columns}${suffix}.`);
|
|
159
187
|
}
|
|
160
188
|
return serialized;
|
|
161
189
|
}
|
|
190
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
191
|
+
const assertSerializableIndexValues = (values) => {
|
|
192
|
+
for (const [key, value] of Object.entries(values)) {
|
|
193
|
+
if (value === void 0) throw new Error(`Cursor index value "${key}" is undefined.`);
|
|
194
|
+
if (typeof value === "number" && !Number.isFinite(value)) throw new Error(`Cursor index value "${key}" must be a finite number.`);
|
|
195
|
+
if (typeof value === "bigint") throw new Error(`Cursor index value "${key}" must not be a BigInt.`);
|
|
196
|
+
if (typeof value === "function" || typeof value === "symbol") throw new Error(`Cursor index value "${key}" is not JSON-serializable.`);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
162
199
|
|
|
163
200
|
//#endregion
|
|
164
201
|
export { Cursor, createCursorFromRecord, decodeCursor, serializeCursorValues };
|
package/dist/query/cursor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.js","names":["#indexName","#orderDirection","#pageSize","#indexValues","json: string","indexValues: Record<string, unknown>","serialized: Record<string, unknown>"],"sources":["../../src/query/cursor.ts"],"sourcesContent":["import type { AnyColumn } from \"../schema/create\";\nimport { createSQLSerializer } from \"./serialize/create-sql-serializer\";\nimport { resolveFragnoIdValue } from \"./value-encoding\";\nimport type { DriverConfig } from \"../adapters/generic-sql/driver-config\";\n\n/**\n * Cursor object containing all information needed for pagination\n */\nexport class Cursor {\n readonly #indexName: string;\n readonly #orderDirection: \"asc\" | \"desc\";\n readonly #pageSize: number;\n readonly #indexValues: Record<string, unknown>;\n\n constructor(data: {\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n indexValues: Record<string, unknown>;\n }) {\n this.#indexName = data.indexName;\n this.#orderDirection = data.orderDirection;\n this.#pageSize = data.pageSize;\n this.#indexValues = data.indexValues;\n }\n\n /**\n * Get the index name being used for pagination\n */\n get indexName(): string {\n return this.#indexName;\n }\n\n /**\n * Get the ordering direction\n */\n get orderDirection(): \"asc\" | \"desc\" {\n return this.#orderDirection;\n }\n\n /**\n * Get the page size\n */\n get pageSize(): number {\n return this.#pageSize;\n }\n\n /**\n * Get the cursor position values\n */\n get indexValues(): Record<string, unknown> {\n return this.#indexValues;\n }\n\n /**\n * Encode cursor to an opaque base64 string (safe to send to client)\n */\n encode(): string {\n const data: CursorData = {\n v: 1,\n indexName: this.#indexName,\n orderDirection: this.#orderDirection,\n pageSize: this.#pageSize,\n indexValues: this.#indexValues,\n };\n return encodeCursorData(data);\n }\n}\n\n/**\n * Result of a cursor-based query containing items and pagination cursor\n */\nexport interface CursorResult<T> {\n /**\n * The query results\n */\n items: T[];\n /**\n * Cursor to fetch the next page (undefined if no more results)\n */\n cursor?: Cursor;\n /**\n * Whether there are more results available after this page\n */\n hasNextPage: boolean;\n}\n\n/**\n * Cursor data structure for serialization\n */\nexport interface CursorData {\n v: number; // version\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n indexValues: Record<string, unknown>;\n}\n\n/**\n * Encode cursor data to a base64 string (internal)\n */\nfunction encodeCursorData(data: CursorData): string {\n const json = JSON.stringify(data);\n // Use Buffer in Node.js or btoa in browsers\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(json, \"utf-8\").toString(\"base64\");\n }\n return btoa(json);\n}\n\n/**\n * Decode a base64 cursor string back to a Cursor object\n *\n * @param cursor - The base64-encoded cursor string\n * @returns Decoded Cursor object\n * @throws Error if cursor is invalid or malformed\n *\n * @example\n * ```ts\n * const cursor = decodeCursor(\"eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==\");\n * ```\n */\nexport function decodeCursor(cursor: string): Cursor {\n try {\n let json: string;\n if (typeof Buffer !== \"undefined\") {\n json = Buffer.from(cursor, \"base64\").toString(\"utf-8\");\n } else {\n json = atob(cursor);\n }\n const data = JSON.parse(json);\n\n // Validate structure\n if (\n !data ||\n typeof data !== \"object\" ||\n !data.indexValues ||\n typeof data.indexValues !== \"object\" ||\n typeof data.pageSize !== \"number\" ||\n !data.indexName ||\n !data.orderDirection ||\n (data.orderDirection !== \"asc\" && data.orderDirection !== \"desc\")\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n\n // Only support v1\n const version = typeof data.v === \"number\" ? data.v : 0;\n if (version !== 1) {\n throw new Error(`Unsupported cursor version: ${version}. Only v1 is supported.`);\n }\n\n return new Cursor({\n indexName: data.indexName,\n orderDirection: data.orderDirection,\n pageSize: data.pageSize,\n indexValues: data.indexValues,\n });\n } catch (error) {\n throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : \"malformed data\"}`);\n }\n}\n\n/**\n * Create a cursor from a record and pagination metadata\n *\n * @param record - The database record\n * @param indexColumns - The columns that make up the index\n * @param metadata - Pagination metadata (index name, order direction, page size)\n * @returns Cursor object\n *\n * @example\n * ```ts\n * const cursor = createCursorFromRecord(\n * { id: \"abc\", name: \"Alice\", createdAt: 123 },\n * [table.columns.createdAt, table.columns.id],\n * {\n * indexName: \"idx_created\",\n * orderDirection: \"asc\",\n * pageSize: 10\n * }\n * );\n * ```\n */\nexport function createCursorFromRecord(\n record: Record<string, unknown>,\n indexColumns: AnyColumn[],\n metadata: {\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n },\n): Cursor {\n const indexValues: Record<string, unknown> = {};\n\n for (const col of indexColumns) {\n indexValues[col.ormName] = record[col.ormName];\n }\n\n return new Cursor({\n indexName: metadata.indexName,\n orderDirection: metadata.orderDirection,\n pageSize: metadata.pageSize,\n indexValues,\n });\n}\n\n/**\n * Serialize cursor values for database queries\n *\n * Converts cursor values (which are in JSON-compatible format after decode)\n * to database format using the column serialization rules.\n *\n * This function performs a two-step process:\n * 1. Deserialize from JSON format to application format (e.g., ISO string → Date)\n * 2. Serialize from application format to database format (e.g., Date → driver format)\n *\n * @param cursor - The cursor object\n * @param indexColumns - The columns that make up the index\n * @param driverConfig - The driver configuration\n * @returns Serialized values ready for database queries\n *\n * @example\n * ```ts\n * const serialized = serializeCursorValues(\n * cursor,\n * [table.columns.createdAt],\n * driverConfig\n * );\n * ```\n */\nexport function serializeCursorValues(\n cursor: Cursor,\n indexColumns: AnyColumn[],\n driverConfig: DriverConfig,\n): Record<string, unknown> {\n const serializer = createSQLSerializer(driverConfig);\n const serialized: Record<string, unknown> = {};\n\n for (const col of indexColumns) {\n const value = cursor.indexValues[col.ormName];\n if (value !== undefined) {\n // First deserialize from JSON format to application format\n // (e.g., \"2025-11-07T09:36:57.959Z\" string → Date object)\n const deserialized = serializer.deserialize(value, col);\n // Resolve FragnoId/FragnoReference to primitive values (if present)\n const resolved = resolveFragnoIdValue(deserialized, col);\n // Then serialize to database format\n // (e.g., Date → database driver format)\n serialized[col.ormName] = serializer.serialize(resolved, col);\n }\n }\n\n return serialized;\n}\n"],"mappings":";;;;;;;AAQA,IAAa,SAAb,MAAoB;CAClB,CAASA;CACT,CAASC;CACT,CAASC;CACT,CAASC;CAET,YAAY,MAKT;AACD,QAAKH,YAAa,KAAK;AACvB,QAAKC,iBAAkB,KAAK;AAC5B,QAAKC,WAAY,KAAK;AACtB,QAAKC,cAAe,KAAK;;;;;CAM3B,IAAI,YAAoB;AACtB,SAAO,MAAKH;;;;;CAMd,IAAI,iBAAiC;AACnC,SAAO,MAAKC;;;;;CAMd,IAAI,WAAmB;AACrB,SAAO,MAAKC;;;;;CAMd,IAAI,cAAuC;AACzC,SAAO,MAAKC;;;;;CAMd,SAAiB;AAQf,SAAO,iBAPkB;GACvB,GAAG;GACH,WAAW,MAAKH;GAChB,gBAAgB,MAAKC;GACrB,UAAU,MAAKC;GACf,aAAa,MAAKC;GACnB,CAC4B;;;;;;AAoCjC,SAAS,iBAAiB,MAA0B;CAClD,MAAM,OAAO,KAAK,UAAU,KAAK;AAEjC,KAAI,OAAO,WAAW,YACpB,QAAO,OAAO,KAAK,MAAM,QAAQ,CAAC,SAAS,SAAS;AAEtD,QAAO,KAAK,KAAK;;;;;;;;;;;;;;AAenB,SAAgB,aAAa,QAAwB;AACnD,KAAI;EACF,IAAIC;AACJ,MAAI,OAAO,WAAW,YACpB,QAAO,OAAO,KAAK,QAAQ,SAAS,CAAC,SAAS,QAAQ;MAEtD,QAAO,KAAK,OAAO;EAErB,MAAM,OAAO,KAAK,MAAM,KAAK;AAG7B,MACE,CAAC,QACD,OAAO,SAAS,YAChB,CAAC,KAAK,eACN,OAAO,KAAK,gBAAgB,YAC5B,OAAO,KAAK,aAAa,YACzB,CAAC,KAAK,aACN,CAAC,KAAK,kBACL,KAAK,mBAAmB,SAAS,KAAK,mBAAmB,OAE1D,OAAM,IAAI,MAAM,2BAA2B;EAI7C,MAAM,UAAU,OAAO,KAAK,MAAM,WAAW,KAAK,IAAI;AACtD,MAAI,YAAY,EACd,OAAM,IAAI,MAAM,+BAA+B,QAAQ,yBAAyB;AAGlF,SAAO,IAAI,OAAO;GAChB,WAAW,KAAK;GAChB,gBAAgB,KAAK;GACrB,UAAU,KAAK;GACf,aAAa,KAAK;GACnB,CAAC;UACK,OAAO;AACd,QAAM,IAAI,MAAM,mBAAmB,iBAAiB,QAAQ,MAAM,UAAU,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;AAyBnG,SAAgB,uBACd,QACA,cACA,UAKQ;CACR,MAAMC,cAAuC,EAAE;AAE/C,MAAK,MAAM,OAAO,aAChB,aAAY,IAAI,WAAW,OAAO,IAAI;AAGxC,QAAO,IAAI,OAAO;EAChB,WAAW,SAAS;EACpB,gBAAgB,SAAS;EACzB,UAAU,SAAS;EACnB;EACD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BJ,SAAgB,sBACd,QACA,cACA,cACyB;CACzB,MAAM,aAAa,oBAAoB,aAAa;CACpD,MAAMC,aAAsC,EAAE;AAE9C,MAAK,MAAM,OAAO,cAAc;EAC9B,MAAM,QAAQ,OAAO,YAAY,IAAI;AACrC,MAAI,UAAU,QAAW;GAKvB,MAAM,WAAW,qBAFI,WAAW,YAAY,OAAO,IAAI,EAEH,IAAI;AAGxD,cAAW,IAAI,WAAW,WAAW,UAAU,UAAU,IAAI;;;AAIjE,QAAO"}
|
|
1
|
+
{"version":3,"file":"cursor.js","names":["#indexName","#orderDirection","#pageSize","#indexValues","json: string","indexValues: Record<string, unknown>","serialized: Record<string, unknown>","missingColumns: string[]"],"sources":["../../src/query/cursor.ts"],"sourcesContent":["import type { DriverConfig } from \"../adapters/generic-sql/driver-config\";\nimport type { SQLiteStorageMode } from \"../adapters/generic-sql/sqlite-storage\";\nimport type { AnyColumn } from \"../schema/create\";\nimport { createSQLSerializer } from \"./serialize/create-sql-serializer\";\nimport { resolveFragnoIdValue } from \"./value-encoding\";\n\n/**\n * Cursor object containing all information needed for pagination\n */\nexport class Cursor {\n readonly #indexName: string;\n readonly #orderDirection: \"asc\" | \"desc\";\n readonly #pageSize: number;\n readonly #indexValues: Record<string, unknown>;\n\n constructor(data: {\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n indexValues: Record<string, unknown>;\n }) {\n this.#indexName = data.indexName;\n this.#orderDirection = data.orderDirection;\n this.#pageSize = data.pageSize;\n this.#indexValues = data.indexValues;\n }\n\n /**\n * Get the index name being used for pagination\n */\n get indexName(): string {\n return this.#indexName;\n }\n\n /**\n * Get the ordering direction\n */\n get orderDirection(): \"asc\" | \"desc\" {\n return this.#orderDirection;\n }\n\n /**\n * Get the page size\n */\n get pageSize(): number {\n return this.#pageSize;\n }\n\n /**\n * Get the cursor position values\n */\n get indexValues(): Record<string, unknown> {\n return this.#indexValues;\n }\n\n /**\n * Encode cursor to an opaque base64 string (safe to send to client)\n */\n encode(): string {\n assertSerializableIndexValues(this.#indexValues);\n const data: CursorData = {\n v: 1,\n indexName: this.#indexName,\n orderDirection: this.#orderDirection,\n pageSize: this.#pageSize,\n indexValues: this.#indexValues,\n };\n return encodeCursorData(data);\n }\n}\n\n/**\n * Result of a cursor-based query containing items and pagination cursor\n */\nexport interface CursorResult<T> {\n /**\n * The query results\n */\n items: T[];\n /**\n * Cursor to fetch the next page (undefined if no more results)\n */\n cursor?: Cursor;\n /**\n * Whether there are more results available after this page\n */\n hasNextPage: boolean;\n}\n\n/**\n * Cursor data structure for serialization\n */\nexport interface CursorData {\n v: number; // version\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n indexValues: Record<string, unknown>;\n}\n\n/**\n * Encode cursor data to a base64 string (internal)\n */\nfunction encodeCursorData(data: CursorData): string {\n let json: string;\n try {\n json = JSON.stringify(data);\n } catch (error) {\n throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : \"malformed data\"}`);\n }\n // Use Buffer in Node.js or btoa in browsers\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(json, \"utf-8\").toString(\"base64\");\n }\n return btoa(json);\n}\n\n/**\n * Decode a base64 cursor string back to a Cursor object\n *\n * @param cursor - The base64-encoded cursor string\n * @returns Decoded Cursor object\n * @throws Error if cursor is invalid or malformed\n *\n * @example\n * ```ts\n * const cursor = decodeCursor(\"eyJpbmRleFZhbHVlcyI6e30sImRpcmVjdGlvbiI6ImZvcndhcmQifQ==\");\n * ```\n */\nexport function decodeCursor(cursor: string): Cursor {\n try {\n let json: string;\n if (typeof Buffer !== \"undefined\") {\n json = Buffer.from(cursor, \"base64\").toString(\"utf-8\");\n } else {\n json = atob(cursor);\n }\n const data = JSON.parse(json);\n const record = data as Record<string, unknown>;\n\n // Validate structure\n if (\n !isPlainObject(data) ||\n !isPlainObject(record[\"indexValues\"]) ||\n typeof record[\"indexName\"] !== \"string\" ||\n record[\"indexName\"].length === 0 ||\n typeof record[\"orderDirection\"] !== \"string\" ||\n (record[\"orderDirection\"] !== \"asc\" && record[\"orderDirection\"] !== \"desc\") ||\n typeof record[\"pageSize\"] !== \"number\" ||\n !Number.isFinite(record[\"pageSize\"]) ||\n !Number.isInteger(record[\"pageSize\"]) ||\n record[\"pageSize\"] <= 0\n ) {\n throw new Error(\"Invalid cursor structure\");\n }\n\n // Only support v1\n if (typeof record[\"v\"] !== \"number\") {\n throw new Error(\"Unsupported cursor version: missing. Only v1 is supported.\");\n }\n const version = record[\"v\"];\n if (version !== 1) {\n throw new Error(`Unsupported cursor version: ${version}. Only v1 is supported.`);\n }\n\n return new Cursor({\n indexName: record[\"indexName\"],\n orderDirection: record[\"orderDirection\"],\n pageSize: record[\"pageSize\"],\n indexValues: record[\"indexValues\"],\n });\n } catch (error) {\n throw new Error(`Invalid cursor: ${error instanceof Error ? error.message : \"malformed data\"}`);\n }\n}\n\n/**\n * Create a cursor from a record and pagination metadata\n *\n * @param record - The database record\n * @param indexColumns - The columns that make up the index\n * @param metadata - Pagination metadata (index name, order direction, page size)\n * @returns Cursor object\n *\n * @example\n * ```ts\n * const cursor = createCursorFromRecord(\n * { id: \"abc\", name: \"Alice\", createdAt: 123 },\n * [table.columns.createdAt, table.columns.id],\n * {\n * indexName: \"idx_created\",\n * orderDirection: \"asc\",\n * pageSize: 10\n * }\n * );\n * ```\n */\nexport function createCursorFromRecord(\n record: Record<string, unknown>,\n indexColumns: AnyColumn[],\n metadata: {\n indexName: string;\n orderDirection: \"asc\" | \"desc\";\n pageSize: number;\n },\n): Cursor {\n const indexValues: Record<string, unknown> = {};\n\n for (const col of indexColumns) {\n const value = record[col.name];\n if (value === undefined) {\n throw new Error(`Record is missing value for index column \"${col.name}\".`);\n }\n // Resolve FragnoId/FragnoReference to primitive values for cursor serialization.\n const resolved = resolveFragnoIdValue(value, col);\n // BigInt values are not JSON-serializable, so store them as strings.\n indexValues[col.name] = typeof resolved === \"bigint\" ? resolved.toString() : resolved;\n }\n\n return new Cursor({\n indexName: metadata.indexName,\n orderDirection: metadata.orderDirection,\n pageSize: metadata.pageSize,\n indexValues,\n });\n}\n\n/**\n * Serialize cursor values for database queries\n *\n * Converts cursor values (which are in JSON-compatible format after decode)\n * to database format using the column serialization rules.\n *\n * This function performs a two-step process:\n * 1. Deserialize from JSON format to application format (e.g., ISO string → Date)\n * 2. Serialize from application format to database format (e.g., Date → driver format)\n *\n * @param cursor - The cursor object\n * @param indexColumns - The columns that make up the index\n * @param driverConfig - The driver configuration\n * @param sqliteStorageMode - Optional SQLite storage mode override\n * @returns Serialized values ready for database queries\n *\n * @example\n * ```ts\n * const serialized = serializeCursorValues(\n * cursor,\n * [table.columns.createdAt],\n * driverConfig\n * );\n * ```\n */\nexport function serializeCursorValues(\n cursor: Cursor,\n indexColumns: AnyColumn[],\n driverConfig: DriverConfig,\n sqliteStorageMode?: SQLiteStorageMode,\n): Record<string, unknown> {\n const serializer = createSQLSerializer(driverConfig, sqliteStorageMode);\n const serialized: Record<string, unknown> = {};\n const missingColumns: string[] = [];\n\n for (const col of indexColumns) {\n const value = cursor.indexValues[col.name];\n if (value === undefined) {\n missingColumns.push(col.name);\n continue;\n }\n\n // If the cursor value is already a FragnoId/FragnoReference, resolve it directly\n // to avoid deserializing non-JSON objects.\n const resolvedDirect = resolveFragnoIdValue(value, col);\n if (resolvedDirect !== value) {\n serialized[col.name] = serializer.serialize(resolvedDirect, col);\n continue;\n }\n\n // First deserialize from JSON format to application format\n // (e.g., \"2025-11-07T09:36:57.959Z\" string → Date object)\n const deserialized = serializer.deserialize(value, col);\n // Resolve FragnoId/FragnoReference to primitive values (if present)\n const resolved = resolveFragnoIdValue(deserialized, col);\n // Then serialize to database format\n // (e.g., Date → database driver format)\n serialized[col.name] = serializer.serialize(resolved, col);\n }\n\n if (missingColumns.length > 0) {\n const suffix = cursor.indexName ? ` for index \"${cursor.indexName}\"` : \"\";\n const columns = missingColumns.map((name) => `\"${name}\"`).join(\", \");\n const plural = missingColumns.length === 1 ? \"\" : \"s\";\n throw new Error(`Cursor is missing values for index column${plural} ${columns}${suffix}.`);\n }\n\n return serialized;\n}\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst assertSerializableIndexValues = (values: Record<string, unknown>): void => {\n for (const [key, value] of Object.entries(values)) {\n if (value === undefined) {\n throw new Error(`Cursor index value \"${key}\" is undefined.`);\n }\n if (typeof value === \"number\" && !Number.isFinite(value)) {\n throw new Error(`Cursor index value \"${key}\" must be a finite number.`);\n }\n if (typeof value === \"bigint\") {\n throw new Error(`Cursor index value \"${key}\" must not be a BigInt.`);\n }\n if (typeof value === \"function\" || typeof value === \"symbol\") {\n throw new Error(`Cursor index value \"${key}\" is not JSON-serializable.`);\n }\n }\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/query/db-now.d.ts
|
|
2
|
+
type DbInterval = {
|
|
3
|
+
tag: "db-interval";
|
|
4
|
+
ms: number;
|
|
5
|
+
};
|
|
6
|
+
type DbIntervalInput = number | {
|
|
7
|
+
ms?: number;
|
|
8
|
+
seconds?: number;
|
|
9
|
+
minutes?: number;
|
|
10
|
+
hours?: number;
|
|
11
|
+
days?: number;
|
|
12
|
+
};
|
|
13
|
+
type DbNow = {
|
|
14
|
+
tag: "db-now";
|
|
15
|
+
offsetMs?: number;
|
|
16
|
+
plus: (interval: DbInterval | DbIntervalInput) => DbNow;
|
|
17
|
+
};
|
|
18
|
+
declare const dbNow: () => DbNow;
|
|
19
|
+
declare const dbInterval: (input: DbIntervalInput) => DbInterval;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { DbInterval, DbIntervalInput, DbNow, dbInterval, dbNow };
|
|
22
|
+
//# sourceMappingURL=db-now.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-now.d.ts","names":[],"sources":["../../src/query/db-now.ts"],"sourcesContent":[],"mappings":";KAAY,UAAA;EAAA,GAAA,EAAA,aAAU;EAEV,EAAA,EAAA,MAAA;AAUZ,CAAA;AAGmB,KAbP,eAAA,GAaO,MAAA,GAAA;EAAa,EAAA,CAAA,EAAA,MAAA;EAAoB,OAAA,CAAA,EAAA,MAAA;EAAK,OAAA,CAAA,EAAA,MAAA;EA2C5C,KAAA,CAAA,EAAmC,MAAA;EAQnC,IAAA,CAAA,EAAA,MAGX;;KAzDU,KAAA;;;mBAGO,aAAa,oBAAoB;;cA2CvC,aAAY;cAQZ,oBAAqB,oBAAkB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/query/db-now.ts
|
|
2
|
+
const toIntervalMs = (input) => {
|
|
3
|
+
if (typeof input === "number") {
|
|
4
|
+
if (!Number.isFinite(input)) throw new Error("DB_INTERVAL_INVALID");
|
|
5
|
+
return input;
|
|
6
|
+
}
|
|
7
|
+
if (typeof input === "object" && input !== null && "tag" in input) {
|
|
8
|
+
const tagged = input;
|
|
9
|
+
if (tagged.tag === "db-interval") {
|
|
10
|
+
if (!Number.isFinite(tagged.ms)) throw new Error("DB_INTERVAL_INVALID");
|
|
11
|
+
return tagged.ms;
|
|
12
|
+
}
|
|
13
|
+
throw new Error("DB_INTERVAL_INVALID");
|
|
14
|
+
}
|
|
15
|
+
const interval = input;
|
|
16
|
+
const totalMs = (interval.ms ?? 0) + (interval.seconds ?? 0) * 1e3 + (interval.minutes ?? 0) * 6e4 + (interval.hours ?? 0) * 36e5 + (interval.days ?? 0) * 864e5;
|
|
17
|
+
if (!Number.isFinite(totalMs)) throw new Error("DB_INTERVAL_INVALID");
|
|
18
|
+
return totalMs;
|
|
19
|
+
};
|
|
20
|
+
const createDbNow = (offsetMs = 0) => ({
|
|
21
|
+
tag: "db-now",
|
|
22
|
+
offsetMs,
|
|
23
|
+
plus: (interval) => createDbNow(offsetMs + toIntervalMs(interval))
|
|
24
|
+
});
|
|
25
|
+
const dbNow = () => createDbNow(0);
|
|
26
|
+
const isDbNow = (value) => typeof value === "object" && value !== null && value.tag === "db-now";
|
|
27
|
+
const getDbNowOffsetMs = (value) => typeof value.offsetMs === "number" ? value.offsetMs : 0;
|
|
28
|
+
const dbInterval = (input) => ({
|
|
29
|
+
tag: "db-interval",
|
|
30
|
+
ms: toIntervalMs(input)
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { dbInterval, dbNow, getDbNowOffsetMs, isDbNow };
|
|
35
|
+
//# sourceMappingURL=db-now.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-now.js","names":[],"sources":["../../src/query/db-now.ts"],"sourcesContent":["export type DbInterval = { tag: \"db-interval\"; ms: number };\n\nexport type DbIntervalInput =\n | number\n | {\n ms?: number;\n seconds?: number;\n minutes?: number;\n hours?: number;\n days?: number;\n };\n\nexport type DbNow = {\n tag: \"db-now\";\n offsetMs?: number;\n plus: (interval: DbInterval | DbIntervalInput) => DbNow;\n};\n\nconst toIntervalMs = (input: DbInterval | DbIntervalInput): number => {\n if (typeof input === \"number\") {\n if (!Number.isFinite(input)) {\n throw new Error(\"DB_INTERVAL_INVALID\");\n }\n return input;\n }\n\n if (typeof input === \"object\" && input !== null && \"tag\" in input) {\n const tagged = input as DbInterval;\n if (tagged.tag === \"db-interval\") {\n if (!Number.isFinite(tagged.ms)) {\n throw new Error(\"DB_INTERVAL_INVALID\");\n }\n return tagged.ms;\n }\n throw new Error(\"DB_INTERVAL_INVALID\");\n }\n\n const interval = input as Exclude<DbIntervalInput, number>;\n const totalMs =\n (interval.ms ?? 0) +\n (interval.seconds ?? 0) * 1000 +\n (interval.minutes ?? 0) * 60_000 +\n (interval.hours ?? 0) * 3_600_000 +\n (interval.days ?? 0) * 86_400_000;\n\n if (!Number.isFinite(totalMs)) {\n throw new Error(\"DB_INTERVAL_INVALID\");\n }\n\n return totalMs;\n};\n\nconst createDbNow = (offsetMs = 0): DbNow => ({\n tag: \"db-now\",\n offsetMs,\n plus: (interval) => createDbNow(offsetMs + toIntervalMs(interval)),\n});\n\nexport const dbNow = (): DbNow => createDbNow(0);\n\nexport const isDbNow = (value: unknown): value is DbNow =>\n typeof value === \"object\" && value !== null && (value as { tag?: string }).tag === \"db-now\";\n\nexport const getDbNowOffsetMs = (value: DbNow): number =>\n typeof value.offsetMs === \"number\" ? value.offsetMs : 0;\n\nexport const dbInterval = (input: DbIntervalInput): DbInterval => ({\n tag: \"db-interval\",\n ms: toIntervalMs(input),\n});\n\nexport const isDbInterval = (value: unknown): value is DbInterval =>\n typeof value === \"object\" && value !== null && (value as { tag?: string }).tag === \"db-interval\";\n"],"mappings":";AAkBA,MAAM,gBAAgB,UAAgD;AACpE,KAAI,OAAO,UAAU,UAAU;AAC7B,MAAI,CAAC,OAAO,SAAS,MAAM,CACzB,OAAM,IAAI,MAAM,sBAAsB;AAExC,SAAO;;AAGT,KAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,SAAS,OAAO;EACjE,MAAM,SAAS;AACf,MAAI,OAAO,QAAQ,eAAe;AAChC,OAAI,CAAC,OAAO,SAAS,OAAO,GAAG,CAC7B,OAAM,IAAI,MAAM,sBAAsB;AAExC,UAAO,OAAO;;AAEhB,QAAM,IAAI,MAAM,sBAAsB;;CAGxC,MAAM,WAAW;CACjB,MAAM,WACH,SAAS,MAAM,MACf,SAAS,WAAW,KAAK,OACzB,SAAS,WAAW,KAAK,OACzB,SAAS,SAAS,KAAK,QACvB,SAAS,QAAQ,KAAK;AAEzB,KAAI,CAAC,OAAO,SAAS,QAAQ,CAC3B,OAAM,IAAI,MAAM,sBAAsB;AAGxC,QAAO;;AAGT,MAAM,eAAe,WAAW,OAAc;CAC5C,KAAK;CACL;CACA,OAAO,aAAa,YAAY,WAAW,aAAa,SAAS,CAAC;CACnE;AAED,MAAa,cAAqB,YAAY,EAAE;AAEhD,MAAa,WAAW,UACtB,OAAO,UAAU,YAAY,UAAU,QAAS,MAA2B,QAAQ;AAErF,MAAa,oBAAoB,UAC/B,OAAO,MAAM,aAAa,WAAW,MAAM,WAAW;AAExD,MAAa,cAAc,WAAwC;CACjE,KAAK;CACL,IAAI,aAAa,MAAM;CACxB"}
|