diff --git a/.github/workflows/release-fork-to-npm.yaml b/.github/workflows/release-fork-to-npm.yaml new file mode 100644 index 000000000..376a19826 --- /dev/null +++ b/.github/workflows/release-fork-to-npm.yaml @@ -0,0 +1,178 @@ +name: Release fork to NPM + +on: + push: + branches: + - main + workflow_dispatch: + +env: + PACKAGE_VERSION: 0.32.1-${{ github.sha }} + +jobs: + release: + permissions: write-all + strategy: + fail-fast: false + matrix: + package: + - drizzle-orm + runs-on: ubuntu-20.04 + services: + postgres-postgis: + image: postgis/postgis:16-3.4 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: drizzle + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 54322:5432 + postgres-vector: + image: pgvector/pgvector:pg16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: drizzle + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 54321:5432 + postgres: + image: postgres:14 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: drizzle + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 55433:5432 + mysql: + image: mysql:8 + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: drizzle + options: >- + --health-cmd "mysqladmin ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 33306:3306 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '18.18' + registry-url: 'https://registry.npmjs.org' + + - uses: pnpm/action-setup@v3 + name: Install pnpm + id: pnpm-install + with: + version: latest + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Generate prisma schemas + run: | + ( + cd drizzle-orm + pnpm prisma generate --schema src/prisma/schema.prisma + ) + ( + cd integration-tests + pnpm prisma generate --schema tests/prisma/pg/schema.prisma + pnpm prisma generate --schema tests/prisma/mysql/schema.prisma + pnpm prisma generate --schema tests/prisma/sqlite/schema.prisma + ) + + - name: Build + working-directory: ${{ matrix.package }} + run: | + jq '.name="@drodrigues4/${{ matrix.package }}"' package.json --tab > tmp.$$.json && mv tmp.$$.json package.json + jq --arg version "$PACKAGE_VERSION" '.version=$version' package.json --tab > tmp.$$.json && mv tmp.$$.json package.json + jq 'del(.publishConfig.provenance)' package.json --tab > tmp.$$.json && mv tmp.$$.json package.json + + pnpm build + + - name: Run tests + if: 0 + env: + PG_CONNECTION_STRING: postgres://postgres:postgres@localhost:55433/drizzle + PG_VECTOR_CONNECTION_STRING: postgres://postgres:postgres@localhost:54321/drizzle + PG_POSTGIS_CONNECTION_STRING: postgres://postgres:postgres@localhost:54322/drizzle + MYSQL_CONNECTION_STRING: mysql://root:root@localhost:33306/drizzle + PLANETSCALE_CONNECTION_STRING: ${{ secrets.PLANETSCALE_CONNECTION_STRING }} + NEON_CONNECTION_STRING: ${{ secrets.NEON_CONNECTION_STRING }} + TIDB_CONNECTION_STRING: ${{ secrets.TIDB_CONNECTION_STRING }} + XATA_API_KEY: ${{ secrets.XATA_API_KEY }} + XATA_BRANCH: ${{ secrets.XATA_BRANCH }} + LIBSQL_URL: file:local.db + run: | + if [[ "${{ matrix.package }}" == "drizzle-orm" ]]; then + pnpm test --filter ${{ matrix.package }} --filter integration-tests + else + pnpm test --filter ${{ matrix.package }} + fi + + - name: Pack + working-directory: ${{ matrix.package }} + shell: bash + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} + run: | + npm run pack + + - name: Run @arethetypeswrong/cli + if: 0 + working-directory: ${{ matrix.package }} + shell: bash + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }} + run: | + pnpm attw package.tgz + + - name: Publish + working-directory: ${{ matrix.package }} + shell: bash + env: + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} + run: | + echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc + + echo "Publishing ${{ matrix.package }}@$PACKAGE_VERSION" + npm publish package.tgz --access public + + echo "npm: \`+ ${{ matrix.package }}@$PACKAGE_VERSION\`" >> $GITHUB_STEP_SUMMARY + + # Post release message to Discord + # curl -X POST -H "Content-Type: application/json" -d "{\"embeds\": [{\"title\": \"New \`${{ matrix.package }}\` release! 🎉\", \"url\": \"https://www.npmjs.com/package/${{ matrix.package }}\", \"color\": \"12907856\", \"fields\": [{\"name\": \"Tag\", \"value\": \"\`$tag\`\"}]}]}" ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }} diff --git a/changelogs/drizzle-kit/0.25.0.md b/changelogs/drizzle-kit/0.25.0.md new file mode 100644 index 000000000..fc4b36c83 --- /dev/null +++ b/changelogs/drizzle-kit/0.25.0.md @@ -0,0 +1,144 @@ +## Breaking changes and migrate guide for Turso users + +If you are using Turso and libsql, you will need to upgrade your `drizzle.config` and `@libsql/client` package. + +1. This version of drizzle-orm will only work with `@libsql/client@0.10.0` or higher if you are using the `migrate` function. For other use cases, you can continue using previous versions(But the suggestion is to upgrade) +To install the latest version, use the command: + +```bash +npm i @libsql/client@latest +``` + +2. Previously, we had a common `drizzle.config` for SQLite and Turso users, which allowed a shared strategy for both dialects. Starting with this release, we are introducing the turso dialect in drizzle-kit. We will evolve and improve Turso as a separate dialect with its own migration strategies. + +**Before** + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "sqlite", + schema: "./schema.ts", + out: "./drizzle", + dbCredentials: { + url: "database.db", + }, + breakpoints: true, + verbose: true, + strict: true, +}); +``` + +**After** + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "turso", + schema: "./schema.ts", + out: "./drizzle", + dbCredentials: { + url: "database.db", + }, + breakpoints: true, + verbose: true, + strict: true, +}); +``` + +If you are using only SQLite, you can use `dialect: "sqlite"` + +## LibSQL/Turso and Sqlite migration updates + +### SQLite "generate" and "push" statements updates + +Starting from this release, we will no longer generate comments like this: + +```sql + '/*\n SQLite does not support "Changing existing column type" out of the box, we do not generate automatic migration for that, so it has to be done manually' + + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' + + '\n https://www.sqlite.org/lang_altertable.html' + + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' + + "\n\n Due to that we don't generate migration automatically and it has to be done manually" + + '\n*/' +``` + +We will generate a set of statements, and you can decide if it's appropriate to create data-moving statements instead. Here is an example of the SQL file you'll receive now: + +```sql +PRAGMA foreign_keys=OFF; +--> statement-breakpoint +CREATE TABLE `__new_worker` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `salary` text NOT NULL, + `job_id` integer, + FOREIGN KEY (`job_id`) REFERENCES `job`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_worker`("id", "name", "salary", "job_id") SELECT "id", "name", "salary", "job_id" FROM `worker`; +--> statement-breakpoint +DROP TABLE `worker`; +--> statement-breakpoint +ALTER TABLE `__new_worker` RENAME TO `worker`; +--> statement-breakpoint +PRAGMA foreign_keys=ON; +``` + +### LibSQL/Turso "generate" and "push" statements updates + +Since LibSQL supports more ALTER statements than SQLite, we can generate more statements without recreating your schema and moving all the data, which can be potentially dangerous for production environments. + +LibSQL and Turso will now have a separate dialect in the Drizzle config file, meaning that we will evolve Turso and LibSQL independently from SQLite and will aim to support as many features as Turso/LibSQL offer. + +With the updated LibSQL migration strategy, you will have the ability to: + +- **Change Data Type**: Set a new data type for existing columns. +- **Set and Drop Default Values**: Add or remove default values for existing columns. +- **Set and Drop NOT NULL**: Add or remove the NOT NULL constraint on existing columns. +- **Add References to Existing Columns**: Add foreign key references to existing columns + +You can find more information in the [LibSQL documentation](https://github.com/tursodatabase/libsql/blob/main/libsql-sqlite3/doc/libsql_extensions.md#altering-columns) + +### LIMITATIONS + +- Dropping or altering an index will cause table recreation. + +This is because LibSQL/Turso does not support dropping this type of index. + +```sql +CREATE TABLE `users` ( + `id` integer NOT NULL, + `name` integer, + `age` integer PRIMARY KEY NOT NULL + FOREIGN KEY (`name`) REFERENCES `users1`("id") ON UPDATE no action ON DELETE no action +); +``` + +- If the table has indexes, altering columns will cause table recreation. +- Drizzle-Kit will drop the indexes, modify the columns, and then recreate the indexes. +- Adding or dropping composite foreign keys is not supported and will cause table recreation + +### NOTES + +- You can create a reference on any column type, but if you want to insert values, the referenced column must have a unique index or primary key. + +```sql +CREATE TABLE parent(a PRIMARY KEY, b UNIQUE, c, d, e, f); +CREATE UNIQUE INDEX i1 ON parent(c, d); +CREATE INDEX i2 ON parent(e); +CREATE UNIQUE INDEX i3 ON parent(f COLLATE nocase); + +CREATE TABLE child1(f, g REFERENCES parent(a)); -- Ok +CREATE TABLE child2(h, i REFERENCES parent(b)); -- Ok +CREATE TABLE child3(j, k, FOREIGN KEY(j, k) REFERENCES parent(c, d)); -- Ok +CREATE TABLE child4(l, m REFERENCES parent(e)); -- Error! +CREATE TABLE child5(n, o REFERENCES parent(f)); -- Error! +CREATE TABLE child6(p, q, FOREIGN KEY(p, q) REFERENCES parent(b, c)); -- Error! +CREATE TABLE child7(r REFERENCES parent(c)); -- Error! +``` + +> **NOTE**: The foreign key for the table child5 is an error because, although the parent key column has a unique index, the index uses a different collating sequence. + +See more: https://www.sqlite.org/foreignkeys.html \ No newline at end of file diff --git a/changelogs/drizzle-orm/0.34.0.md b/changelogs/drizzle-orm/0.34.0.md new file mode 100644 index 000000000..490422628 --- /dev/null +++ b/changelogs/drizzle-orm/0.34.0.md @@ -0,0 +1,255 @@ +## Breaking changes and migrate guide for Turso users + +If you are using Turso and libsql, you will need to upgrade your `drizzle.config` and `@libsql/client` package. + +1. This version of drizzle-orm will only work with `@libsql/client@0.10.0` or higher if you are using the `migrate` function. For other use cases, you can continue using previous versions(But the suggestion is to upgrade) +To install the latest version, use the command: + +```bash +npm i @libsql/client@latest +``` + +2. Previously, we had a common `drizzle.config` for SQLite and Turso users, which allowed a shared strategy for both dialects. Starting with this release, we are introducing the turso dialect in drizzle-kit. We will evolve and improve Turso as a separate dialect with its own migration strategies. + +**Before** + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "sqlite", + schema: "./schema.ts", + out: "./drizzle", + dbCredentials: { + url: "database.db", + }, + breakpoints: true, + verbose: true, + strict: true, +}); +``` + +**After** + +```ts +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "turso", + schema: "./schema.ts", + out: "./drizzle", + dbCredentials: { + url: "database.db", + }, + breakpoints: true, + verbose: true, + strict: true, +}); +``` + +If you are using only SQLite, you can use `dialect: "sqlite"` + +## LibSQL/Turso and Sqlite migration updates + +### SQLite "generate" and "push" statements updates + +Starting from this release, we will no longer generate comments like this: + +```sql + '/*\n SQLite does not support "Changing existing column type" out of the box, we do not generate automatic migration for that, so it has to be done manually' + + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' + + '\n https://www.sqlite.org/lang_altertable.html' + + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' + + "\n\n Due to that we don't generate migration automatically and it has to be done manually" + + '\n*/' +``` + +We will generate a set of statements, and you can decide if it's appropriate to create data-moving statements instead. Here is an example of the SQL file you'll receive now: + +```sql +PRAGMA foreign_keys=OFF; +--> statement-breakpoint +CREATE TABLE `__new_worker` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `salary` text NOT NULL, + `job_id` integer, + FOREIGN KEY (`job_id`) REFERENCES `job`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_worker`("id", "name", "salary", "job_id") SELECT "id", "name", "salary", "job_id" FROM `worker`; +--> statement-breakpoint +DROP TABLE `worker`; +--> statement-breakpoint +ALTER TABLE `__new_worker` RENAME TO `worker`; +--> statement-breakpoint +PRAGMA foreign_keys=ON; +``` + +### LibSQL/Turso "generate" and "push" statements updates + +Since LibSQL supports more ALTER statements than SQLite, we can generate more statements without recreating your schema and moving all the data, which can be potentially dangerous for production environments. + +LibSQL and Turso will now have a separate dialect in the Drizzle config file, meaning that we will evolve Turso and LibSQL independently from SQLite and will aim to support as many features as Turso/LibSQL offer. + +With the updated LibSQL migration strategy, you will have the ability to: + +- **Change Data Type**: Set a new data type for existing columns. +- **Set and Drop Default Values**: Add or remove default values for existing columns. +- **Set and Drop NOT NULL**: Add or remove the NOT NULL constraint on existing columns. +- **Add References to Existing Columns**: Add foreign key references to existing columns + +You can find more information in the [LibSQL documentation](https://github.com/tursodatabase/libsql/blob/main/libsql-sqlite3/doc/libsql_extensions.md#altering-columns) + +### LIMITATIONS + +- Dropping or altering an index will cause table recreation. + +This is because LibSQL/Turso does not support dropping this type of index. + +```sql +CREATE TABLE `users` ( + `id` integer NOT NULL, + `name` integer, + `age` integer PRIMARY KEY NOT NULL + FOREIGN KEY (`name`) REFERENCES `users1`("id") ON UPDATE no action ON DELETE no action +); +``` + +- If the table has indexes, altering columns will cause table recreation. +- Drizzle-Kit will drop the indexes, modify the columns, and then recreate the indexes. +- Adding or dropping composite foreign keys is not supported and will cause table recreation + +### NOTES + +- You can create a reference on any column type, but if you want to insert values, the referenced column must have a unique index or primary key. + +```sql +CREATE TABLE parent(a PRIMARY KEY, b UNIQUE, c, d, e, f); +CREATE UNIQUE INDEX i1 ON parent(c, d); +CREATE INDEX i2 ON parent(e); +CREATE UNIQUE INDEX i3 ON parent(f COLLATE nocase); + +CREATE TABLE child1(f, g REFERENCES parent(a)); -- Ok +CREATE TABLE child2(h, i REFERENCES parent(b)); -- Ok +CREATE TABLE child3(j, k, FOREIGN KEY(j, k) REFERENCES parent(c, d)); -- Ok +CREATE TABLE child4(l, m REFERENCES parent(e)); -- Error! +CREATE TABLE child5(n, o REFERENCES parent(f)); -- Error! +CREATE TABLE child6(p, q, FOREIGN KEY(p, q) REFERENCES parent(b, c)); -- Error! +CREATE TABLE child7(r REFERENCES parent(c)); -- Error! +``` + +> **NOTE**: The foreign key for the table child5 is an error because, although the parent key column has a unique index, the index uses a different collating sequence. + +See more: https://www.sqlite.org/foreignkeys.html + +## A new and easy way to start using drizzle + +Current and the only way to do, is to define client yourself and pass it to drizzle + +```ts +const client = new Pool({ url: '' }); +drizzle(client, { logger: true }); +``` + +But we want to introduce you to a new API, which is a simplified method in addition to the existing one. + +Most clients will have a few options to connect, starting with the easiest and most common one, and allowing you to control your client connection as needed. + +Let's use `node-postgres` as an example, but the same pattern can be applied to all other clients + +```ts +// Finally, one import for all available clients and dialects! +import { drizzle } from 'drizzle-orm' + +// Choose a client and use a connection URL — nothing else is needed! +const db1 = await drizzle("node-postgres", process.env.POSTGRES_URL); + +// If you need to pass a logger, schema, or other configurations, you can use an object and specify the client-specific URL in the connection +const db2 = await drizzle("node-postgres", { + connection: process.env.POSTGRES_URL, + logger: true +}); + +// And finally, if you need to use full client/driver-specific types in connections, you can use a URL or host/port/etc. as an object inferred from the underlying client connection types +const db3 = await drizzle("node-postgres", { + connection: { + connectionString: process.env.POSTGRES_URL, + }, +}); + +const db4 = await drizzle("node-postgres", { + connection: { + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + database: process.env.DB_NAME, + ssl: true, + }, +}); +``` + +A few clients will have a slightly different API due to their specific behavior. Let's take a look at them: + +For `aws-data-api-pg`, Drizzle will require `resourceArn`, `database`, and `secretArn`, along with any other AWS Data API client types for the connection, such as credentials, region, etc. + +```ts +drizzle("aws-data-api-pg", { + connection: { + resourceArn: "", + database: "", + secretArn: "", + }, +}); +``` + +For `d1`, the Cloudflare Worker types as described in the [documentation](https://developers.cloudflare.com/d1/get-started/) here will be required. + +```ts +drizzle("d1", { + connection: env.DB // Cloudflare Worker Types +}) +``` + +For `vercel-postgres`, nothing is needed since Vercel automatically retrieves the `POSTGRES_URL` from the `.env` file. You can check this [documentation](https://vercel.com/docs/storage/vercel-postgres/quickstart) for more info + +```ts +drizzle("vercel-postgres") +``` + +> Note that the first example with the client is still available and not deprecated. You can use it if you don't want to await the drizzle object. The new way of defining drizzle is designed to make it easier to import from one place and get autocomplete for all the available clients + +## New "count" API + +Befor this release to count entities in a table, you would need to do this: + +```ts +const res = await db.select({ count: sql`count(*)` }).from(users); +const count = res[0].count; +``` + +The new API will look like this: + +```ts +// how many users are in the database +const count: number = await db.$count(users); + +// how many users with the name "Dan" are in the database +const count: number = await db.$count(users, eq(name, "Dan")); +``` + +This can also work as a subquery and within relational queries + +```ts +const users = await db.select({ + ...users, + postsCount: db.$count(posts, eq(posts.authorId, users.id)) +}); + +const users = await db.query.users.findMany({ + extras: { + postsCount: db.$count(posts, eq(posts.authorId, users.id)) + } +}) +``` diff --git a/drizzle-kit/build.ts b/drizzle-kit/build.ts index 701e9c84c..ec7fc76c0 100644 --- a/drizzle-kit/build.ts +++ b/drizzle-kit/build.ts @@ -1,3 +1,4 @@ +/// import * as esbuild from 'esbuild'; import { readFileSync, writeFileSync } from 'node:fs'; import * as tsup from 'tsup'; @@ -16,6 +17,7 @@ const driversPackages = [ // sqlite drivers '@libsql/client', 'better-sqlite3', + 'bun:sqlite', ]; esbuild.buildSync({ @@ -82,6 +84,7 @@ const main = async () => { await tsup.build({ entryPoints: ['./src/index.ts', './src/api.ts'], outDir: './dist', + external: ['bun:sqlite'], splitting: false, dts: true, format: ['cjs', 'esm'], diff --git a/drizzle-kit/package.json b/drizzle-kit/package.json index 9d9e1d227..66f19e6be 100644 --- a/drizzle-kit/package.json +++ b/drizzle-kit/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-kit", - "version": "0.24.2", + "version": "0.25.0", "homepage": "https://orm.drizzle.team", "keywords": [ "drizzle", @@ -54,7 +54,7 @@ "@electric-sql/pglite": "^0.1.5", "@hono/node-server": "^1.9.0", "@hono/zod-validator": "^0.2.1", - "@libsql/client": "^0.4.2", + "@libsql/client": "^0.10.0", "@neondatabase/serverless": "^0.9.1", "@originjs/vite-plugin-commonjs": "^1.0.3", "@planetscale/database": "^1.16.0", @@ -74,6 +74,7 @@ "@vercel/postgres": "^0.8.0", "ava": "^5.1.0", "better-sqlite3": "^9.4.3", + "bun-types": "^0.6.6", "camelcase": "^7.0.1", "chalk": "^5.2.0", "commander": "^12.1.0", diff --git a/drizzle-kit/pnpm-lock.yaml b/drizzle-kit/pnpm-lock.yaml deleted file mode 100644 index 8f4d58f55..000000000 --- a/drizzle-kit/pnpm-lock.yaml +++ /dev/null @@ -1,7603 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -patchedDependencies: - difflib@0.2.4: - hash: jq4t3ysdpnbunjeje4v7nrqn2q - path: patches/difflib@0.2.4.patch - -importers: - - .: - dependencies: - '@esbuild-kit/esm-loader': - specifier: ^2.5.5 - version: 2.6.5 - esbuild: - specifier: ^0.19.7 - version: 0.19.12 - esbuild-register: - specifier: ^3.5.0 - version: 3.5.0(esbuild@0.19.12) - devDependencies: - '@arethetypeswrong/cli': - specifier: ^0.15.3 - version: 0.15.3 - '@aws-sdk/client-rds-data': - specifier: ^3.556.0 - version: 3.577.0 - '@cloudflare/workers-types': - specifier: ^4.20230518.0 - version: 4.20240512.0 - '@electric-sql/pglite': - specifier: ^0.1.5 - version: 0.1.5 - '@hono/node-server': - specifier: ^1.9.0 - version: 1.11.1 - '@hono/zod-validator': - specifier: ^0.2.1 - version: 0.2.1(hono@4.3.9)(zod@3.23.8) - '@libsql/client': - specifier: ^0.4.2 - version: 0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3) - '@neondatabase/serverless': - specifier: ^0.9.1 - version: 0.9.3 - '@originjs/vite-plugin-commonjs': - specifier: ^1.0.3 - version: 1.0.3 - '@planetscale/database': - specifier: ^1.16.0 - version: 1.18.0 - '@types/better-sqlite3': - specifier: ^7.6.4 - version: 7.6.10 - '@types/dockerode': - specifier: ^3.3.28 - version: 3.3.29 - '@types/glob': - specifier: ^8.1.0 - version: 8.1.0 - '@types/json-diff': - specifier: ^1.0.3 - version: 1.0.3 - '@types/minimatch': - specifier: ^5.1.2 - version: 5.1.2 - '@types/node': - specifier: ^18.11.15 - version: 18.19.33 - '@types/pg': - specifier: ^8.10.7 - version: 8.11.6 - '@types/pluralize': - specifier: ^0.0.33 - version: 0.0.33 - '@types/semver': - specifier: ^7.5.5 - version: 7.5.8 - '@types/uuid': - specifier: ^9.0.8 - version: 9.0.8 - '@types/ws': - specifier: ^8.5.10 - version: 8.5.10 - '@typescript-eslint/eslint-plugin': - specifier: ^7.2.0 - version: 7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/parser': - specifier: ^7.2.0 - version: 7.10.0(eslint@8.57.0)(typescript@5.4.5) - '@vercel/postgres': - specifier: ^0.8.0 - version: 0.8.0 - ava: - specifier: ^5.1.0 - version: 5.3.1 - better-sqlite3: - specifier: ^9.4.3 - version: 9.6.0 - camelcase: - specifier: ^7.0.1 - version: 7.0.1 - chalk: - specifier: ^5.2.0 - version: 5.3.0 - commander: - specifier: ^12.1.0 - version: 12.1.0 - dockerode: - specifier: ^3.3.4 - version: 3.3.5 - dotenv: - specifier: ^16.0.3 - version: 16.4.5 - drizzle-kit: - specifier: 0.21.2 - version: 0.21.2 - drizzle-orm: - specifier: 0.32.0-85c8008 - version: 0.32.0-85c8008(@aws-sdk/client-rds-data@3.577.0)(@cloudflare/workers-types@4.20240512.0)(@electric-sql/pglite@0.1.5)(@libsql/client@0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3))(@neondatabase/serverless@0.9.3)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@vercel/postgres@0.8.0)(better-sqlite3@9.6.0)(mysql2@2.3.3)(pg@8.11.5)(postgres@3.4.4) - env-paths: - specifier: ^3.0.0 - version: 3.0.0 - esbuild-node-externals: - specifier: ^1.9.0 - version: 1.13.1(esbuild@0.19.12) - eslint: - specifier: ^8.57.0 - version: 8.57.0 - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.0) - eslint-plugin-prettier: - specifier: ^5.1.3 - version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8) - get-port: - specifier: ^6.1.2 - version: 6.1.2 - glob: - specifier: ^8.1.0 - version: 8.1.0 - hanji: - specifier: ^0.0.5 - version: 0.0.5 - hono: - specifier: ^4.1.5 - version: 4.3.9 - json-diff: - specifier: 1.0.6 - version: 1.0.6 - minimatch: - specifier: ^7.4.3 - version: 7.4.6 - mysql2: - specifier: 2.3.3 - version: 2.3.3 - node-fetch: - specifier: ^3.3.2 - version: 3.3.2 - pg: - specifier: ^8.11.5 - version: 8.11.5 - pluralize: - specifier: ^8.0.0 - version: 8.0.0 - postgres: - specifier: ^3.4.4 - version: 3.4.4 - prettier: - specifier: ^2.8.1 - version: 2.8.8 - semver: - specifier: ^7.5.4 - version: 7.6.2 - superjson: - specifier: ^2.2.1 - version: 2.2.1 - tsup: - specifier: ^8.0.2 - version: 8.0.2(postcss@8.4.38)(typescript@5.4.5) - tsx: - specifier: ^3.12.1 - version: 3.14.0 - typescript: - specifier: ^5.4.3 - version: 5.4.5 - uuid: - specifier: ^9.0.1 - version: 9.0.1 - vite-tsconfig-paths: - specifier: ^4.3.2 - version: 4.3.2(typescript@5.4.5)(vite@5.2.11(@types/node@18.19.33)) - vitest: - specifier: ^1.4.0 - version: 1.6.0(@types/node@18.19.33) - wrangler: - specifier: ^3.22.1 - version: 3.57.0(@cloudflare/workers-types@4.20240512.0)(bufferutil@4.0.8)(utf-8-validate@6.0.3) - ws: - specifier: ^8.16.0 - version: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) - zod: - specifier: ^3.20.2 - version: 3.23.8 - zx: - specifier: ^7.2.2 - version: 7.2.3 - -packages: - - '@andrewbranch/untar.js@1.0.3': - resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==} - - '@arethetypeswrong/cli@0.15.3': - resolution: {integrity: sha512-sIMA9ZJBWDEg1+xt5RkAEflZuf8+PO8SdKj17x6PtETuUho+qlZJg4DgmKc3q+QwQ9zOB5VLK6jVRbFdNLdUIA==} - engines: {node: '>=18'} - hasBin: true - - '@arethetypeswrong/core@0.15.1': - resolution: {integrity: sha512-FYp6GBAgsNz81BkfItRz8RLZO03w5+BaeiPma1uCfmxTnxbtuMrI/dbzGiOk8VghO108uFI0oJo0OkewdSHw7g==} - engines: {node: '>=18'} - - '@aws-crypto/ie11-detection@3.0.0': - resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} - - '@aws-crypto/sha256-browser@3.0.0': - resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} - - '@aws-crypto/sha256-js@3.0.0': - resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} - - '@aws-crypto/supports-web-crypto@3.0.0': - resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} - - '@aws-crypto/util@3.0.0': - resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} - - '@aws-sdk/client-rds-data@3.577.0': - resolution: {integrity: sha512-24a27II6UkNhe2RB6ZwtQPcM3QB/DuRcKvzMmfvipgWS72Q5FEtuq3CO66IObWUel/pxi3ucE6mSvVCFnm7tBQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sso-oidc@3.577.0': - resolution: {integrity: sha512-njmKSPDWueWWYVFpFcZ2P3fI6/pdQVDa0FgCyYZhOnJLgEHZIcBBg1AsnkVWacBuLopp9XVt2m+7hO6ugY1/1g==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sso@3.577.0': - resolution: {integrity: sha512-BwujdXrydlk6UEyPmewm5GqG4nkQ6OVyRhS/SyZP/6UKSFv2/sf391Cmz0hN0itUTH1rR4XeLln8XCOtarkrzg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sts@3.577.0': - resolution: {integrity: sha512-509Kklimva1XVlhGbpTpeX3kOP6ORpm44twJxDHpa9TURbmoaxj7veWlnLCbDorxDTrbsDghvYZshvcLsojVpg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/core@3.576.0': - resolution: {integrity: sha512-KDvDlbeipSTIf+ffKtTg1m419TK7s9mZSWC8bvuZ9qx6/sjQFOXIKOVqyuli6DnfxGbvRcwoRuY99OcCH1N/0w==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-env@3.577.0': - resolution: {integrity: sha512-Jxu255j0gToMGEiqufP8ZtKI8HW90lOLjwJ3LrdlD/NLsAY0tOQf1fWc53u28hWmmNGMxmCrL2p66IOgMDhDUw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-http@3.577.0': - resolution: {integrity: sha512-n++yhCp67b9+ZRGEdY1jhamB5E/O+QsIDOPSuRmdaSGMCOd82oUEKPgIVEU1bkqxDsBxgiEWuvtfhK6sNiDS0A==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-ini@3.577.0': - resolution: {integrity: sha512-q7lHPtv6BjRvChUE3m0tIaEZKxPTaZ1B3lKxGYsFl3VLAu5N8yGCUKwuA1izf4ucT+LyKscVGqK6VDZx1ev3nw==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.577.0 - - '@aws-sdk/credential-provider-node@3.577.0': - resolution: {integrity: sha512-epZ1HOMsrXBNczc0HQpv0VMjqAEpc09DUA7Rg3gUJfn8umhML7A7bXnUyqPA+S54q397UYg1leQKdSn23OiwQQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-process@3.577.0': - resolution: {integrity: sha512-Gin6BWtOiXxIgITrJ3Nwc+Y2P1uVT6huYR4EcbA/DJUPWyO0n9y5UFLewPvVbLkRn15JeEqErBLUrHclkiOKtw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-sso@3.577.0': - resolution: {integrity: sha512-iVm5SQvS7EgZTJsRaqUOmDQpBQPPPat42SCbWFvFQOLrl8qewq8OP94hFS5w2mP62zngeYzqhJnDel79HXbxew==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.577.0': - resolution: {integrity: sha512-ZGHGNRaCtJJmszb9UTnC7izNCtRUttdPlLdMkh41KPS32vfdrBDHs1JrpbZijItRj1xKuOXsiYSXLAaHGcLh8Q==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.577.0 - - '@aws-sdk/middleware-host-header@3.577.0': - resolution: {integrity: sha512-9ca5MJz455CODIVXs0/sWmJm7t3QO4EUa1zf8pE8grLpzf0J94bz/skDWm37Pli13T3WaAQBHCTiH2gUVfCsWg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-logger@3.577.0': - resolution: {integrity: sha512-aPFGpGjTZcJYk+24bg7jT4XdIp42mFXSuPt49lw5KygefLyJM/sB0bKKqPYYivW0rcuZ9brQ58eZUNthrzYAvg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-recursion-detection@3.577.0': - resolution: {integrity: sha512-pn3ZVEd2iobKJlR3H+bDilHjgRnNrQ6HMmK9ZzZw89Ckn3Dcbv48xOv4RJvu0aU8SDLl/SNCxppKjeLDTPGBNA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-user-agent@3.577.0': - resolution: {integrity: sha512-P55HAXgwmiHHpFx5JEPvOnAbfhN7v6sWv9PBQs+z2tC7QiBcPS0cdJR6PfV7J1n4VPK52/OnrK3l9VxdQ7Ms0g==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/region-config-resolver@3.577.0': - resolution: {integrity: sha512-4ChCFACNwzqx/xjg3zgFcW8Ali6R9C95cFECKWT/7CUM1D0MGvkclSH2cLarmHCmJgU6onKkJroFtWp0kHhgyg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/token-providers@3.577.0': - resolution: {integrity: sha512-0CkIZpcC3DNQJQ1hDjm2bdSy/Xjs7Ny5YvSsacasGOkNfk+FdkiQy6N67bZX3Zbc9KIx+Nz4bu3iDeNSNplnnQ==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sso-oidc': ^3.577.0 - - '@aws-sdk/types@3.577.0': - resolution: {integrity: sha512-FT2JZES3wBKN/alfmhlo+3ZOq/XJ0C7QOZcDNrpKjB0kqYoKjhVKZ/Hx6ArR0czkKfHzBBEs6y40ebIHx2nSmA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-endpoints@3.577.0': - resolution: {integrity: sha512-FjuUz1Kdy4Zly2q/c58tpdqHd6z7iOdU/caYzoc8jwgAHBDBbIJNQLCU9hXJnPV2M8pWxQDyIZsoVwtmvErPzw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-locate-window@3.568.0': - resolution: {integrity: sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-user-agent-browser@3.577.0': - resolution: {integrity: sha512-zEAzHgR6HWpZOH7xFgeJLc6/CzMcx4nxeQolZxVZoB5pPaJd3CjyRhZN0xXeZB0XIRCWmb4yJBgyiugXLNMkLA==} - - '@aws-sdk/util-user-agent-node@3.577.0': - resolution: {integrity: sha512-XqvtFjbSMtycZTWVwDe8DRWovuoMbA54nhUoZwVU6rW9OSD6NZWGR512BUGHFaWzW0Wg8++Dj10FrKTG2XtqfA==} - engines: {node: '>=16.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - - '@aws-sdk/util-utf8-browser@3.259.0': - resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} - - '@balena/dockerignore@1.0.2': - resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} - - '@cloudflare/kv-asset-handler@0.3.2': - resolution: {integrity: sha512-EeEjMobfuJrwoctj7FA1y1KEbM0+Q1xSjobIEyie9k4haVEBB7vkDvsasw1pM3rO39mL2akxIAzLMUAtrMHZhA==} - engines: {node: '>=16.13'} - - '@cloudflare/workerd-darwin-64@1.20240512.0': - resolution: {integrity: sha512-VMp+CsSHFALQiBzPdQ5dDI4T1qwLu0mQ0aeKVNDosXjueN0f3zj/lf+mFil5/9jBbG3t4mG0y+6MMnalP9Lobw==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20240512.0': - resolution: {integrity: sha512-lZktXGmzMrB5rJqY9+PmnNfv1HKlj/YLZwMjPfF0WVKHUFdvQbAHsi7NlKv6mW9uIvlZnS+K4sIkWc0MDXcRnA==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-linux-64@1.20240512.0': - resolution: {integrity: sha512-wrHvqCZZqXz6Y3MUTn/9pQNsvaoNjbJpuA6vcXsXu8iCzJi911iVW2WUEBX+MpUWD+mBIP0oXni5tTlhkokOPw==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20240512.0': - resolution: {integrity: sha512-YPezHMySL9J9tFdzxz390eBswQ//QJNYcZolz9Dgvb3FEfdpK345cE/bsWbMOqw5ws2f82l388epoenghtYvAg==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-windows-64@1.20240512.0': - resolution: {integrity: sha512-SxKapDrIYSscMR7lGIp/av0l6vokjH4xQ9ACxHgXh+OdOus9azppSmjaPyw4/ePvg7yqpkaNjf9o258IxWtvKQ==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - - '@cloudflare/workers-types@4.20240512.0': - resolution: {integrity: sha512-o2yTEWg+YK/I1t/Me+dA0oarO0aCbjibp6wSeaw52DSE9tDyKJ7S+Qdyw/XsMrKn4t8kF6f/YOba+9O4MJfW9w==} - - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@electric-sql/pglite@0.1.5': - resolution: {integrity: sha512-eymv4ONNvoPZQTvOQIi5dbpR+J5HzEv0qQH9o/y3gvNheJV/P/NFcrbsfJZYTsDKoq7DKrTiFNexsRkJKy8x9Q==} - - '@esbuild-kit/core-utils@3.3.2': - resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} - - '@esbuild-kit/esm-loader@2.6.5': - resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} - - '@esbuild-plugins/node-globals-polyfill@0.2.3': - resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} - peerDependencies: - esbuild: '*' - - '@esbuild-plugins/node-modules-polyfill@0.2.2': - resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==} - peerDependencies: - esbuild: '*' - - '@esbuild/aix-ppc64@0.19.12': - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.20.2': - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.17.19': - resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.18.20': - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.19.12': - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.20.2': - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.17.19': - resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.18.20': - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.19.12': - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.20.2': - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.17.19': - resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.18.20': - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.19.12': - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.20.2': - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.17.19': - resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.18.20': - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.19.12': - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.20.2': - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.17.19': - resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.18.20': - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.19.12': - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.20.2': - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.17.19': - resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.18.20': - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.19.12': - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.20.2': - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.17.19': - resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.18.20': - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.19.12': - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.20.2': - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.17.19': - resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.18.20': - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.19.12': - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.20.2': - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.17.19': - resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.18.20': - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.19.12': - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.20.2': - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.17.19': - resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.18.20': - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.19.12': - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.20.2': - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.14.54': - resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.17.19': - resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.18.20': - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.19.12': - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.20.2': - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.17.19': - resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.18.20': - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.19.12': - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.20.2': - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.17.19': - resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.18.20': - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.19.12': - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.20.2': - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.17.19': - resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.18.20': - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.19.12': - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.20.2': - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.17.19': - resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.18.20': - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.19.12': - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.20.2': - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.17.19': - resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.18.20': - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.19.12': - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.20.2': - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.17.19': - resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.18.20': - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.19.12': - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.20.2': - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.17.19': - resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.18.20': - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.19.12': - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.20.2': - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.17.19': - resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.18.20': - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.19.12': - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.20.2': - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.17.19': - resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.18.20': - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.19.12': - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.20.2': - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.17.19': - resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.18.20': - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.19.12': - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.20.2': - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.17.19': - resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.18.20': - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.19.12': - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.20.2': - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.10.0': - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/js@8.57.0': - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@ewoudenberg/difflib@0.1.0': - resolution: {integrity: sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A==} - - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} - - '@hono/node-server@1.11.1': - resolution: {integrity: sha512-GW1Iomhmm1o4Z+X57xGby8A35Cu9UZLL7pSMdqDBkD99U5cywff8F+8hLk5aBTzNubnsFAvWQ/fZjNwPsEn9lA==} - engines: {node: '>=18.14.1'} - - '@hono/zod-validator@0.2.1': - resolution: {integrity: sha512-HFoxln7Q6JsE64qz2WBS28SD33UB2alp3aRKmcWnNLDzEL1BLsWfbdX6e1HIiUprHYTIXf5y7ax8eYidKUwyaA==} - peerDependencies: - hono: '>=3.9.0' - zod: ^3.19.1 - - '@humanwhocodes/config-array@0.11.14': - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - - '@libsql/client@0.4.3': - resolution: {integrity: sha512-AUYKnSPqAsFBVWBvmtrb4dG3pQlvTKT92eztAest9wQU2iJkabH8WzHLDb3dKFWKql7/kiCqvBQUVpozDwhekQ==} - - '@libsql/core@0.4.3': - resolution: {integrity: sha512-r28iYBtaLBW9RRgXPFh6cGCsVI/rwRlOzSOpAu/1PVTm6EJ3t233pUf97jETVHU0vjdr1d8VvV6fKAvJkokqCw==} - - '@libsql/darwin-arm64@0.2.0': - resolution: {integrity: sha512-+qyT2W/n5CFH1YZWv2mxW4Fsoo4dX9Z9M/nvbQqZ7H84J8hVegvVAsIGYzcK8xAeMEcpU5yGKB1Y9NoDY4hOSQ==} - cpu: [arm64] - os: [darwin] - - '@libsql/darwin-x64@0.2.0': - resolution: {integrity: sha512-hwmO2mF1n8oDHKFrUju6Jv+n9iFtTf5JUK+xlnIE3Td0ZwGC/O1R/Z/btZTd9nD+vsvakC8SJT7/Q6YlWIkhEw==} - cpu: [x64] - os: [darwin] - - '@libsql/hrana-client@0.5.6': - resolution: {integrity: sha512-mjQoAmejZ1atG+M3YR2ZW+rg6ceBByH/S/h17ZoYZkqbWrvohFhXyz2LFxj++ARMoY9m6w3RJJIRdJdmnEUlFg==} - - '@libsql/isomorphic-fetch@0.1.12': - resolution: {integrity: sha512-MRo4UcmjAGAa3ac56LoD5OE13m2p0lu0VEtZC2NZMcogM/jc5fU9YtMQ3qbPjFJ+u2BBjFZgMPkQaLS1dlMhpg==} - - '@libsql/isomorphic-ws@0.1.5': - resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} - - '@libsql/linux-arm64-gnu@0.2.0': - resolution: {integrity: sha512-1w2lPXIYtnBaK5t/Ej5E8x7lPiE+jP3KATI/W4yei5Z/ONJh7jQW5PJ7sYU95vTME3hWEM1FXN6kvzcpFAte7w==} - cpu: [arm64] - os: [linux] - - '@libsql/linux-arm64-musl@0.2.0': - resolution: {integrity: sha512-lkblBEJ7xuNiWNjP8DDq0rqoWccszfkUS7Efh5EjJ+GDWdCBVfh08mPofIZg0fZVLWQCY3j+VZCG1qZfATBizg==} - cpu: [arm64] - os: [linux] - - '@libsql/linux-x64-gnu@0.2.0': - resolution: {integrity: sha512-+x/d289KeJydwOhhqSxKT+6MSQTCfLltzOpTzPccsvdt5fxg8CBi+gfvEJ4/XW23Sa+9bc7zodFP0i6MOlxX7w==} - cpu: [x64] - os: [linux] - - '@libsql/linux-x64-musl@0.2.0': - resolution: {integrity: sha512-5Xn0c5A6vKf9D1ASpgk7mef//FuY7t5Lktj/eiU4n3ryxG+6WTpqstTittJUgepVjcleLPYxIhQAYeYwTYH1IQ==} - cpu: [x64] - os: [linux] - - '@libsql/win32-x64-msvc@0.2.0': - resolution: {integrity: sha512-rpK+trBIpRST15m3cMYg5aPaX7kvCIottxY7jZPINkKAaScvfbn9yulU/iZUM9YtuK96Y1ZmvwyVIK/Y5DzoMQ==} - cpu: [x64] - os: [win32] - - '@neon-rs/load@0.0.4': - resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} - - '@neondatabase/serverless@0.7.2': - resolution: {integrity: sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==} - - '@neondatabase/serverless@0.9.3': - resolution: {integrity: sha512-6ZBK8asl2Z3+ADEaELvbaVVGVlmY1oAzkxxZfpmXPKFuJhbDN+5fU3zYBamsahS/Ch1zE+CVWB3R+8QEI2LMSw==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@originjs/vite-plugin-commonjs@1.0.3': - resolution: {integrity: sha512-KuEXeGPptM2lyxdIEJ4R11+5ztipHoE7hy8ClZt3PYaOVQ/pyngd2alaSrPnwyFeOW1UagRBaQ752aA1dTMdOQ==} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@pkgr/core@0.1.1': - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - - '@planetscale/database@1.18.0': - resolution: {integrity: sha512-t2XdOfrVgcF7AW791FtdPS27NyNqcE1SpoXgk3HpziousvUMsJi4Q6NL3JyOBpsMOrvk94749o8yyonvX5quPw==} - engines: {node: '>=16'} - - '@rollup/rollup-android-arm-eabi@4.17.2': - resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.17.2': - resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.17.2': - resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.17.2': - resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': - resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.17.2': - resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.17.2': - resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.17.2': - resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': - resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.17.2': - resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.17.2': - resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.17.2': - resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.17.2': - resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.17.2': - resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.17.2': - resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.17.2': - resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} - cpu: [x64] - os: [win32] - - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@sindresorhus/is@4.6.0': - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - - '@smithy/abort-controller@3.0.0': - resolution: {integrity: sha512-p6GlFGBt9K4MYLu72YuJ523NVR4A8oHlC5M2JO6OmQqN8kAc/uh1JqLE+FizTokrSJGg0CSvC+BrsmGzKtsZKA==} - engines: {node: '>=16.0.0'} - - '@smithy/config-resolver@3.0.0': - resolution: {integrity: sha512-2GzOfADwYLQugYkKQhIyZyQlM05K+tMKvRnc6eFfZcpJGRfKoMUMYdPlBKmqHwQFXQKBrGV6cxL9oymWgDzvFw==} - engines: {node: '>=16.0.0'} - - '@smithy/core@2.0.1': - resolution: {integrity: sha512-rcMkjvwxH/bER+oZUPR0yTA0ELD6m3A+d92+CFkdF6HJFCBB1bXo7P5pm21L66XwTN01B6bUhSCQ7cymWRD8zg==} - engines: {node: '>=16.0.0'} - - '@smithy/credential-provider-imds@3.0.0': - resolution: {integrity: sha512-lfmBiFQcA3FsDAPxNfY0L7CawcWtbyWsBOHo34nF095728JLkBX4Y9q/VPPE2r7fqMVK+drmDigqE2/SSQeVRA==} - engines: {node: '>=16.0.0'} - - '@smithy/fetch-http-handler@3.0.1': - resolution: {integrity: sha512-uaH74i5BDj+rBwoQaXioKpI0SHBJFtOVwzrCpxZxphOW0ki5jhj7dXvDMYM2IJem8TpdFvS2iC08sjOblfFGFg==} - - '@smithy/hash-node@3.0.0': - resolution: {integrity: sha512-84qXstNemP3XS5jcof0el6+bDfjzuvhJPQTEfro3lgtbCtKgzPm3MgiS6ehXVPjeQ5+JS0HqmTz8f/RYfzHVxw==} - engines: {node: '>=16.0.0'} - - '@smithy/invalid-dependency@3.0.0': - resolution: {integrity: sha512-F6wBBaEFgJzj0s4KUlliIGPmqXemwP6EavgvDqYwCH40O5Xr2iMHvS8todmGVZtuJCorBkXsYLyTu4PuizVq5g==} - - '@smithy/is-array-buffer@3.0.0': - resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-content-length@3.0.0': - resolution: {integrity: sha512-3C4s4d/iGobgCtk2tnWW6+zSTOBg1PRAm2vtWZLdriwTroFbbWNSr3lcyzHdrQHnEXYCC5K52EbpfodaIUY8sg==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-endpoint@3.0.0': - resolution: {integrity: sha512-aXOAWztw/5qAfp0NcA2OWpv6ZI/E+Dh9mByif7i91D/0iyYNUcKvskmXiowKESFkuZ7PIMd3VOR4fTibZDs2OQ==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-retry@3.0.1': - resolution: {integrity: sha512-hBhSEuL841FhJBK/19WpaGk5YWSzFk/P2UaVjANGKRv3eYNO8Y1lANWgqnuPWjOyCEWMPr58vELFDWpxvRKANw==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-serde@3.0.0': - resolution: {integrity: sha512-I1vKG1foI+oPgG9r7IMY1S+xBnmAn1ISqployvqkwHoSb8VPsngHDTOgYGYBonuOKndaWRUGJZrKYYLB+Ane6w==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-stack@3.0.0': - resolution: {integrity: sha512-+H0jmyfAyHRFXm6wunskuNAqtj7yfmwFB6Fp37enytp2q047/Od9xetEaUbluyImOlGnGpaVGaVfjwawSr+i6Q==} - engines: {node: '>=16.0.0'} - - '@smithy/node-config-provider@3.0.0': - resolution: {integrity: sha512-buqfaSdDh0zo62EPLf8rGDvcpKwGpO5ho4bXS2cdFhlOta7tBkWJt+O5uiaAeICfIOfPclNOndshDNSanX2X9g==} - engines: {node: '>=16.0.0'} - - '@smithy/node-http-handler@3.0.0': - resolution: {integrity: sha512-3trD4r7NOMygwLbUJo4eodyQuypAWr7uvPnebNJ9a70dQhVn+US8j/lCnvoJS6BXfZeF7PkkkI0DemVJw+n+eQ==} - engines: {node: '>=16.0.0'} - - '@smithy/property-provider@3.0.0': - resolution: {integrity: sha512-LmbPgHBswdXCrkWWuUwBm9w72S2iLWyC/5jet9/Y9cGHtzqxi+GVjfCfahkvNV4KXEwgnH8EMpcrD9RUYe0eLQ==} - engines: {node: '>=16.0.0'} - - '@smithy/protocol-http@4.0.0': - resolution: {integrity: sha512-qOQZOEI2XLWRWBO9AgIYuHuqjZ2csyr8/IlgFDHDNuIgLAMRx2Bl8ck5U5D6Vh9DPdoaVpuzwWMa0xcdL4O/AQ==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-builder@3.0.0': - resolution: {integrity: sha512-bW8Fi0NzyfkE0TmQphDXr1AmBDbK01cA4C1Z7ggwMAU5RDz5AAv/KmoRwzQAS0kxXNf/D2ALTEgwK0U2c4LtRg==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-parser@3.0.0': - resolution: {integrity: sha512-UzHwthk0UEccV4dHzPySnBy34AWw3V9lIqUTxmozQ+wPDAO9csCWMfOLe7V9A2agNYy7xE+Pb0S6K/J23JSzfQ==} - engines: {node: '>=16.0.0'} - - '@smithy/service-error-classification@3.0.0': - resolution: {integrity: sha512-3BsBtOUt2Gsnc3X23ew+r2M71WwtpHfEDGhHYHSDg6q1t8FrWh15jT25DLajFV1H+PpxAJ6gqe9yYeRUsmSdFA==} - engines: {node: '>=16.0.0'} - - '@smithy/shared-ini-file-loader@3.0.0': - resolution: {integrity: sha512-REVw6XauXk8xE4zo5aGL7Rz4ywA8qNMUn8RtWeTRQsgAlmlvbJ7CEPBcaXU2NDC3AYBgYAXrGyWD8XrN8UGDog==} - engines: {node: '>=16.0.0'} - - '@smithy/signature-v4@3.0.0': - resolution: {integrity: sha512-kXFOkNX+BQHe2qnLxpMEaCRGap9J6tUGLzc3A9jdn+nD4JdMwCKTJ+zFwQ20GkY+mAXGatyTw3HcoUlR39HwmA==} - engines: {node: '>=16.0.0'} - - '@smithy/smithy-client@3.0.1': - resolution: {integrity: sha512-KAiFY4Y4jdHxR+4zerH/VBhaFKM8pbaVmJZ/CWJRwtM/CmwzTfXfvYwf6GoUwiHepdv+lwiOXCuOl6UBDUEINw==} - engines: {node: '>=16.0.0'} - - '@smithy/types@3.0.0': - resolution: {integrity: sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw==} - engines: {node: '>=16.0.0'} - - '@smithy/url-parser@3.0.0': - resolution: {integrity: sha512-2XLazFgUu+YOGHtWihB3FSLAfCUajVfNBXGGYjOaVKjLAuAxx3pSBY3hBgLzIgB17haf59gOG3imKqTy8mcrjw==} - - '@smithy/util-base64@3.0.0': - resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-body-length-browser@3.0.0': - resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} - - '@smithy/util-body-length-node@3.0.0': - resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-buffer-from@3.0.0': - resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-config-provider@3.0.0': - resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-defaults-mode-browser@3.0.1': - resolution: {integrity: sha512-nW5kEzdJn1Bn5TF+gOPHh2rcPli8JU9vSSXLbfg7uPnfR1TMRQqs9zlYRhIb87NeSxIbpdXOI94tvXSy+fvDYg==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-defaults-mode-node@3.0.1': - resolution: {integrity: sha512-TFk+Qb+elLc/MOhtSp+50fstyfZ6avQbgH2d96xUBpeScu+Al9elxv+UFAjaTHe0HQe5n+wem8ZLpXvU8lwV6Q==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-endpoints@2.0.0': - resolution: {integrity: sha512-+exaXzEY3DNt2qtA2OtRNSDlVrE4p32j1JSsQkzA5AdP0YtJNjkYbYhJxkFmPYcjI1abuwopOZCwUmv682QkiQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-hex-encoding@3.0.0': - resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-middleware@3.0.0': - resolution: {integrity: sha512-q5ITdOnV2pXHSVDnKWrwgSNTDBAMHLptFE07ua/5Ty5WJ11bvr0vk2a7agu7qRhrCFRQlno5u3CneU5EELK+DQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-retry@3.0.0': - resolution: {integrity: sha512-nK99bvJiziGv/UOKJlDvFF45F00WgPLKVIGUfAK+mDhzVN2hb/S33uW2Tlhg5PVBoqY7tDVqL0zmu4OxAHgo9g==} - engines: {node: '>=16.0.0'} - - '@smithy/util-stream@3.0.1': - resolution: {integrity: sha512-7F7VNNhAsfMRA8I986YdOY5fE0/T1/ZjFF6OLsqkvQVNP3vZ/szYDfGCyphb7ioA09r32K/0qbSFfNFU68aSzA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-uri-escape@3.0.0': - resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} - engines: {node: '>=16.0.0'} - - '@smithy/util-utf8@3.0.0': - resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} - engines: {node: '>=16.0.0'} - - '@types/better-sqlite3@7.6.10': - resolution: {integrity: sha512-TZBjD+yOsyrUJGmcUj6OS3JADk3+UZcNv3NOBqGkM09bZdi28fNZw8ODqbMOLfKCu7RYCO62/ldq1iHbzxqoPw==} - - '@types/docker-modem@3.0.6': - resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} - - '@types/dockerode@3.3.29': - resolution: {integrity: sha512-5PRRq/yt5OT/Jf77ltIdz4EiR9+VLnPF+HpU4xGFwUqmV24Co2HKBNW3w+slqZ1CYchbcDeqJASHDYWzZCcMiQ==} - - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - - '@types/fs-extra@11.0.4': - resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} - - '@types/glob@8.1.0': - resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} - - '@types/json-diff@1.0.3': - resolution: {integrity: sha512-Qvxm8fpRMv/1zZR3sQWImeRK2mBYJji20xF51Fq9Gt//Ed18u0x6/FNLogLS1xhfUWTEmDyqveJqn95ltB6Kvw==} - - '@types/jsonfile@6.1.4': - resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - - '@types/minimatch@5.1.2': - resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - - '@types/node-fetch@2.6.11': - resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - - '@types/node-forge@1.3.11': - resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} - - '@types/node@18.19.33': - resolution: {integrity: sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==} - - '@types/pg@8.11.6': - resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} - - '@types/pg@8.6.6': - resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} - - '@types/pluralize@0.0.33': - resolution: {integrity: sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==} - - '@types/ps-tree@1.1.6': - resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==} - - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - - '@types/ssh2@1.15.0': - resolution: {integrity: sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww==} - - '@types/uuid@9.0.8': - resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} - - '@types/which@3.0.3': - resolution: {integrity: sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==} - - '@types/ws@8.5.10': - resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} - - '@typescript-eslint/eslint-plugin@7.10.0': - resolution: {integrity: sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@7.10.0': - resolution: {integrity: sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@7.10.0': - resolution: {integrity: sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/type-utils@7.10.0': - resolution: {integrity: sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@7.10.0': - resolution: {integrity: sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/typescript-estree@7.10.0': - resolution: {integrity: sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@7.10.0': - resolution: {integrity: sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - - '@typescript-eslint/visitor-keys@7.10.0': - resolution: {integrity: sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - - '@vercel/postgres@0.8.0': - resolution: {integrity: sha512-/QUV9ExwaNdKooRjOQqvrKNVnRvsaXeukPNI5DB1ovUTesglfR/fparw7ngo1KUWWKIVpEj2TRrA+ObRHRdaLg==} - engines: {node: '>=14.6'} - - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} - - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} - - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} - - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} - - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - - acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - - aggregate-error@4.0.1: - resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} - engines: {node: '>=12'} - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ansi-escapes@6.2.1: - resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} - engines: {node: '>=14.16'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - ansicolors@0.3.2: - resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - array-find-index@1.0.2: - resolution: {integrity: sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==} - engines: {node: '>=0.10.0'} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - arrgv@1.0.2: - resolution: {integrity: sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==} - engines: {node: '>=8.0.0'} - - arrify@3.0.0: - resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} - engines: {node: '>=12'} - - as-table@1.0.55: - resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} - - asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - ava@5.3.1: - resolution: {integrity: sha512-Scv9a4gMOXB6+ni4toLuhAm9KYWEjsgBglJl+kMGI5+IVDt120CCDZyB5HNU9DjmLI2t4I0GbnxGLmmRfGTJGg==} - engines: {node: '>=14.19 <15 || >=16.15 <17 || >=18'} - hasBin: true - peerDependencies: - '@ava/typescript': '*' - peerDependenciesMeta: - '@ava/typescript': - optional: true - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - - better-sqlite3@9.6.0: - resolution: {integrity: sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - blake3-wasm@2.1.5: - resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} - - blueimp-md5@2.19.0: - resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} - - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - bufferutil@4.0.8: - resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} - engines: {node: '>=6.14.2'} - - buildcheck@0.0.6: - resolution: {integrity: sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==} - engines: {node: '>=10.0.0'} - - bundle-require@4.1.0: - resolution: {integrity: sha512-FeArRFM+ziGkRViKRnSTbHZc35dgmR9yNog05Kn0+ItI59pOAISGvnnIwW1WgFZQW59IxD9QpJnUPkdIPfZuXg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.17' - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - callsites@4.1.0: - resolution: {integrity: sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==} - engines: {node: '>=12.20'} - - camelcase@7.0.1: - resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} - engines: {node: '>=14.16'} - - capnp-ts@0.7.0: - resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} - - cardinal@2.1.1: - resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} - hasBin: true - - cbor@8.1.0: - resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==} - engines: {node: '>=12.19'} - - chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - - chunkd@2.0.1: - resolution: {integrity: sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - ci-parallel-vars@1.0.1: - resolution: {integrity: sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==} - - clean-stack@4.2.0: - resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} - engines: {node: '>=12'} - - clean-yaml-object@0.1.0: - resolution: {integrity: sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==} - engines: {node: '>=0.10.0'} - - cli-color@2.0.4: - resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==} - engines: {node: '>=0.10'} - - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - - cli-truncate@3.1.0: - resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - code-excerpt@4.0.0: - resolution: {integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colors@1.4.0: - resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} - engines: {node: '>=0.1.90'} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@10.0.1: - resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} - engines: {node: '>=14'} - - commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - - common-path-prefix@3.0.0: - resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concordance@5.0.4: - resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} - engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} - - confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} - - convert-to-spaces@2.0.1: - resolution: {integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} - - copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - - cpu-features@0.0.10: - resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} - engines: {node: '>=10.0.0'} - - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - - currently-unhandled@0.4.1: - resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} - engines: {node: '>=0.10.0'} - - d@1.0.2: - resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} - engines: {node: '>=0.12'} - - data-uri-to-buffer@2.0.2: - resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} - - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - - date-time@3.1.0: - resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} - engines: {node: '>=6'} - - debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - - deep-eql@4.1.3: - resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} - engines: {node: '>=6'} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - - detect-libc@2.0.2: - resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} - engines: {node: '>=8'} - - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - difflib@0.2.4: - resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - docker-modem@3.0.8: - resolution: {integrity: sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==} - engines: {node: '>= 8.0'} - - dockerode@3.3.5: - resolution: {integrity: sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==} - engines: {node: '>= 8.0'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - - dreamopt@0.8.0: - resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==} - engines: {node: '>=0.4.0'} - - drizzle-kit@0.21.2: - resolution: {integrity: sha512-U87IhZyCt/9d0ZT/Na3KFJVY31tSxtTx/n9UMcWFpW/5c2Ede39xiCG5efNV/0iimsv97UIRtDI0ldLBW5lbcg==} - hasBin: true - - drizzle-orm@0.32.0-85c8008: - resolution: {integrity: sha512-gHLqGZz0eqAvSw4vq46sHRV8qLHxrbuCVlwaVZ1t4ntyH8csyCKEXTWO78cBJwYUpz7BCSzqVX+5ZYa/QM+/Gw==} - peerDependencies: - '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' - '@neondatabase/serverless': '>=0.1' - '@op-engineering/op-sqlite': '>=2' - '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' - '@tidbcloud/serverless': '*' - '@types/better-sqlite3': '*' - '@types/pg': '*' - '@types/react': '>=18' - '@types/sql.js': '*' - '@vercel/postgres': '>=0.8.0' - '@xata.io/client': '*' - better-sqlite3: '>=7' - bun-types: '*' - expo-sqlite: '>=13.2.0' - knex: '*' - kysely: '*' - mysql2: '>=2' - pg: '>=8' - postgres: '>=3' - react: '>=18' - sql.js: '>=1' - sqlite3: '>=5' - peerDependenciesMeta: - '@aws-sdk/client-rds-data': - optional: true - '@cloudflare/workers-types': - optional: true - '@electric-sql/pglite': - optional: true - '@libsql/client': - optional: true - '@neondatabase/serverless': - optional: true - '@op-engineering/op-sqlite': - optional: true - '@opentelemetry/api': - optional: true - '@planetscale/database': - optional: true - '@tidbcloud/serverless': - optional: true - '@types/better-sqlite3': - optional: true - '@types/pg': - optional: true - '@types/react': - optional: true - '@types/sql.js': - optional: true - '@vercel/postgres': - optional: true - '@xata.io/client': - optional: true - better-sqlite3: - optional: true - bun-types: - optional: true - expo-sqlite: - optional: true - knex: - optional: true - kysely: - optional: true - mysql2: - optional: true - pg: - optional: true - postgres: - optional: true - react: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - - duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emittery@1.0.3: - resolution: {integrity: sha512-tJdCJitoy2lrC2ldJcqN4vkqJ00lT+tOWNT1hBJjO/3FDMJa5TTIiYGCKGkn/WfCyOzUMObeohbVTj00fhiLiA==} - engines: {node: '>=14.16'} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - emojilib@2.4.0: - resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} - - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - - env-paths@3.0.0: - resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - es5-ext@0.10.64: - resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} - engines: {node: '>=0.10'} - - es6-iterator@2.0.3: - resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} - - es6-symbol@3.1.4: - resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} - engines: {node: '>=0.12'} - - es6-weak-map@2.0.3: - resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} - - esbuild-android-64@0.14.54: - resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - esbuild-android-arm64@0.14.54: - resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - esbuild-darwin-64@0.14.54: - resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - esbuild-darwin-arm64@0.14.54: - resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - esbuild-freebsd-64@0.14.54: - resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - esbuild-freebsd-arm64@0.14.54: - resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - esbuild-linux-32@0.14.54: - resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - esbuild-linux-64@0.14.54: - resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - esbuild-linux-arm64@0.14.54: - resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - esbuild-linux-arm@0.14.54: - resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - esbuild-linux-mips64le@0.14.54: - resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - esbuild-linux-ppc64le@0.14.54: - resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - esbuild-linux-riscv64@0.14.54: - resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - esbuild-linux-s390x@0.14.54: - resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - esbuild-netbsd-64@0.14.54: - resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - esbuild-node-externals@1.13.1: - resolution: {integrity: sha512-ho4Lokc6iMB1lWbb2tWJ6otien+3Kfoaxe0fy7NUNgVuLnfmlW+GRINftTVUGtTVY/dapuwUu/CvFylYNwzkMA==} - engines: {node: '>=12'} - peerDependencies: - esbuild: 0.12 - 0.21 - - esbuild-openbsd-64@0.14.54: - resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - esbuild-register@3.5.0: - resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} - peerDependencies: - esbuild: '>=0.12 <1' - - esbuild-sunos-64@0.14.54: - resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - esbuild-windows-32@0.14.54: - resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - esbuild-windows-64@0.14.54: - resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - esbuild-windows-arm64@0.14.54: - resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - esbuild@0.14.54: - resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.17.19: - resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} - engines: {node: '>=12'} - hasBin: true - - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-plugin-prettier@5.1.3: - resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '*' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - - esniff@2.0.1: - resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==} - engines: {node: '>=0.10'} - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@0.6.1: - resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - event-emitter@0.3.5: - resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} - - event-stream@3.3.4: - resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - - exit-hook@2.2.1: - resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} - engines: {node: '>=6'} - - expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - - ext@1.7.0: - resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fast-xml-parser@4.2.5: - resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} - hasBin: true - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - - figures@5.0.0: - resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} - engines: {node: '>=14'} - - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - find-up@6.3.0: - resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} - - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - - from@0.1.7: - resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - - fs-extra@11.2.0: - resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} - engines: {node: '>=14.14'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - fx@34.0.0: - resolution: {integrity: sha512-/fZih3/WLsrtlaj2mahjWxAmyuikmcl3D5kKPqLtFmEilLsy9wp0+/vEmfvYXXhwJc+ajtCFDCf+yttXmPMHSQ==} - hasBin: true - - generate-function@2.3.1: - resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - - get-port@6.1.2: - resolution: {integrity: sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - get-source@2.0.12: - resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} - - github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - - glob@10.3.15: - resolution: {integrity: sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==} - engines: {node: '>=16 || 14 >=14.18'} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - - glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globby@13.2.2: - resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - hanji@0.0.5: - resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - heap@0.2.7: - resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} - - hono@4.3.9: - resolution: {integrity: sha512-6c5LVE23HnIS8iBhY+XPmYJlPeeClznOi7mBNsAsJCgxo8Ciz75LTjqRUf5wv4RYq8kL+1KPLUZHCtKmbZssNg==} - engines: {node: '>=16.0.0'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore-by-default@2.1.0: - resolution: {integrity: sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==} - engines: {node: '>=10 <11 || >=12 <13 || >=14'} - - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - indent-string@5.0.0: - resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} - engines: {node: '>=12'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - irregular-plurals@3.5.0: - resolution: {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} - engines: {node: '>=8'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} - - is-error@2.2.2: - resolution: {integrity: sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - - is-promise@2.2.2: - resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - is-property@1.0.2: - resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - - is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} - engines: {node: '>=12.13'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - - js-string-escape@1.0.1: - resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} - engines: {node: '>= 0.8'} - - js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-diff@0.9.0: - resolution: {integrity: sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==} - hasBin: true - - json-diff@1.0.6: - resolution: {integrity: sha512-tcFIPRdlc35YkYdGxcamJjllUhXWv4n2rK9oJ2RsAzV4FBkuV4ojKEDgcZ+kpKxDmJKv+PFK65+1tVVOnSeEqA==} - hasBin: true - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - libsql@0.2.0: - resolution: {integrity: sha512-ELBRqhpJx5Dap0187zKQnntZyk4EjlDHSrjIVL8t+fQ5e8IxbQTeYgZgigMjB1EvrETdkm0Y0VxBGhzPQ+t0Jg==} - cpu: [x64, arm64] - os: [darwin, linux, win32] - - lilconfig@3.1.1: - resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - load-json-file@7.0.1: - resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} - engines: {node: '>=14'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - - lodash.throttle@4.1.1: - resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - long@4.0.0: - resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} - - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - - lru-cache@10.2.2: - resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} - engines: {node: 14 || >=16.14} - - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - lru-queue@0.1.0: - resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - - magic-string@0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - - map-age-cleaner@0.1.3: - resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==} - engines: {node: '>=6'} - - map-stream@0.1.0: - resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - - marked-terminal@6.2.0: - resolution: {integrity: sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==} - engines: {node: '>=16.0.0'} - peerDependencies: - marked: '>=1 <12' - - marked@9.1.6: - resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} - engines: {node: '>= 16'} - hasBin: true - - matcher@5.0.0: - resolution: {integrity: sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - md5-hex@3.0.1: - resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} - engines: {node: '>=8'} - - mem@9.0.2: - resolution: {integrity: sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==} - engines: {node: '>=12.20'} - - memoizee@0.4.15: - resolution: {integrity: sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - - miniflare@3.20240512.0: - resolution: {integrity: sha512-X0PlKR0AROKpxFoJNmRtCMIuJxj+ngEcyTOlEokj2rAQ0TBwUhB4/1uiPvdI6ofW5NugPOD1uomAv+gLjwsLDQ==} - engines: {node: '>=16.13'} - hasBin: true - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - - minimatch@7.4.6: - resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} - engines: {node: '>=10'} - - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.1: - resolution: {integrity: sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==} - engines: {node: '>=16 || 14 >=14.17'} - - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - - mlly@1.7.0: - resolution: {integrity: sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==} - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mustache@4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - - mysql2@2.3.3: - resolution: {integrity: sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==} - engines: {node: '>= 8.0'} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - named-placeholders@1.1.3: - resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} - engines: {node: '>=12.0.0'} - - nan@2.19.0: - resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} - - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - next-tick@1.1.0: - resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - - node-abi@3.62.0: - resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==} - engines: {node: '>=10'} - - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - - node-emoji@2.1.3: - resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} - engines: {node: '>=18'} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-fetch@3.3.1: - resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - node-forge@1.3.1: - resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} - engines: {node: '>= 6.13.0'} - - node-gyp-build@4.8.1: - resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} - hasBin: true - - nofilter@3.1.0: - resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==} - engines: {node: '>=12.19'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - obuf@1.1.2: - resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - p-defer@1.0.0: - resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} - engines: {node: '>=4'} - - p-event@5.0.1: - resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-map@5.5.0: - resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} - engines: {node: '>=12'} - - p-timeout@5.1.0: - resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} - engines: {node: '>=12'} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-ms@3.0.0: - resolution: {integrity: sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==} - engines: {node: '>=12'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-to-regexp@6.2.2: - resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - - pause-stream@0.0.11: - resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - - pg-cloudflare@1.1.1: - resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} - - pg-connection-string@2.6.4: - resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-numeric@1.0.2: - resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} - engines: {node: '>=4'} - - pg-pool@3.6.2: - resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.6.1: - resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg-types@4.0.2: - resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} - engines: {node: '>=10'} - - pg@8.11.5: - resolution: {integrity: sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==} - engines: {node: '>= 8.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - - pkg-conf@4.0.0: - resolution: {integrity: sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - pkg-types@1.1.1: - resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==} - - plur@5.1.0: - resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} - - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-array@3.0.2: - resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} - engines: {node: '>=12'} - - postgres-bytea@1.0.0: - resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} - engines: {node: '>=0.10.0'} - - postgres-bytea@3.0.0: - resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} - engines: {node: '>= 6'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-date@2.1.0: - resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} - engines: {node: '>=12'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - postgres-interval@3.0.0: - resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} - engines: {node: '>=12'} - - postgres-range@1.1.4: - resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} - - postgres@3.4.4: - resolution: {integrity: sha512-IbyN+9KslkqcXa8AO9fxpk97PA4pzewvpi2B3Dwy9u4zpV32QicaEdgmF3eSQUzdRk7ttDHQejNgAEr4XoeH4A==} - engines: {node: '>=12'} - - prebuild-install@7.1.2: - resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} - engines: {node: '>=10'} - hasBin: true - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - pretty-ms@8.0.0: - resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} - engines: {node: '>=14.16'} - - printable-characters@1.0.42: - resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} - - ps-tree@1.2.0: - resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} - engines: {node: '>= 0.10'} - hasBin: true - - pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - redeyed@2.1.1: - resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - - rollup-plugin-inject@3.0.2: - resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} - deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. - - rollup-plugin-node-polyfills@0.2.1: - resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} - - rollup-pluginutils@2.8.2: - resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} - - rollup@4.17.2: - resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - selfsigned@2.4.1: - resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} - engines: {node: '>=10'} - - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} - engines: {node: '>=10'} - hasBin: true - - seq-queue@0.0.5: - resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} - - serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - simple-concat@1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - - simple-get@4.0.1: - resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - skin-tone@2.0.0: - resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} - engines: {node: '>=8'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} - - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.8.0-beta.0: - resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} - engines: {node: '>= 8'} - - sourcemap-codec@1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - deprecated: Please use @jridgewell/sourcemap-codec instead - - split-ca@1.0.1: - resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - split@0.3.3: - resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - sqlstring@2.3.3: - resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} - engines: {node: '>= 0.6'} - - ssh2@1.15.0: - resolution: {integrity: sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==} - engines: {node: '>=10.16.0'} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - stacktracey@2.1.8: - resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} - - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - - stoppable@1.1.0: - resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} - engines: {node: '>=4', npm: '>=6'} - - stream-combiner@0.0.4: - resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - - strnum@1.0.5: - resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} - - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - superjson@2.2.1: - resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==} - engines: {node: '>=16'} - - supertap@3.0.1: - resolution: {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-hyperlinks@3.0.0: - resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} - engines: {node: '>=14.18'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - synckit@0.8.8: - resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} - engines: {node: ^14.18.0 || >=16.0.0} - - tar-fs@2.0.1: - resolution: {integrity: sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==} - - tar-fs@2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - temp-dir@3.0.0: - resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} - engines: {node: '>=14.16'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - time-zone@1.0.0: - resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} - engines: {node: '>=4'} - - timers-ext@0.1.7: - resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==} - - tinybench@2.8.0: - resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} - - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} - engines: {node: '>=14.0.0'} - - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} - engines: {node: '>=14.0.0'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tr46@1.0.1: - resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - ts-expose-internals-conditionally@1.0.0-empty.0: - resolution: {integrity: sha512-F8m9NOF6ZhdOClDVdlM8gj3fDCav4ZIFSs/EI3ksQbAAXVSCN/Jh5OCJDDZWBuBy9psFc6jULGDlPwjMYMhJDw==} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - tsconfck@3.0.3: - resolution: {integrity: sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - - tsup@8.0.2: - resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - - tsx@3.14.0: - resolution: {integrity: sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==} - hasBin: true - - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type@2.7.2: - resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} - - typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} - engines: {node: '>=14.17'} - hasBin: true - - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - undici@5.28.4: - resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} - engines: {node: '>=14.0'} - - unicode-emoji-modifier-base@1.0.0: - resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} - engines: {node: '>=4'} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - utf-8-validate@6.0.3: - resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==} - engines: {node: '>=6.14.2'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - - validate-npm-package-name@5.0.1: - resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite-tsconfig-paths@4.3.2: - resolution: {integrity: sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@5.2.11: - resolution: {integrity: sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - webidl-conversions@4.0.2: - resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - - webpod@0.0.2: - resolution: {integrity: sha512-cSwwQIeg8v4i3p4ajHhwgR7N6VyxAf+KYSSsY6Pd3aETE+xEU4vbitz7qQkB0I321xnhDdgtxuiSfk5r/FVtjg==} - hasBin: true - - well-known-symbols@2.0.0: - resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} - engines: {node: '>=6'} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - whatwg-url@7.1.0: - resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - which@3.0.1: - resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - - why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} - engines: {node: '>=8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - workerd@1.20240512.0: - resolution: {integrity: sha512-VUBmR1PscAPHEE0OF/G2K7/H1gnr9aDWWZzdkIgWfNKkv8dKFCT75H+GJtUHjfwqz3rYCzaNZmatSXOpLGpF8A==} - engines: {node: '>=16'} - hasBin: true - - wrangler@3.57.0: - resolution: {integrity: sha512-izK3AZtlFoTq8N0EZjLOQ7hqwsjaXCc1cbNKuhsLJjDX1jB1YZBDPhIhtXL4VVzkJAcH+0Zw2gguOePFCHNaxw==} - engines: {node: '>=16.17.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20240512.0 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - ws@8.14.2: - resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.17.0: - resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - - xxhash-wasm@1.0.2: - resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yaml@2.4.2: - resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==} - engines: {node: '>= 14'} - hasBin: true - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - - youch@3.3.3: - resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==} - - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - - zx@7.2.3: - resolution: {integrity: sha512-QODu38nLlYXg/B/Gw7ZKiZrvPkEsjPN3LQ5JFXM7h0JvwhEdPNNl+4Ao1y4+o3CLNiDUNcwzQYZ4/Ko7kKzCMA==} - engines: {node: '>= 16.0.0'} - hasBin: true - -snapshots: - - '@andrewbranch/untar.js@1.0.3': {} - - '@arethetypeswrong/cli@0.15.3': - dependencies: - '@arethetypeswrong/core': 0.15.1 - chalk: 4.1.2 - cli-table3: 0.6.5 - commander: 10.0.1 - marked: 9.1.6 - marked-terminal: 6.2.0(marked@9.1.6) - semver: 7.6.2 - - '@arethetypeswrong/core@0.15.1': - dependencies: - '@andrewbranch/untar.js': 1.0.3 - fflate: 0.8.2 - semver: 7.6.2 - ts-expose-internals-conditionally: 1.0.0-empty.0 - typescript: 5.3.3 - validate-npm-package-name: 5.0.1 - - '@aws-crypto/ie11-detection@3.0.0': - dependencies: - tslib: 1.14.1 - - '@aws-crypto/sha256-browser@3.0.0': - dependencies: - '@aws-crypto/ie11-detection': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-crypto/supports-web-crypto': 3.0.0 - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-locate-window': 3.568.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - - '@aws-crypto/sha256-js@3.0.0': - dependencies: - '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.577.0 - tslib: 1.14.1 - - '@aws-crypto/supports-web-crypto@3.0.0': - dependencies: - tslib: 1.14.1 - - '@aws-crypto/util@3.0.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-utf8-browser': 3.259.0 - tslib: 1.14.1 - - '@aws-sdk/client-rds-data@3.577.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/client-sts': 3.577.0 - '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/middleware-host-header': 3.577.0 - '@aws-sdk/middleware-logger': 3.577.0 - '@aws-sdk/middleware-recursion-detection': 3.577.0 - '@aws-sdk/middleware-user-agent': 3.577.0 - '@aws-sdk/region-config-resolver': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@aws-sdk/util-user-agent-browser': 3.577.0 - '@aws-sdk/util-user-agent-node': 3.577.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.1 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.1 - '@smithy/util-defaults-mode-node': 3.0.1 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sts': 3.577.0 - '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/middleware-host-header': 3.577.0 - '@aws-sdk/middleware-logger': 3.577.0 - '@aws-sdk/middleware-recursion-detection': 3.577.0 - '@aws-sdk/middleware-user-agent': 3.577.0 - '@aws-sdk/region-config-resolver': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@aws-sdk/util-user-agent-browser': 3.577.0 - '@aws-sdk/util-user-agent-node': 3.577.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.1 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.1 - '@smithy/util-defaults-mode-node': 3.0.1 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sts' - - aws-crt - - '@aws-sdk/client-sso@3.577.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/core': 3.576.0 - '@aws-sdk/middleware-host-header': 3.577.0 - '@aws-sdk/middleware-logger': 3.577.0 - '@aws-sdk/middleware-recursion-detection': 3.577.0 - '@aws-sdk/middleware-user-agent': 3.577.0 - '@aws-sdk/region-config-resolver': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@aws-sdk/util-user-agent-browser': 3.577.0 - '@aws-sdk/util-user-agent-node': 3.577.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.1 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.1 - '@smithy/util-defaults-mode-node': 3.0.1 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sts@3.577.0': - dependencies: - '@aws-crypto/sha256-browser': 3.0.0 - '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/middleware-host-header': 3.577.0 - '@aws-sdk/middleware-logger': 3.577.0 - '@aws-sdk/middleware-recursion-detection': 3.577.0 - '@aws-sdk/middleware-user-agent': 3.577.0 - '@aws-sdk/region-config-resolver': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@aws-sdk/util-user-agent-browser': 3.577.0 - '@aws-sdk/util-user-agent-node': 3.577.0 - '@smithy/config-resolver': 3.0.0 - '@smithy/core': 2.0.1 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/hash-node': 3.0.0 - '@smithy/invalid-dependency': 3.0.0 - '@smithy/middleware-content-length': 3.0.0 - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/node-http-handler': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.1 - '@smithy/util-defaults-mode-node': 3.0.1 - '@smithy/util-endpoints': 2.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/core@3.576.0': - dependencies: - '@smithy/core': 2.0.1 - '@smithy/protocol-http': 4.0.0 - '@smithy/signature-v4': 3.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - fast-xml-parser: 4.2.5 - tslib: 2.6.2 - - '@aws-sdk/credential-provider-env@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/credential-provider-http@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/node-http-handler': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/util-stream': 3.0.1 - tslib: 2.6.2 - - '@aws-sdk/credential-provider-ini@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0)': - dependencies: - '@aws-sdk/client-sts': 3.577.0 - '@aws-sdk/credential-provider-env': 3.577.0 - '@aws-sdk/credential-provider-process': 3.577.0 - '@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) - '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/types': 3.577.0 - '@smithy/credential-provider-imds': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - - '@aws-sdk/credential-provider-node@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0)': - dependencies: - '@aws-sdk/credential-provider-env': 3.577.0 - '@aws-sdk/credential-provider-http': 3.577.0 - '@aws-sdk/credential-provider-ini': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/credential-provider-process': 3.577.0 - '@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) - '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/types': 3.577.0 - '@smithy/credential-provider-imds': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - '@aws-sdk/client-sts' - - aws-crt - - '@aws-sdk/credential-provider-process@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/credential-provider-sso@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))': - dependencies: - '@aws-sdk/client-sso': 3.577.0 - '@aws-sdk/token-providers': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - - '@aws-sdk/credential-provider-web-identity@3.577.0(@aws-sdk/client-sts@3.577.0)': - dependencies: - '@aws-sdk/client-sts': 3.577.0 - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/middleware-host-header@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/middleware-logger@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/middleware-recursion-detection@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/middleware-user-agent@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@aws-sdk/util-endpoints': 3.577.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/region-config-resolver@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))': - dependencies: - '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) - '@aws-sdk/types': 3.577.0 - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/types@3.577.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/util-endpoints@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/types': 3.0.0 - '@smithy/util-endpoints': 2.0.0 - tslib: 2.6.2 - - '@aws-sdk/util-locate-window@3.568.0': - dependencies: - tslib: 2.6.2 - - '@aws-sdk/util-user-agent-browser@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/types': 3.0.0 - bowser: 2.11.0 - tslib: 2.6.2 - - '@aws-sdk/util-user-agent-node@3.577.0': - dependencies: - '@aws-sdk/types': 3.577.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@aws-sdk/util-utf8-browser@3.259.0': - dependencies: - tslib: 2.6.2 - - '@balena/dockerignore@1.0.2': {} - - '@cloudflare/kv-asset-handler@0.3.2': - dependencies: - mime: 3.0.0 - - '@cloudflare/workerd-darwin-64@1.20240512.0': - optional: true - - '@cloudflare/workerd-darwin-arm64@1.20240512.0': - optional: true - - '@cloudflare/workerd-linux-64@1.20240512.0': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20240512.0': - optional: true - - '@cloudflare/workerd-windows-64@1.20240512.0': - optional: true - - '@cloudflare/workers-types@4.20240512.0': {} - - '@colors/colors@1.5.0': - optional: true - - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - - '@electric-sql/pglite@0.1.5': {} - - '@esbuild-kit/core-utils@3.3.2': - dependencies: - esbuild: 0.18.20 - source-map-support: 0.5.21 - - '@esbuild-kit/esm-loader@2.6.5': - dependencies: - '@esbuild-kit/core-utils': 3.3.2 - get-tsconfig: 4.7.5 - - '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': - dependencies: - esbuild: 0.17.19 - - '@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19)': - dependencies: - esbuild: 0.17.19 - escape-string-regexp: 4.0.0 - rollup-plugin-node-polyfills: 0.2.1 - - '@esbuild/aix-ppc64@0.19.12': - optional: true - - '@esbuild/aix-ppc64@0.20.2': - optional: true - - '@esbuild/android-arm64@0.17.19': - optional: true - - '@esbuild/android-arm64@0.18.20': - optional: true - - '@esbuild/android-arm64@0.19.12': - optional: true - - '@esbuild/android-arm64@0.20.2': - optional: true - - '@esbuild/android-arm@0.17.19': - optional: true - - '@esbuild/android-arm@0.18.20': - optional: true - - '@esbuild/android-arm@0.19.12': - optional: true - - '@esbuild/android-arm@0.20.2': - optional: true - - '@esbuild/android-x64@0.17.19': - optional: true - - '@esbuild/android-x64@0.18.20': - optional: true - - '@esbuild/android-x64@0.19.12': - optional: true - - '@esbuild/android-x64@0.20.2': - optional: true - - '@esbuild/darwin-arm64@0.17.19': - optional: true - - '@esbuild/darwin-arm64@0.18.20': - optional: true - - '@esbuild/darwin-arm64@0.19.12': - optional: true - - '@esbuild/darwin-arm64@0.20.2': - optional: true - - '@esbuild/darwin-x64@0.17.19': - optional: true - - '@esbuild/darwin-x64@0.18.20': - optional: true - - '@esbuild/darwin-x64@0.19.12': - optional: true - - '@esbuild/darwin-x64@0.20.2': - optional: true - - '@esbuild/freebsd-arm64@0.17.19': - optional: true - - '@esbuild/freebsd-arm64@0.18.20': - optional: true - - '@esbuild/freebsd-arm64@0.19.12': - optional: true - - '@esbuild/freebsd-arm64@0.20.2': - optional: true - - '@esbuild/freebsd-x64@0.17.19': - optional: true - - '@esbuild/freebsd-x64@0.18.20': - optional: true - - '@esbuild/freebsd-x64@0.19.12': - optional: true - - '@esbuild/freebsd-x64@0.20.2': - optional: true - - '@esbuild/linux-arm64@0.17.19': - optional: true - - '@esbuild/linux-arm64@0.18.20': - optional: true - - '@esbuild/linux-arm64@0.19.12': - optional: true - - '@esbuild/linux-arm64@0.20.2': - optional: true - - '@esbuild/linux-arm@0.17.19': - optional: true - - '@esbuild/linux-arm@0.18.20': - optional: true - - '@esbuild/linux-arm@0.19.12': - optional: true - - '@esbuild/linux-arm@0.20.2': - optional: true - - '@esbuild/linux-ia32@0.17.19': - optional: true - - '@esbuild/linux-ia32@0.18.20': - optional: true - - '@esbuild/linux-ia32@0.19.12': - optional: true - - '@esbuild/linux-ia32@0.20.2': - optional: true - - '@esbuild/linux-loong64@0.14.54': - optional: true - - '@esbuild/linux-loong64@0.17.19': - optional: true - - '@esbuild/linux-loong64@0.18.20': - optional: true - - '@esbuild/linux-loong64@0.19.12': - optional: true - - '@esbuild/linux-loong64@0.20.2': - optional: true - - '@esbuild/linux-mips64el@0.17.19': - optional: true - - '@esbuild/linux-mips64el@0.18.20': - optional: true - - '@esbuild/linux-mips64el@0.19.12': - optional: true - - '@esbuild/linux-mips64el@0.20.2': - optional: true - - '@esbuild/linux-ppc64@0.17.19': - optional: true - - '@esbuild/linux-ppc64@0.18.20': - optional: true - - '@esbuild/linux-ppc64@0.19.12': - optional: true - - '@esbuild/linux-ppc64@0.20.2': - optional: true - - '@esbuild/linux-riscv64@0.17.19': - optional: true - - '@esbuild/linux-riscv64@0.18.20': - optional: true - - '@esbuild/linux-riscv64@0.19.12': - optional: true - - '@esbuild/linux-riscv64@0.20.2': - optional: true - - '@esbuild/linux-s390x@0.17.19': - optional: true - - '@esbuild/linux-s390x@0.18.20': - optional: true - - '@esbuild/linux-s390x@0.19.12': - optional: true - - '@esbuild/linux-s390x@0.20.2': - optional: true - - '@esbuild/linux-x64@0.17.19': - optional: true - - '@esbuild/linux-x64@0.18.20': - optional: true - - '@esbuild/linux-x64@0.19.12': - optional: true - - '@esbuild/linux-x64@0.20.2': - optional: true - - '@esbuild/netbsd-x64@0.17.19': - optional: true - - '@esbuild/netbsd-x64@0.18.20': - optional: true - - '@esbuild/netbsd-x64@0.19.12': - optional: true - - '@esbuild/netbsd-x64@0.20.2': - optional: true - - '@esbuild/openbsd-x64@0.17.19': - optional: true - - '@esbuild/openbsd-x64@0.18.20': - optional: true - - '@esbuild/openbsd-x64@0.19.12': - optional: true - - '@esbuild/openbsd-x64@0.20.2': - optional: true - - '@esbuild/sunos-x64@0.17.19': - optional: true - - '@esbuild/sunos-x64@0.18.20': - optional: true - - '@esbuild/sunos-x64@0.19.12': - optional: true - - '@esbuild/sunos-x64@0.20.2': - optional: true - - '@esbuild/win32-arm64@0.17.19': - optional: true - - '@esbuild/win32-arm64@0.18.20': - optional: true - - '@esbuild/win32-arm64@0.19.12': - optional: true - - '@esbuild/win32-arm64@0.20.2': - optional: true - - '@esbuild/win32-ia32@0.17.19': - optional: true - - '@esbuild/win32-ia32@0.18.20': - optional: true - - '@esbuild/win32-ia32@0.19.12': - optional: true - - '@esbuild/win32-ia32@0.20.2': - optional: true - - '@esbuild/win32-x64@0.17.19': - optional: true - - '@esbuild/win32-x64@0.18.20': - optional: true - - '@esbuild/win32-x64@0.19.12': - optional: true - - '@esbuild/win32-x64@0.20.2': - optional: true - - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': - dependencies: - eslint: 8.57.0 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.10.0': {} - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.1 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.0': {} - - '@ewoudenberg/difflib@0.1.0': - dependencies: - heap: 0.2.7 - - '@fastify/busboy@2.1.1': {} - - '@hono/node-server@1.11.1': {} - - '@hono/zod-validator@0.2.1(hono@4.3.9)(zod@3.23.8)': - dependencies: - hono: 4.3.9 - zod: 3.23.8 - - '@humanwhocodes/config-array@0.11.14': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - - '@jridgewell/gen-mapping@0.3.5': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.4.15': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - - '@jridgewell/trace-mapping@0.3.9': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - - '@libsql/client@0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3)': - dependencies: - '@libsql/core': 0.4.3 - '@libsql/hrana-client': 0.5.6(bufferutil@4.0.8)(utf-8-validate@6.0.3) - js-base64: 3.7.7 - optionalDependencies: - libsql: 0.2.0 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@libsql/core@0.4.3': - dependencies: - js-base64: 3.7.7 - - '@libsql/darwin-arm64@0.2.0': - optional: true - - '@libsql/darwin-x64@0.2.0': - optional: true - - '@libsql/hrana-client@0.5.6(bufferutil@4.0.8)(utf-8-validate@6.0.3)': - dependencies: - '@libsql/isomorphic-fetch': 0.1.12 - '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3) - js-base64: 3.7.7 - node-fetch: 3.3.2 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@libsql/isomorphic-fetch@0.1.12': - dependencies: - '@types/node-fetch': 2.6.11 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - - '@libsql/isomorphic-ws@0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3)': - dependencies: - '@types/ws': 8.5.10 - ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@libsql/linux-arm64-gnu@0.2.0': - optional: true - - '@libsql/linux-arm64-musl@0.2.0': - optional: true - - '@libsql/linux-x64-gnu@0.2.0': - optional: true - - '@libsql/linux-x64-musl@0.2.0': - optional: true - - '@libsql/win32-x64-msvc@0.2.0': - optional: true - - '@neon-rs/load@0.0.4': - optional: true - - '@neondatabase/serverless@0.7.2': - dependencies: - '@types/pg': 8.6.6 - - '@neondatabase/serverless@0.9.3': - dependencies: - '@types/pg': 8.11.6 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - - '@originjs/vite-plugin-commonjs@1.0.3': - dependencies: - esbuild: 0.14.54 - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@pkgr/core@0.1.1': {} - - '@planetscale/database@1.18.0': {} - - '@rollup/rollup-android-arm-eabi@4.17.2': - optional: true - - '@rollup/rollup-android-arm64@4.17.2': - optional: true - - '@rollup/rollup-darwin-arm64@4.17.2': - optional: true - - '@rollup/rollup-darwin-x64@4.17.2': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.17.2': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.17.2': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.17.2': - optional: true - - '@rollup/rollup-linux-x64-musl@4.17.2': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.17.2': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.17.2': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.17.2': - optional: true - - '@sinclair/typebox@0.27.8': {} - - '@sindresorhus/is@4.6.0': {} - - '@smithy/abort-controller@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/config-resolver@3.0.0': - dependencies: - '@smithy/node-config-provider': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 - - '@smithy/core@2.0.1': - dependencies: - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-retry': 3.0.1 - '@smithy/middleware-serde': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 - - '@smithy/credential-provider-imds@3.0.0': - dependencies: - '@smithy/node-config-provider': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - tslib: 2.6.2 - - '@smithy/fetch-http-handler@3.0.1': - dependencies: - '@smithy/protocol-http': 4.0.0 - '@smithy/querystring-builder': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-base64': 3.0.0 - tslib: 2.6.2 - - '@smithy/hash-node@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - - '@smithy/invalid-dependency@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/is-array-buffer@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/middleware-content-length@3.0.0': - dependencies: - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/middleware-endpoint@3.0.0': - dependencies: - '@smithy/middleware-serde': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/url-parser': 3.0.0 - '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 - - '@smithy/middleware-retry@3.0.1': - dependencies: - '@smithy/node-config-provider': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/service-error-classification': 3.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-retry': 3.0.0 - tslib: 2.6.2 - uuid: 9.0.1 - - '@smithy/middleware-serde@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/middleware-stack@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/node-config-provider@3.0.0': - dependencies: - '@smithy/property-provider': 3.0.0 - '@smithy/shared-ini-file-loader': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/node-http-handler@3.0.0': - dependencies: - '@smithy/abort-controller': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/querystring-builder': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/property-provider@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/protocol-http@4.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/querystring-builder@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - '@smithy/util-uri-escape': 3.0.0 - tslib: 2.6.2 - - '@smithy/querystring-parser@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/service-error-classification@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - - '@smithy/shared-ini-file-loader@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/signature-v4@3.0.0': - dependencies: - '@smithy/is-array-buffer': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-middleware': 3.0.0 - '@smithy/util-uri-escape': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - - '@smithy/smithy-client@3.0.1': - dependencies: - '@smithy/middleware-endpoint': 3.0.0 - '@smithy/middleware-stack': 3.0.0 - '@smithy/protocol-http': 4.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-stream': 3.0.1 - tslib: 2.6.2 - - '@smithy/types@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/url-parser@3.0.0': - dependencies: - '@smithy/querystring-parser': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-base64@3.0.0': - dependencies: - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-body-length-browser@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-body-length-node@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-buffer-from@3.0.0': - dependencies: - '@smithy/is-array-buffer': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-config-provider@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-defaults-mode-browser@3.0.1': - dependencies: - '@smithy/property-provider': 3.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - bowser: 2.11.0 - tslib: 2.6.2 - - '@smithy/util-defaults-mode-node@3.0.1': - dependencies: - '@smithy/config-resolver': 3.0.0 - '@smithy/credential-provider-imds': 3.0.0 - '@smithy/node-config-provider': 3.0.0 - '@smithy/property-provider': 3.0.0 - '@smithy/smithy-client': 3.0.1 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-endpoints@2.0.0': - dependencies: - '@smithy/node-config-provider': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-hex-encoding@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-middleware@3.0.0': - dependencies: - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-retry@3.0.0': - dependencies: - '@smithy/service-error-classification': 3.0.0 - '@smithy/types': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-stream@3.0.1': - dependencies: - '@smithy/fetch-http-handler': 3.0.1 - '@smithy/node-http-handler': 3.0.0 - '@smithy/types': 3.0.0 - '@smithy/util-base64': 3.0.0 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 - - '@smithy/util-uri-escape@3.0.0': - dependencies: - tslib: 2.6.2 - - '@smithy/util-utf8@3.0.0': - dependencies: - '@smithy/util-buffer-from': 3.0.0 - tslib: 2.6.2 - - '@types/better-sqlite3@7.6.10': - dependencies: - '@types/node': 18.19.33 - - '@types/docker-modem@3.0.6': - dependencies: - '@types/node': 18.19.33 - '@types/ssh2': 1.15.0 - - '@types/dockerode@3.3.29': - dependencies: - '@types/docker-modem': 3.0.6 - '@types/node': 18.19.33 - '@types/ssh2': 1.15.0 - - '@types/estree@1.0.5': {} - - '@types/fs-extra@11.0.4': - dependencies: - '@types/jsonfile': 6.1.4 - '@types/node': 18.19.33 - - '@types/glob@8.1.0': - dependencies: - '@types/minimatch': 5.1.2 - '@types/node': 18.19.33 - - '@types/json-diff@1.0.3': {} - - '@types/jsonfile@6.1.4': - dependencies: - '@types/node': 18.19.33 - - '@types/minimatch@5.1.2': {} - - '@types/minimist@1.2.5': {} - - '@types/node-fetch@2.6.11': - dependencies: - '@types/node': 18.19.33 - form-data: 4.0.0 - - '@types/node-forge@1.3.11': - dependencies: - '@types/node': 18.19.33 - - '@types/node@18.19.33': - dependencies: - undici-types: 5.26.5 - - '@types/pg@8.11.6': - dependencies: - '@types/node': 18.19.33 - pg-protocol: 1.6.1 - pg-types: 4.0.2 - - '@types/pg@8.6.6': - dependencies: - '@types/node': 18.19.33 - pg-protocol: 1.6.1 - pg-types: 2.2.0 - - '@types/pluralize@0.0.33': {} - - '@types/ps-tree@1.1.6': {} - - '@types/semver@7.5.8': {} - - '@types/ssh2@1.15.0': - dependencies: - '@types/node': 18.19.33 - - '@types/uuid@9.0.8': {} - - '@types/which@3.0.3': {} - - '@types/ws@8.5.10': - dependencies: - '@types/node': 18.19.33 - - '@typescript-eslint/eslint-plugin@7.10.0(@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.10.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 7.10.0 - '@typescript-eslint/type-utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.10.0 - eslint: 8.57.0 - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@7.10.0(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@typescript-eslint/scope-manager': 7.10.0 - '@typescript-eslint/types': 7.10.0 - '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.10.0 - debug: 4.3.4 - eslint: 8.57.0 - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@7.10.0': - dependencies: - '@typescript-eslint/types': 7.10.0 - '@typescript-eslint/visitor-keys': 7.10.0 - - '@typescript-eslint/type-utils@7.10.0(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) - '@typescript-eslint/utils': 7.10.0(eslint@8.57.0)(typescript@5.4.5) - debug: 4.3.4 - eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@7.10.0': {} - - '@typescript-eslint/typescript-estree@7.10.0(typescript@5.4.5)': - dependencies: - '@typescript-eslint/types': 7.10.0 - '@typescript-eslint/visitor-keys': 7.10.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.6.2 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@7.10.0(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@typescript-eslint/scope-manager': 7.10.0 - '@typescript-eslint/types': 7.10.0 - '@typescript-eslint/typescript-estree': 7.10.0(typescript@5.4.5) - eslint: 8.57.0 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@7.10.0': - dependencies: - '@typescript-eslint/types': 7.10.0 - eslint-visitor-keys: 3.4.3 - - '@ungap/structured-clone@1.2.0': {} - - '@vercel/postgres@0.8.0': - dependencies: - '@neondatabase/serverless': 0.7.2 - bufferutil: 4.0.8 - utf-8-validate: 6.0.3 - ws: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) - - '@vitest/expect@1.6.0': - dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.4.1 - - '@vitest/runner@1.6.0': - dependencies: - '@vitest/utils': 1.6.0 - p-limit: 5.0.0 - pathe: 1.1.2 - - '@vitest/snapshot@1.6.0': - dependencies: - magic-string: 0.30.10 - pathe: 1.1.2 - pretty-format: 29.7.0 - - '@vitest/spy@1.6.0': - dependencies: - tinyspy: 2.2.1 - - '@vitest/utils@1.6.0': - dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 - - acorn-jsx@5.3.2(acorn@8.11.3): - dependencies: - acorn: 8.11.3 - - acorn-walk@8.3.2: {} - - acorn@8.11.3: {} - - aggregate-error@4.0.1: - dependencies: - clean-stack: 4.2.0 - indent-string: 5.0.0 - - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-escapes@6.2.1: {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.0.1: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - ansi-styles@6.2.1: {} - - ansicolors@0.3.2: {} - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - argparse@2.0.1: {} - - array-find-index@1.0.2: {} - - array-union@2.1.0: {} - - arrgv@1.0.2: {} - - arrify@3.0.0: {} - - as-table@1.0.55: - dependencies: - printable-characters: 1.0.42 - - asn1@0.2.6: - dependencies: - safer-buffer: 2.1.2 - - assertion-error@1.1.0: {} - - asynckit@0.4.0: {} - - ava@5.3.1: - dependencies: - acorn: 8.11.3 - acorn-walk: 8.3.2 - ansi-styles: 6.2.1 - arrgv: 1.0.2 - arrify: 3.0.0 - callsites: 4.1.0 - cbor: 8.1.0 - chalk: 5.3.0 - chokidar: 3.6.0 - chunkd: 2.0.1 - ci-info: 3.9.0 - ci-parallel-vars: 1.0.1 - clean-yaml-object: 0.1.0 - cli-truncate: 3.1.0 - code-excerpt: 4.0.0 - common-path-prefix: 3.0.0 - concordance: 5.0.4 - currently-unhandled: 0.4.1 - debug: 4.3.4 - emittery: 1.0.3 - figures: 5.0.0 - globby: 13.2.2 - ignore-by-default: 2.1.0 - indent-string: 5.0.0 - is-error: 2.2.2 - is-plain-object: 5.0.0 - is-promise: 4.0.0 - matcher: 5.0.0 - mem: 9.0.2 - ms: 2.1.3 - p-event: 5.0.1 - p-map: 5.5.0 - picomatch: 2.3.1 - pkg-conf: 4.0.0 - plur: 5.1.0 - pretty-ms: 8.0.0 - resolve-cwd: 3.0.0 - stack-utils: 2.0.6 - strip-ansi: 7.1.0 - supertap: 3.0.1 - temp-dir: 3.0.0 - write-file-atomic: 5.0.1 - yargs: 17.7.2 - transitivePeerDependencies: - - supports-color - - balanced-match@1.0.2: {} - - base64-js@1.5.1: {} - - bcrypt-pbkdf@1.0.2: - dependencies: - tweetnacl: 0.14.5 - - better-sqlite3@9.6.0: - dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.2 - - binary-extensions@2.3.0: {} - - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - - blake3-wasm@2.1.5: {} - - blueimp-md5@2.19.0: {} - - bowser@2.11.0: {} - - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.1: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.2: - dependencies: - fill-range: 7.1.1 - - buffer-from@1.1.2: {} - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bufferutil@4.0.8: - dependencies: - node-gyp-build: 4.8.1 - - buildcheck@0.0.6: - optional: true - - bundle-require@4.1.0(esbuild@0.19.12): - dependencies: - esbuild: 0.19.12 - load-tsconfig: 0.2.5 - - cac@6.7.14: {} - - callsites@3.1.0: {} - - callsites@4.1.0: {} - - camelcase@7.0.1: {} - - capnp-ts@0.7.0: - dependencies: - debug: 4.3.4 - tslib: 2.6.2 - transitivePeerDependencies: - - supports-color - - cardinal@2.1.1: - dependencies: - ansicolors: 0.3.2 - redeyed: 2.1.1 - - cbor@8.1.0: - dependencies: - nofilter: 3.1.0 - - chai@4.4.1: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.3 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@5.3.0: {} - - char-regex@1.0.2: {} - - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chownr@1.1.4: {} - - chunkd@2.0.1: {} - - ci-info@3.9.0: {} - - ci-parallel-vars@1.0.1: {} - - clean-stack@4.2.0: - dependencies: - escape-string-regexp: 5.0.0 - - clean-yaml-object@0.1.0: {} - - cli-color@2.0.4: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-iterator: 2.0.3 - memoizee: 0.4.15 - timers-ext: 0.1.7 - - cli-table3@0.6.5: - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - - cli-truncate@3.1.0: - dependencies: - slice-ansi: 5.0.0 - string-width: 5.1.2 - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - code-excerpt@4.0.0: - dependencies: - convert-to-spaces: 2.0.1 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colors@1.4.0: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - commander@10.0.1: {} - - commander@12.1.0: {} - - commander@4.1.1: {} - - commander@9.5.0: {} - - common-path-prefix@3.0.0: {} - - concat-map@0.0.1: {} - - concordance@5.0.4: - dependencies: - date-time: 3.1.0 - esutils: 2.0.3 - fast-diff: 1.3.0 - js-string-escape: 1.0.1 - lodash: 4.17.21 - md5-hex: 3.0.1 - semver: 7.6.2 - well-known-symbols: 2.0.0 - - confbox@0.1.7: {} - - convert-to-spaces@2.0.1: {} - - cookie@0.5.0: {} - - copy-anything@3.0.5: - dependencies: - is-what: 4.1.16 - - cpu-features@0.0.10: - dependencies: - buildcheck: 0.0.6 - nan: 2.19.0 - optional: true - - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - currently-unhandled@0.4.1: - dependencies: - array-find-index: 1.0.2 - - d@1.0.2: - dependencies: - es5-ext: 0.10.64 - type: 2.7.2 - - data-uri-to-buffer@2.0.2: {} - - data-uri-to-buffer@4.0.1: {} - - date-time@3.1.0: - dependencies: - time-zone: 1.0.0 - - debug@4.3.4: - dependencies: - ms: 2.1.2 - - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - - deep-eql@4.1.3: - dependencies: - type-detect: 4.0.8 - - deep-extend@0.6.0: {} - - deep-is@0.1.4: {} - - delayed-stream@1.0.0: {} - - denque@2.1.0: {} - - detect-libc@2.0.2: - optional: true - - detect-libc@2.0.3: {} - - diff-sequences@29.6.3: {} - - difflib@0.2.4(patch_hash=jq4t3ysdpnbunjeje4v7nrqn2q): - dependencies: - heap: 0.2.7 - - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - - docker-modem@3.0.8: - dependencies: - debug: 4.3.4 - readable-stream: 3.6.2 - split-ca: 1.0.1 - ssh2: 1.15.0 - transitivePeerDependencies: - - supports-color - - dockerode@3.3.5: - dependencies: - '@balena/dockerignore': 1.0.2 - docker-modem: 3.0.8 - tar-fs: 2.0.1 - transitivePeerDependencies: - - supports-color - - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - - dotenv@16.4.5: {} - - dreamopt@0.8.0: - dependencies: - wordwrap: 1.0.0 - - drizzle-kit@0.21.2: - dependencies: - '@esbuild-kit/esm-loader': 2.6.5 - commander: 9.5.0 - env-paths: 3.0.0 - esbuild: 0.19.12 - esbuild-register: 3.5.0(esbuild@0.19.12) - glob: 8.1.0 - hanji: 0.0.5 - json-diff: 0.9.0 - zod: 3.23.8 - transitivePeerDependencies: - - supports-color - - drizzle-orm@0.32.0-85c8008(@aws-sdk/client-rds-data@3.577.0)(@cloudflare/workers-types@4.20240512.0)(@electric-sql/pglite@0.1.5)(@libsql/client@0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3))(@neondatabase/serverless@0.9.3)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@vercel/postgres@0.8.0)(better-sqlite3@9.6.0)(mysql2@2.3.3)(pg@8.11.5)(postgres@3.4.4): - optionalDependencies: - '@aws-sdk/client-rds-data': 3.577.0 - '@cloudflare/workers-types': 4.20240512.0 - '@electric-sql/pglite': 0.1.5 - '@libsql/client': 0.4.3(bufferutil@4.0.8)(utf-8-validate@6.0.3) - '@neondatabase/serverless': 0.9.3 - '@planetscale/database': 1.18.0 - '@types/better-sqlite3': 7.6.10 - '@types/pg': 8.11.6 - '@vercel/postgres': 0.8.0 - better-sqlite3: 9.6.0 - mysql2: 2.3.3 - pg: 8.11.5 - postgres: 3.4.4 - - duplexer@0.1.2: {} - - eastasianwidth@0.2.0: {} - - emittery@1.0.3: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - emojilib@2.4.0: {} - - end-of-stream@1.4.4: - dependencies: - once: 1.4.0 - - env-paths@3.0.0: {} - - es5-ext@0.10.64: - dependencies: - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - esniff: 2.0.1 - next-tick: 1.1.0 - - es6-iterator@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-symbol: 3.1.4 - - es6-symbol@3.1.4: - dependencies: - d: 1.0.2 - ext: 1.7.0 - - es6-weak-map@2.0.3: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-iterator: 2.0.3 - es6-symbol: 3.1.4 - - esbuild-android-64@0.14.54: - optional: true - - esbuild-android-arm64@0.14.54: - optional: true - - esbuild-darwin-64@0.14.54: - optional: true - - esbuild-darwin-arm64@0.14.54: - optional: true - - esbuild-freebsd-64@0.14.54: - optional: true - - esbuild-freebsd-arm64@0.14.54: - optional: true - - esbuild-linux-32@0.14.54: - optional: true - - esbuild-linux-64@0.14.54: - optional: true - - esbuild-linux-arm64@0.14.54: - optional: true - - esbuild-linux-arm@0.14.54: - optional: true - - esbuild-linux-mips64le@0.14.54: - optional: true - - esbuild-linux-ppc64le@0.14.54: - optional: true - - esbuild-linux-riscv64@0.14.54: - optional: true - - esbuild-linux-s390x@0.14.54: - optional: true - - esbuild-netbsd-64@0.14.54: - optional: true - - esbuild-node-externals@1.13.1(esbuild@0.19.12): - dependencies: - esbuild: 0.19.12 - find-up: 5.0.0 - tslib: 2.6.2 - - esbuild-openbsd-64@0.14.54: - optional: true - - esbuild-register@3.5.0(esbuild@0.19.12): - dependencies: - debug: 4.3.4 - esbuild: 0.19.12 - transitivePeerDependencies: - - supports-color - - esbuild-sunos-64@0.14.54: - optional: true - - esbuild-windows-32@0.14.54: - optional: true - - esbuild-windows-64@0.14.54: - optional: true - - esbuild-windows-arm64@0.14.54: - optional: true - - esbuild@0.14.54: - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - - esbuild@0.17.19: - optionalDependencies: - '@esbuild/android-arm': 0.17.19 - '@esbuild/android-arm64': 0.17.19 - '@esbuild/android-x64': 0.17.19 - '@esbuild/darwin-arm64': 0.17.19 - '@esbuild/darwin-x64': 0.17.19 - '@esbuild/freebsd-arm64': 0.17.19 - '@esbuild/freebsd-x64': 0.17.19 - '@esbuild/linux-arm': 0.17.19 - '@esbuild/linux-arm64': 0.17.19 - '@esbuild/linux-ia32': 0.17.19 - '@esbuild/linux-loong64': 0.17.19 - '@esbuild/linux-mips64el': 0.17.19 - '@esbuild/linux-ppc64': 0.17.19 - '@esbuild/linux-riscv64': 0.17.19 - '@esbuild/linux-s390x': 0.17.19 - '@esbuild/linux-x64': 0.17.19 - '@esbuild/netbsd-x64': 0.17.19 - '@esbuild/openbsd-x64': 0.17.19 - '@esbuild/sunos-x64': 0.17.19 - '@esbuild/win32-arm64': 0.17.19 - '@esbuild/win32-ia32': 0.17.19 - '@esbuild/win32-x64': 0.17.19 - - esbuild@0.18.20: - optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 - - esbuild@0.19.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.19.12 - '@esbuild/android-arm': 0.19.12 - '@esbuild/android-arm64': 0.19.12 - '@esbuild/android-x64': 0.19.12 - '@esbuild/darwin-arm64': 0.19.12 - '@esbuild/darwin-x64': 0.19.12 - '@esbuild/freebsd-arm64': 0.19.12 - '@esbuild/freebsd-x64': 0.19.12 - '@esbuild/linux-arm': 0.19.12 - '@esbuild/linux-arm64': 0.19.12 - '@esbuild/linux-ia32': 0.19.12 - '@esbuild/linux-loong64': 0.19.12 - '@esbuild/linux-mips64el': 0.19.12 - '@esbuild/linux-ppc64': 0.19.12 - '@esbuild/linux-riscv64': 0.19.12 - '@esbuild/linux-s390x': 0.19.12 - '@esbuild/linux-x64': 0.19.12 - '@esbuild/netbsd-x64': 0.19.12 - '@esbuild/openbsd-x64': 0.19.12 - '@esbuild/sunos-x64': 0.19.12 - '@esbuild/win32-arm64': 0.19.12 - '@esbuild/win32-ia32': 0.19.12 - '@esbuild/win32-x64': 0.19.12 - - esbuild@0.20.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 - - escalade@3.1.2: {} - - escape-string-regexp@2.0.0: {} - - escape-string-regexp@4.0.0: {} - - escape-string-regexp@5.0.0: {} - - eslint-config-prettier@9.1.0(eslint@8.57.0): - dependencies: - eslint: 8.57.0 - - eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8): - dependencies: - eslint: 8.57.0 - prettier: 2.8.8 - prettier-linter-helpers: 1.0.0 - synckit: 0.8.8 - optionalDependencies: - eslint-config-prettier: 9.1.0(eslint@8.57.0) - - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint@8.57.0: - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.10.0 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.1 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - esniff@2.0.1: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - event-emitter: 0.3.5 - type: 2.7.2 - - espree@9.6.1: - dependencies: - acorn: 8.11.3 - acorn-jsx: 5.3.2(acorn@8.11.3) - eslint-visitor-keys: 3.4.3 - - esprima@4.0.1: {} - - esquery@1.5.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-walker@0.6.1: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.5 - - esutils@2.0.3: {} - - event-emitter@0.3.5: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - - event-stream@3.3.4: - dependencies: - duplexer: 0.1.2 - from: 0.1.7 - map-stream: 0.1.0 - pause-stream: 0.0.11 - split: 0.3.3 - stream-combiner: 0.0.4 - through: 2.3.8 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - execa@8.0.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - - exit-hook@2.2.1: {} - - expand-template@2.0.3: {} - - ext@1.7.0: - dependencies: - type: 2.7.2 - - fast-deep-equal@3.1.3: {} - - fast-diff@1.3.0: {} - - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fast-xml-parser@4.2.5: - dependencies: - strnum: 1.0.5 - - fastq@1.17.1: - dependencies: - reusify: 1.0.4 - - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - - fflate@0.8.2: {} - - figures@5.0.0: - dependencies: - escape-string-regexp: 5.0.0 - is-unicode-supported: 1.3.0 - - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - - file-uri-to-path@1.0.0: {} - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - find-up@6.3.0: - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - - flat-cache@3.2.0: - dependencies: - flatted: 3.3.1 - keyv: 4.5.4 - rimraf: 3.0.2 - - flatted@3.3.1: {} - - foreground-child@3.1.1: - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 - - from@0.1.7: {} - - fs-constants@1.0.0: {} - - fs-extra@11.2.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - fx@34.0.0: {} - - generate-function@2.3.1: - dependencies: - is-property: 1.0.2 - - get-caller-file@2.0.5: {} - - get-func-name@2.0.2: {} - - get-port@6.1.2: {} - - get-source@2.0.12: - dependencies: - data-uri-to-buffer: 2.0.2 - source-map: 0.6.1 - - get-stream@6.0.1: {} - - get-stream@8.0.1: {} - - get-tsconfig@4.7.5: - dependencies: - resolve-pkg-maps: 1.0.0 - - github-from-package@0.0.0: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob-to-regexp@0.4.1: {} - - glob@10.3.15: - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.4 - minipass: 7.1.1 - path-scurry: 1.11.1 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - glob@8.1.0: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 3.0.0 - - globby@13.2.2: - dependencies: - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 4.0.0 - - globrex@0.1.2: {} - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - - hanji@0.0.5: - dependencies: - lodash.throttle: 4.1.1 - sisteransi: 1.0.5 - - has-flag@4.0.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - heap@0.2.7: {} - - hono@4.3.9: {} - - human-signals@2.1.0: {} - - human-signals@5.0.0: {} - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - ieee754@1.2.1: {} - - ignore-by-default@2.1.0: {} - - ignore@5.3.1: {} - - import-fresh@3.3.0: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - indent-string@5.0.0: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - ini@1.3.8: {} - - irregular-plurals@3.5.0: {} - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-core-module@2.13.1: - dependencies: - hasown: 2.0.2 - - is-error@2.2.2: {} - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-fullwidth-code-point@4.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - is-path-inside@3.0.3: {} - - is-plain-object@5.0.0: {} - - is-promise@2.2.2: {} - - is-promise@4.0.0: {} - - is-property@1.0.2: {} - - is-stream@2.0.1: {} - - is-stream@3.0.0: {} - - is-unicode-supported@1.3.0: {} - - is-what@4.1.16: {} - - isexe@2.0.0: {} - - jackspeak@2.3.6: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - joycon@3.1.1: {} - - js-base64@3.7.7: {} - - js-string-escape@1.0.1: {} - - js-tokens@9.0.0: {} - - js-yaml@3.14.1: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - - json-buffer@3.0.1: {} - - json-diff@0.9.0: - dependencies: - cli-color: 2.0.4 - difflib: 0.2.4(patch_hash=jq4t3ysdpnbunjeje4v7nrqn2q) - dreamopt: 0.8.0 - - json-diff@1.0.6: - dependencies: - '@ewoudenberg/difflib': 0.1.0 - colors: 1.4.0 - dreamopt: 0.8.0 - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - libsql@0.2.0: - dependencies: - '@neon-rs/load': 0.0.4 - detect-libc: 2.0.2 - optionalDependencies: - '@libsql/darwin-arm64': 0.2.0 - '@libsql/darwin-x64': 0.2.0 - '@libsql/linux-arm64-gnu': 0.2.0 - '@libsql/linux-arm64-musl': 0.2.0 - '@libsql/linux-x64-gnu': 0.2.0 - '@libsql/linux-x64-musl': 0.2.0 - '@libsql/win32-x64-msvc': 0.2.0 - optional: true - - lilconfig@3.1.1: {} - - lines-and-columns@1.2.4: {} - - load-json-file@7.0.1: {} - - load-tsconfig@0.2.5: {} - - local-pkg@0.5.0: - dependencies: - mlly: 1.7.0 - pkg-types: 1.1.1 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - locate-path@7.2.0: - dependencies: - p-locate: 6.0.0 - - lodash.merge@4.6.2: {} - - lodash.sortby@4.7.0: {} - - lodash.throttle@4.1.1: {} - - lodash@4.17.21: {} - - long@4.0.0: {} - - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 - - lru-cache@10.2.2: {} - - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - - lru-cache@7.18.3: {} - - lru-queue@0.1.0: - dependencies: - es5-ext: 0.10.64 - - magic-string@0.25.9: - dependencies: - sourcemap-codec: 1.4.8 - - magic-string@0.30.10: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - - map-age-cleaner@0.1.3: - dependencies: - p-defer: 1.0.0 - - map-stream@0.1.0: {} - - marked-terminal@6.2.0(marked@9.1.6): - dependencies: - ansi-escapes: 6.2.1 - cardinal: 2.1.1 - chalk: 5.3.0 - cli-table3: 0.6.5 - marked: 9.1.6 - node-emoji: 2.1.3 - supports-hyperlinks: 3.0.0 - - marked@9.1.6: {} - - matcher@5.0.0: - dependencies: - escape-string-regexp: 5.0.0 - - md5-hex@3.0.1: - dependencies: - blueimp-md5: 2.19.0 - - mem@9.0.2: - dependencies: - map-age-cleaner: 0.1.3 - mimic-fn: 4.0.0 - - memoizee@0.4.15: - dependencies: - d: 1.0.2 - es5-ext: 0.10.64 - es6-weak-map: 2.0.3 - event-emitter: 0.3.5 - is-promise: 2.2.2 - lru-queue: 0.1.0 - next-tick: 1.1.0 - timers-ext: 0.1.7 - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - micromatch@4.0.5: - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime@3.0.0: {} - - mimic-fn@2.1.0: {} - - mimic-fn@4.0.0: {} - - mimic-response@3.1.0: {} - - miniflare@3.20240512.0(bufferutil@4.0.8)(utf-8-validate@6.0.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - acorn: 8.11.3 - acorn-walk: 8.3.2 - capnp-ts: 0.7.0 - exit-hook: 2.2.1 - glob-to-regexp: 0.4.1 - stoppable: 1.1.0 - undici: 5.28.4 - workerd: 1.20240512.0 - ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) - youch: 3.3.3 - zod: 3.23.8 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - - minimatch@5.1.6: - dependencies: - brace-expansion: 2.0.1 - - minimatch@7.4.6: - dependencies: - brace-expansion: 2.0.1 - - minimatch@9.0.4: - dependencies: - brace-expansion: 2.0.1 - - minimist@1.2.8: {} - - minipass@7.1.1: {} - - mkdirp-classic@0.5.3: {} - - mlly@1.7.0: - dependencies: - acorn: 8.11.3 - pathe: 1.1.2 - pkg-types: 1.1.1 - ufo: 1.5.3 - - ms@2.1.2: {} - - ms@2.1.3: {} - - mustache@4.2.0: {} - - mysql2@2.3.3: - dependencies: - denque: 2.1.0 - generate-function: 2.3.1 - iconv-lite: 0.6.3 - long: 4.0.0 - lru-cache: 6.0.0 - named-placeholders: 1.1.3 - seq-queue: 0.0.5 - sqlstring: 2.3.3 - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - named-placeholders@1.1.3: - dependencies: - lru-cache: 7.18.3 - - nan@2.19.0: - optional: true - - nanoid@3.3.7: {} - - napi-build-utils@1.0.2: {} - - natural-compare@1.4.0: {} - - next-tick@1.1.0: {} - - node-abi@3.62.0: - dependencies: - semver: 7.6.2 - - node-domexception@1.0.0: {} - - node-emoji@2.1.3: - dependencies: - '@sindresorhus/is': 4.6.0 - char-regex: 1.0.2 - emojilib: 2.4.0 - skin-tone: 2.0.0 - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-fetch@3.3.1: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - - node-forge@1.3.1: {} - - node-gyp-build@4.8.1: {} - - nofilter@3.1.0: {} - - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - - object-assign@4.1.1: {} - - obuf@1.1.2: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - p-defer@1.0.0: {} - - p-event@5.0.1: - dependencies: - p-timeout: 5.1.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-limit@4.0.0: - dependencies: - yocto-queue: 1.0.0 - - p-limit@5.0.0: - dependencies: - yocto-queue: 1.0.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-locate@6.0.0: - dependencies: - p-limit: 4.0.0 - - p-map@5.5.0: - dependencies: - aggregate-error: 4.0.1 - - p-timeout@5.1.0: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-ms@3.0.0: {} - - path-exists@4.0.0: {} - - path-exists@5.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-key@4.0.0: {} - - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.2.2 - minipass: 7.1.1 - - path-to-regexp@6.2.2: {} - - path-type@4.0.0: {} - - pathe@1.1.2: {} - - pathval@1.1.1: {} - - pause-stream@0.0.11: - dependencies: - through: 2.3.8 - - pg-cloudflare@1.1.1: - optional: true - - pg-connection-string@2.6.4: {} - - pg-int8@1.0.1: {} - - pg-numeric@1.0.2: {} - - pg-pool@3.6.2(pg@8.11.5): - dependencies: - pg: 8.11.5 - - pg-protocol@1.6.1: {} - - pg-types@2.2.0: - dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.0 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 - - pg-types@4.0.2: - dependencies: - pg-int8: 1.0.1 - pg-numeric: 1.0.2 - postgres-array: 3.0.2 - postgres-bytea: 3.0.0 - postgres-date: 2.1.0 - postgres-interval: 3.0.0 - postgres-range: 1.1.4 - - pg@8.11.5: - dependencies: - pg-connection-string: 2.6.4 - pg-pool: 3.6.2(pg@8.11.5) - pg-protocol: 1.6.1 - pg-types: 2.2.0 - pgpass: 1.0.5 - optionalDependencies: - pg-cloudflare: 1.1.1 - - pgpass@1.0.5: - dependencies: - split2: 4.2.0 - - picocolors@1.0.1: {} - - picomatch@2.3.1: {} - - pirates@4.0.6: {} - - pkg-conf@4.0.0: - dependencies: - find-up: 6.3.0 - load-json-file: 7.0.1 - - pkg-types@1.1.1: - dependencies: - confbox: 0.1.7 - mlly: 1.7.0 - pathe: 1.1.2 - - plur@5.1.0: - dependencies: - irregular-plurals: 3.5.0 - - pluralize@8.0.0: {} - - postcss-load-config@4.0.2(postcss@8.4.38): - dependencies: - lilconfig: 3.1.1 - yaml: 2.4.2 - optionalDependencies: - postcss: 8.4.38 - - postcss@8.4.38: - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 - - postgres-array@2.0.0: {} - - postgres-array@3.0.2: {} - - postgres-bytea@1.0.0: {} - - postgres-bytea@3.0.0: - dependencies: - obuf: 1.1.2 - - postgres-date@1.0.7: {} - - postgres-date@2.1.0: {} - - postgres-interval@1.2.0: - dependencies: - xtend: 4.0.2 - - postgres-interval@3.0.0: {} - - postgres-range@1.1.4: {} - - postgres@3.4.4: {} - - prebuild-install@7.1.2: - dependencies: - detect-libc: 2.0.3 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.8 - mkdirp-classic: 0.5.3 - napi-build-utils: 1.0.2 - node-abi: 3.62.0 - pump: 3.0.0 - rc: 1.2.8 - simple-get: 4.0.1 - tar-fs: 2.1.1 - tunnel-agent: 0.6.0 - - prelude-ls@1.2.1: {} - - prettier-linter-helpers@1.0.0: - dependencies: - fast-diff: 1.3.0 - - prettier@2.8.8: {} - - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - - pretty-ms@8.0.0: - dependencies: - parse-ms: 3.0.0 - - printable-characters@1.0.42: {} - - ps-tree@1.2.0: - dependencies: - event-stream: 3.3.4 - - pump@3.0.0: - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - - punycode@2.3.1: {} - - queue-microtask@1.2.3: {} - - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - - react-is@18.3.1: {} - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - redeyed@2.1.1: - dependencies: - esprima: 4.0.1 - - require-directory@2.1.1: {} - - resolve-cwd@3.0.0: - dependencies: - resolve-from: 5.0.0 - - resolve-from@4.0.0: {} - - resolve-from@5.0.0: {} - - resolve-pkg-maps@1.0.0: {} - - resolve.exports@2.0.2: {} - - resolve@1.22.8: - dependencies: - is-core-module: 2.13.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - reusify@1.0.4: {} - - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - - rollup-plugin-inject@3.0.2: - dependencies: - estree-walker: 0.6.1 - magic-string: 0.25.9 - rollup-pluginutils: 2.8.2 - - rollup-plugin-node-polyfills@0.2.1: - dependencies: - rollup-plugin-inject: 3.0.2 - - rollup-pluginutils@2.8.2: - dependencies: - estree-walker: 0.6.1 - - rollup@4.17.2: - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.17.2 - '@rollup/rollup-android-arm64': 4.17.2 - '@rollup/rollup-darwin-arm64': 4.17.2 - '@rollup/rollup-darwin-x64': 4.17.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.17.2 - '@rollup/rollup-linux-arm-musleabihf': 4.17.2 - '@rollup/rollup-linux-arm64-gnu': 4.17.2 - '@rollup/rollup-linux-arm64-musl': 4.17.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.17.2 - '@rollup/rollup-linux-riscv64-gnu': 4.17.2 - '@rollup/rollup-linux-s390x-gnu': 4.17.2 - '@rollup/rollup-linux-x64-gnu': 4.17.2 - '@rollup/rollup-linux-x64-musl': 4.17.2 - '@rollup/rollup-win32-arm64-msvc': 4.17.2 - '@rollup/rollup-win32-ia32-msvc': 4.17.2 - '@rollup/rollup-win32-x64-msvc': 4.17.2 - fsevents: 2.3.3 - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - safe-buffer@5.2.1: {} - - safer-buffer@2.1.2: {} - - selfsigned@2.4.1: - dependencies: - '@types/node-forge': 1.3.11 - node-forge: 1.3.1 - - semver@7.6.2: {} - - seq-queue@0.0.5: {} - - serialize-error@7.0.1: - dependencies: - type-fest: 0.13.1 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - siginfo@2.0.0: {} - - signal-exit@3.0.7: {} - - signal-exit@4.1.0: {} - - simple-concat@1.0.1: {} - - simple-get@4.0.1: - dependencies: - decompress-response: 6.0.0 - once: 1.4.0 - simple-concat: 1.0.1 - - sisteransi@1.0.5: {} - - skin-tone@2.0.0: - dependencies: - unicode-emoji-modifier-base: 1.0.0 - - slash@3.0.0: {} - - slash@4.0.0: {} - - slice-ansi@5.0.0: - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 4.0.0 - - source-map-js@1.2.0: {} - - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - source-map@0.8.0-beta.0: - dependencies: - whatwg-url: 7.1.0 - - sourcemap-codec@1.4.8: {} - - split-ca@1.0.1: {} - - split2@4.2.0: {} - - split@0.3.3: - dependencies: - through: 2.3.8 - - sprintf-js@1.0.3: {} - - sqlstring@2.3.3: {} - - ssh2@1.15.0: - dependencies: - asn1: 0.2.6 - bcrypt-pbkdf: 1.0.2 - optionalDependencies: - cpu-features: 0.0.10 - nan: 2.19.0 - - stack-utils@2.0.6: - dependencies: - escape-string-regexp: 2.0.0 - - stackback@0.0.2: {} - - stacktracey@2.1.8: - dependencies: - as-table: 1.0.55 - get-source: 2.0.12 - - std-env@3.7.0: {} - - stoppable@1.1.0: {} - - stream-combiner@0.0.4: - dependencies: - duplexer: 0.1.2 - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.0.1 - - strip-final-newline@2.0.0: {} - - strip-final-newline@3.0.0: {} - - strip-json-comments@2.0.1: {} - - strip-json-comments@3.1.1: {} - - strip-literal@2.1.0: - dependencies: - js-tokens: 9.0.0 - - strnum@1.0.5: {} - - sucrase@3.35.0: - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - commander: 4.1.1 - glob: 10.3.15 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - - superjson@2.2.1: - dependencies: - copy-anything: 3.0.5 - - supertap@3.0.1: - dependencies: - indent-string: 5.0.0 - js-yaml: 3.14.1 - serialize-error: 7.0.1 - strip-ansi: 7.1.0 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-hyperlinks@3.0.0: - dependencies: - has-flag: 4.0.0 - supports-color: 7.2.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - synckit@0.8.8: - dependencies: - '@pkgr/core': 0.1.1 - tslib: 2.6.2 - - tar-fs@2.0.1: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.0 - tar-stream: 2.2.0 - - tar-fs@2.1.1: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.0 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.4 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - - temp-dir@3.0.0: {} - - text-table@0.2.0: {} - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - through@2.3.8: {} - - time-zone@1.0.0: {} - - timers-ext@0.1.7: - dependencies: - es5-ext: 0.10.64 - next-tick: 1.1.0 - - tinybench@2.8.0: {} - - tinypool@0.8.4: {} - - tinyspy@2.2.1: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - tr46@0.0.3: {} - - tr46@1.0.1: - dependencies: - punycode: 2.3.1 - - tree-kill@1.2.2: {} - - ts-api-utils@1.3.0(typescript@5.4.5): - dependencies: - typescript: 5.4.5 - - ts-expose-internals-conditionally@1.0.0-empty.0: {} - - ts-interface-checker@0.1.13: {} - - tsconfck@3.0.3(typescript@5.4.5): - optionalDependencies: - typescript: 5.4.5 - - tslib@1.14.1: {} - - tslib@2.6.2: {} - - tsup@8.0.2(postcss@8.4.38)(typescript@5.4.5): - dependencies: - bundle-require: 4.1.0(esbuild@0.19.12) - cac: 6.7.14 - chokidar: 3.6.0 - debug: 4.3.4 - esbuild: 0.19.12 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 4.0.2(postcss@8.4.38) - resolve-from: 5.0.0 - rollup: 4.17.2 - source-map: 0.8.0-beta.0 - sucrase: 3.35.0 - tree-kill: 1.2.2 - optionalDependencies: - postcss: 8.4.38 - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - ts-node - - tsx@3.14.0: - dependencies: - esbuild: 0.18.20 - get-tsconfig: 4.7.5 - source-map-support: 0.5.21 - optionalDependencies: - fsevents: 2.3.3 - - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - - tweetnacl@0.14.5: {} - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - type-detect@4.0.8: {} - - type-fest@0.13.1: {} - - type-fest@0.20.2: {} - - type@2.7.2: {} - - typescript@5.3.3: {} - - typescript@5.4.5: {} - - ufo@1.5.3: {} - - undici-types@5.26.5: {} - - undici@5.28.4: - dependencies: - '@fastify/busboy': 2.1.1 - - unicode-emoji-modifier-base@1.0.0: {} - - universalify@2.0.1: {} - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - utf-8-validate@6.0.3: - dependencies: - node-gyp-build: 4.8.1 - - util-deprecate@1.0.2: {} - - uuid@9.0.1: {} - - validate-npm-package-name@5.0.1: {} - - vite-node@1.6.0(@types/node@18.19.33): - dependencies: - cac: 6.7.14 - debug: 4.3.4 - pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.2.11(@types/node@18.19.33) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - vite-tsconfig-paths@4.3.2(typescript@5.4.5)(vite@5.2.11(@types/node@18.19.33)): - dependencies: - debug: 4.3.4 - globrex: 0.1.2 - tsconfck: 3.0.3(typescript@5.4.5) - optionalDependencies: - vite: 5.2.11(@types/node@18.19.33) - transitivePeerDependencies: - - supports-color - - typescript - - vite@5.2.11(@types/node@18.19.33): - dependencies: - esbuild: 0.20.2 - postcss: 8.4.38 - rollup: 4.17.2 - optionalDependencies: - '@types/node': 18.19.33 - fsevents: 2.3.3 - - vitest@1.6.0(@types/node@18.19.33): - dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - acorn-walk: 8.3.2 - chai: 4.4.1 - debug: 4.3.4 - execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.10 - pathe: 1.1.2 - picocolors: 1.0.1 - std-env: 3.7.0 - strip-literal: 2.1.0 - tinybench: 2.8.0 - tinypool: 0.8.4 - vite: 5.2.11(@types/node@18.19.33) - vite-node: 1.6.0(@types/node@18.19.33) - why-is-node-running: 2.2.2 - optionalDependencies: - '@types/node': 18.19.33 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - web-streams-polyfill@3.3.3: {} - - webidl-conversions@3.0.1: {} - - webidl-conversions@4.0.2: {} - - webpod@0.0.2: {} - - well-known-symbols@2.0.0: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - whatwg-url@7.1.0: - dependencies: - lodash.sortby: 4.7.0 - tr46: 1.0.1 - webidl-conversions: 4.0.2 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - which@3.0.1: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.2.2: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - word-wrap@1.2.5: {} - - wordwrap@1.0.0: {} - - workerd@1.20240512.0: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20240512.0 - '@cloudflare/workerd-darwin-arm64': 1.20240512.0 - '@cloudflare/workerd-linux-64': 1.20240512.0 - '@cloudflare/workerd-linux-arm64': 1.20240512.0 - '@cloudflare/workerd-windows-64': 1.20240512.0 - - wrangler@3.57.0(@cloudflare/workers-types@4.20240512.0)(bufferutil@4.0.8)(utf-8-validate@6.0.3): - dependencies: - '@cloudflare/kv-asset-handler': 0.3.2 - '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) - '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) - blake3-wasm: 2.1.5 - chokidar: 3.6.0 - esbuild: 0.17.19 - miniflare: 3.20240512.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) - nanoid: 3.3.7 - path-to-regexp: 6.2.2 - resolve: 1.22.8 - resolve.exports: 2.0.2 - selfsigned: 2.4.1 - source-map: 0.6.1 - xxhash-wasm: 1.0.2 - optionalDependencies: - '@cloudflare/workers-types': 4.20240512.0 - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - wrappy@1.0.2: {} - - write-file-atomic@5.0.1: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 4.1.0 - - ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3): - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 6.0.3 - - ws@8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3): - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 6.0.3 - - xtend@4.0.2: {} - - xxhash-wasm@1.0.2: {} - - y18n@5.0.8: {} - - yallist@4.0.0: {} - - yaml@2.4.2: {} - - yargs-parser@21.1.1: {} - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.1.2 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yocto-queue@0.1.0: {} - - yocto-queue@1.0.0: {} - - youch@3.3.3: - dependencies: - cookie: 0.5.0 - mustache: 4.2.0 - stacktracey: 2.1.8 - - zod@3.23.8: {} - - zx@7.2.3: - dependencies: - '@types/fs-extra': 11.0.4 - '@types/minimist': 1.2.5 - '@types/node': 18.19.33 - '@types/ps-tree': 1.1.6 - '@types/which': 3.0.3 - chalk: 5.3.0 - fs-extra: 11.2.0 - fx: 34.0.0 - globby: 13.2.2 - minimist: 1.2.8 - node-fetch: 3.3.1 - ps-tree: 1.2.0 - webpod: 0.0.2 - which: 3.0.1 - yaml: 2.4.2 diff --git a/drizzle-kit/schema.ts b/drizzle-kit/schema.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/drizzle-kit/src/api.ts b/drizzle-kit/src/api.ts index 00cdb1b61..2ac44f5b7 100644 --- a/drizzle-kit/src/api.ts +++ b/drizzle-kit/src/api.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'crypto'; -import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'; import { LibSQLDatabase } from 'drizzle-orm/libsql'; import type { MySql2Database } from 'drizzle-orm/mysql2'; import { PgDatabase } from 'drizzle-orm/pg-core'; +import { SingleStore2Database } from 'drizzle-orm/singlestore'; import { columnsResolver, enumsResolver, @@ -22,12 +22,19 @@ import { generateMySqlSnapshot } from './serializer/mysqlSerializer'; import { prepareFromExports } from './serializer/pgImports'; import { PgSchema as PgSchemaKit, pgSchema, squashPgScheme } from './serializer/pgSchema'; import { generatePgSnapshot } from './serializer/pgSerializer'; +import { + SingleStoreSchema as SingleStoreSchemaKit, + singlestoreSchema, + squashSingleStoreScheme, +} from './serializer/singlestoreSchema'; +import { generateSingleStoreSnapshot } from './serializer/singlestoreSerializer'; import { SQLiteSchema as SQLiteSchemaKit, sqliteSchema, squashSqliteScheme } from './serializer/sqliteSchema'; import { generateSqliteSnapshot } from './serializer/sqliteSerializer'; import type { DB, SQLiteDB } from './utils'; export type DrizzleSnapshotJSON = PgSchemaKit; export type DrizzleSQLiteSnapshotJSON = SQLiteSchemaKit; export type DrizzleMySQLSnapshotJSON = MySQLSchemaKit; +export type DrizzleSingleStoreSnapshotJSON = SingleStoreSchemaKit; export const generateDrizzleJson = ( imports: Record, @@ -340,6 +347,108 @@ export const pushMySQLSchema = async ( }; }; +// SingleStore + +export const generateSingleStoreDrizzleJson = async ( + imports: Record, + prevId?: string, +): Promise => { + const { prepareFromExports } = await import('./serializer/singlestoreImports'); + + const prepared = prepareFromExports(imports); + + const id = randomUUID(); + + const snapshot = generateSingleStoreSnapshot(prepared.tables); + + return { + ...snapshot, + id, + prevId: prevId ?? originUUID, + }; +}; + +export const generateSingleStoreMigration = async ( + prev: DrizzleSingleStoreSnapshotJSON, + cur: DrizzleSingleStoreSnapshotJSON, +) => { + const { applySingleStoreSnapshotsDiff } = await import('./snapshotsDiffer'); + + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); + + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { sqlStatements } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + validatedPrev, + validatedCur, + ); + + return sqlStatements; +}; + +export const pushSingleStoreSchema = async ( + imports: Record, + drizzleInstance: SingleStore2Database, + databaseName: string, +) => { + const { applySingleStoreSnapshotsDiff } = await import('./snapshotsDiffer'); + const { logSuggestionsAndReturn } = await import( + './cli/commands/singlestorePushUtils' + ); + const { singlestorePushIntrospect } = await import( + './cli/commands/singlestoreIntrospect' + ); + const { sql } = await import('drizzle-orm'); + + const db: DB = { + query: async (query: string, params?: any[]) => { + const res = await drizzleInstance.execute(sql.raw(query)); + return res[0] as unknown as any[]; + }, + }; + const cur = await generateSingleStoreDrizzleJson(imports); + const { schema: prev } = await singlestorePushIntrospect(db, databaseName, []); + + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); + + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { statements } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + validatedPrev, + validatedCur, + 'push', + ); + + const { shouldAskForApprove, statementsToExecute, infoToPrint } = await logSuggestionsAndReturn( + db, + statements, + validatedCur, + ); + + return { + hasDataLoss: shouldAskForApprove, + warnings: infoToPrint, + statementsToExecute, + apply: async () => { + for (const dStmnt of statementsToExecute) { + await db.query(dStmnt); + } + }, + }; +}; + export const upPgSnapshot = (snapshot: Record) => { if (snapshot.version === '5') { return upPgV7(upPgV6(snapshot)); diff --git a/drizzle-kit/src/cli/commands/introspect.ts b/drizzle-kit/src/cli/commands/introspect.ts index 61ba0b44a..9b5a044f6 100644 --- a/drizzle-kit/src/cli/commands/introspect.ts +++ b/drizzle-kit/src/cli/commands/introspect.ts @@ -4,21 +4,31 @@ import { render, renderWithTask } from 'hanji'; import { Minimatch } from 'minimatch'; import { join } from 'path'; import { plural, singular } from 'pluralize'; +import { drySingleStore, SingleStoreSchema, squashSingleStoreScheme } from 'src/serializer/singlestoreSchema'; import { assertUnreachable, originUUID } from '../../global'; import { schemaToTypeScript as mysqlSchemaToTypeScript } from '../../introspect-mysql'; import { paramNameFor, schemaToTypeScript as postgresSchemaToTypeScript } from '../../introspect-pg'; +import { schemaToTypeScript as singlestoreSchemaToTypeScript } from '../../introspect-singlestore'; import { schemaToTypeScript as sqliteSchemaToTypeScript } from '../../introspect-sqlite'; import { dryMySql, MySqlSchema, squashMysqlScheme } from '../../serializer/mysqlSchema'; import { fromDatabase as fromMysqlDatabase } from '../../serializer/mysqlSerializer'; import { dryPg, type PgSchema, squashPgScheme } from '../../serializer/pgSchema'; import { fromDatabase as fromPostgresDatabase } from '../../serializer/pgSerializer'; +import { fromDatabase as fromSingleStoreDatabase } from '../../serializer/singlestoreSerializer'; import { drySQLite, type SQLiteSchema, squashSqliteScheme } from '../../serializer/sqliteSchema'; import { fromDatabase as fromSqliteDatabase } from '../../serializer/sqliteSerializer'; -import { applyMysqlSnapshotsDiff, applyPgSnapshotsDiff, applySqliteSnapshotsDiff } from '../../snapshotsDiffer'; +import { + applyMysqlSnapshotsDiff, + applyPgSnapshotsDiff, + applySingleStoreSnapshotsDiff, + applySqliteSnapshotsDiff, +} from '../../snapshotsDiffer'; import { prepareOutFolder } from '../../utils'; import type { Casing, Prefix } from '../validations/common'; +import { LibSQLCredentials } from '../validations/libsql'; import type { MysqlCredentials } from '../validations/mysql'; import type { PostgresCredentials } from '../validations/postgres'; +import { SingleStoreCredentials } from '../validations/singlestore'; import type { SqliteCredentials } from '../validations/sqlite'; import { IntrospectProgress } from '../views'; import { @@ -201,7 +211,7 @@ export const introspectMysql = async ( writeFileSync(relationsFile, relationsTs.file); console.log(); - const { snapshots, journal } = prepareOutFolder(out, 'postgresql'); + const { snapshots, journal } = prepareOutFolder(out, 'mysql'); if (snapshots.length === 0) { const { sqlStatements, _meta } = await applyMysqlSnapshotsDiff( @@ -254,6 +264,102 @@ export const introspectMysql = async ( process.exit(0); }; +export const introspectSingleStore = async ( + casing: Casing, + out: string, + breakpoints: boolean, + credentials: SingleStoreCredentials, + tablesFilter: string[], + prefix: Prefix, +) => { + const { connectToSingleStore } = await import('../connections'); + const { db, database } = await connectToSingleStore(credentials); + + const matchers = tablesFilter.map((it) => { + return new Minimatch(it); + }); + + const filter = (tableName: string) => { + if (matchers.length === 0) return true; + + let flags: boolean[] = []; + + for (let matcher of matchers) { + if (matcher.negate) { + if (!matcher.match(tableName)) { + flags.push(false); + } + } + + if (matcher.match(tableName)) { + flags.push(true); + } + } + + if (flags.length > 0) { + return flags.every(Boolean); + } + return false; + }; + + const progress = new IntrospectProgress(); + const res = await renderWithTask( + progress, + fromSingleStoreDatabase(db, database, filter, (stage, count, status) => { + progress.update(stage, count, status); + }), + ); + + const schema = { id: originUUID, prevId: '', ...res } as SingleStoreSchema; + const ts = singlestoreSchemaToTypeScript(schema, casing); + const { internal, ...schemaWithoutInternals } = schema; + + const schemaFile = join(out, 'schema.ts'); + writeFileSync(schemaFile, ts.file); + console.log(); + + const { snapshots, journal } = prepareOutFolder(out, 'postgresql'); + + if (snapshots.length === 0) { + const { sqlStatements, _meta } = await applySingleStoreSnapshotsDiff( + squashSingleStoreScheme(drySingleStore), + squashSingleStoreScheme(schema), + tablesResolver, + columnsResolver, + drySingleStore, + schema, + ); + + writeResult({ + cur: schema, + sqlStatements, + journal, + _meta, + outFolder: out, + breakpoints, + type: 'introspect', + prefixMode: prefix, + }); + } else { + render( + `[${ + chalk.blue( + 'i', + ) + }] No SQL generated, you already have migrations in project`, + ); + } + + render( + `[${ + chalk.green( + '✓', + ) + }] You schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`, + ); + process.exit(0); +}; + export const introspectSqlite = async ( casing: Casing, out: string, @@ -312,7 +418,118 @@ export const introspectSqlite = async ( writeFileSync(relationsFile, relationsTs.file); console.log(); - const { snapshots, journal } = prepareOutFolder(out, 'postgresql'); + const { snapshots, journal } = prepareOutFolder(out, 'sqlite'); + + if (snapshots.length === 0) { + const { sqlStatements, _meta } = await applySqliteSnapshotsDiff( + squashSqliteScheme(drySQLite), + squashSqliteScheme(schema), + tablesResolver, + columnsResolver, + drySQLite, + schema, + ); + + writeResult({ + cur: schema, + sqlStatements, + journal, + _meta, + outFolder: out, + breakpoints, + type: 'introspect', + prefixMode: prefix, + }); + } else { + render( + `[${ + chalk.blue( + 'i', + ) + }] No SQL generated, you already have migrations in project`, + ); + } + + render( + `[${ + chalk.green( + '✓', + ) + }] You schema file is ready ➜ ${chalk.bold.underline.blue(schemaFile)} 🚀`, + ); + render( + `[${ + chalk.green( + '✓', + ) + }] You relations file is ready ➜ ${ + chalk.bold.underline.blue( + relationsFile, + ) + } 🚀`, + ); + process.exit(0); +}; + +export const introspectLibSQL = async ( + casing: Casing, + out: string, + breakpoints: boolean, + credentials: LibSQLCredentials, + tablesFilter: string[], + prefix: Prefix, +) => { + const { connectToLibSQL } = await import('../connections'); + const db = await connectToLibSQL(credentials); + + const matchers = tablesFilter.map((it) => { + return new Minimatch(it); + }); + + const filter = (tableName: string) => { + if (matchers.length === 0) return true; + + let flags: boolean[] = []; + + for (let matcher of matchers) { + if (matcher.negate) { + if (!matcher.match(tableName)) { + flags.push(false); + } + } + + if (matcher.match(tableName)) { + flags.push(true); + } + } + + if (flags.length > 0) { + return flags.every(Boolean); + } + return false; + }; + + const progress = new IntrospectProgress(); + const res = await renderWithTask( + progress, + fromSqliteDatabase(db, filter, (stage, count, status) => { + progress.update(stage, count, status); + }), + ); + + const schema = { id: originUUID, prevId: '', ...res } as SQLiteSchema; + const ts = sqliteSchemaToTypeScript(schema, casing); + const relationsTs = relationsToTypeScript(schema, casing); + + // check orm and orm-pg api version + + const schemaFile = join(out, 'schema.ts'); + writeFileSync(schemaFile, ts.file); + const relationsFile = join(out, 'relations.ts'); + writeFileSync(relationsFile, relationsTs.file); + console.log(); + + const { snapshots, journal } = prepareOutFolder(out, 'sqlite'); if (snapshots.length === 0) { const { sqlStatements, _meta } = await applySqliteSnapshotsDiff( diff --git a/drizzle-kit/src/cli/commands/libSqlPushUtils.ts b/drizzle-kit/src/cli/commands/libSqlPushUtils.ts new file mode 100644 index 000000000..01bb61334 --- /dev/null +++ b/drizzle-kit/src/cli/commands/libSqlPushUtils.ts @@ -0,0 +1,346 @@ +import chalk from 'chalk'; + +import { JsonStatement } from 'src/jsonStatements'; +import { findAddedAndRemoved, SQLiteDB } from 'src/utils'; +import { SQLiteSchemaInternal, SQLiteSchemaSquashed, SQLiteSquasher } from '../../serializer/sqliteSchema'; +import { + CreateSqliteIndexConvertor, + fromJson, + LibSQLModifyColumn, + SQLiteCreateTableConvertor, + SQLiteDropTableConvertor, + SqliteRenameTableConvertor, +} from '../../sqlgenerator'; + +export const getOldTableName = ( + tableName: string, + meta: SQLiteSchemaInternal['_meta'], +) => { + for (const key of Object.keys(meta.tables)) { + const value = meta.tables[key]; + if (`"${tableName}"` === value) { + return key.substring(1, key.length - 1); + } + } + return tableName; +}; + +export const _moveDataStatements = ( + tableName: string, + json: SQLiteSchemaSquashed, + dataLoss: boolean = false, +) => { + const statements: string[] = []; + + const newTableName = `__new_${tableName}`; + + // create table statement from a new json2 with proper name + const tableColumns = Object.values(json.tables[tableName].columns); + const referenceData = Object.values(json.tables[tableName].foreignKeys); + const compositePKs = Object.values( + json.tables[tableName].compositePrimaryKeys, + ).map((it) => SQLiteSquasher.unsquashPK(it)); + + const fks = referenceData.map((it) => SQLiteSquasher.unsquashPushFK(it)); + + // create new table + statements.push( + new SQLiteCreateTableConvertor().convert({ + type: 'sqlite_create_table', + tableName: newTableName, + columns: tableColumns, + referenceData: fks, + compositePKs, + }), + ); + + // move data + if (!dataLoss) { + const columns = Object.keys(json.tables[tableName].columns).map( + (c) => `"${c}"`, + ); + + statements.push( + `INSERT INTO \`${newTableName}\`(${ + columns.join( + ', ', + ) + }) SELECT ${columns.join(', ')} FROM \`${tableName}\`;`, + ); + } + + statements.push( + new SQLiteDropTableConvertor().convert({ + type: 'drop_table', + tableName: tableName, + schema: '', + }), + ); + + // rename table + statements.push( + new SqliteRenameTableConvertor().convert({ + fromSchema: '', + tableNameFrom: newTableName, + tableNameTo: tableName, + toSchema: '', + type: 'rename_table', + }), + ); + + for (const idx of Object.values(json.tables[tableName].indexes)) { + statements.push( + new CreateSqliteIndexConvertor().convert({ + type: 'create_index', + tableName: tableName, + schema: '', + data: idx, + }), + ); + } + return statements; +}; + +export const libSqlLogSuggestionsAndReturn = async ( + connection: SQLiteDB, + statements: JsonStatement[], + json1: SQLiteSchemaSquashed, + json2: SQLiteSchemaSquashed, + meta: SQLiteSchemaInternal['_meta'], +) => { + let shouldAskForApprove = false; + const statementsToExecute: string[] = []; + const infoToPrint: string[] = []; + + const tablesToRemove: string[] = []; + const columnsToRemove: string[] = []; + const tablesToTruncate: string[] = []; + + for (const statement of statements) { + if (statement.type === 'drop_table') { + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.tableName, + ) + } table with ${count} items`, + ); + tablesToRemove.push(statement.tableName); + shouldAskForApprove = true; + } + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if (statement.type === 'alter_table_drop_column') { + const tableName = statement.tableName; + + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.columnName, + ) + } column in ${tableName} table with ${count} items`, + ); + columnsToRemove.push(`${tableName}_${statement.columnName}`); + shouldAskForApprove = true; + } + + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if ( + statement.type === 'sqlite_alter_table_add_column' + && statement.column.notNull + && !statement.column.default + ) { + const newTableName = statement.tableName; + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${newTableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + statement.column.name, + ) + } column without default value, which contains ${count} items`, + ); + + tablesToTruncate.push(newTableName); + statementsToExecute.push(`delete from ${newTableName};`); + + shouldAskForApprove = true; + } + + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if (statement.type === 'alter_table_alter_column_set_notnull') { + const tableName = statement.tableName; + + if ( + statement.type === 'alter_table_alter_column_set_notnull' + && typeof statement.columnDefault === 'undefined' + ) { + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to add not-null constraint to ${ + chalk.underline( + statement.columnName, + ) + } column without default value, which contains ${count} items`, + ); + + tablesToTruncate.push(tableName); + statementsToExecute.push(`delete from \`${tableName}\``); + shouldAskForApprove = true; + } + } + + const modifyStatements = new LibSQLModifyColumn().convert(statement, json2); + + statementsToExecute.push( + ...(Array.isArray(modifyStatements) ? modifyStatements : [modifyStatements]), + ); + } else if (statement.type === 'recreate_table') { + const tableName = statement.tableName; + + let dataLoss = false; + + const oldTableName = getOldTableName(tableName, meta); + + const prevColumnNames = Object.keys(json1.tables[oldTableName].columns); + const currentColumnNames = Object.keys(json2.tables[tableName].columns); + const { removedColumns, addedColumns } = findAddedAndRemoved( + prevColumnNames, + currentColumnNames, + ); + + if (removedColumns.length) { + for (const removedColumn of removedColumns) { + const res = await connection.query<{ count: string }>( + `select count(\`${tableName}\`.\`${removedColumn}\`) as count from \`${tableName}\``, + ); + + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + removedColumn, + ) + } column in ${tableName} table with ${count} items`, + ); + columnsToRemove.push(removedColumn); + shouldAskForApprove = true; + } + } + } + + if (addedColumns.length) { + for (const addedColumn of addedColumns) { + const [res] = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, + ); + + const columnConf = json2.tables[tableName].columns[addedColumn]; + + const count = Number(res.count); + if (count > 0 && columnConf.notNull && !columnConf.default) { + dataLoss = true; + + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + addedColumn, + ) + } column without default value to table, which contains ${count} items`, + ); + shouldAskForApprove = true; + tablesToTruncate.push(tableName); + + statementsToExecute.push(`DELETE FROM \`${tableName}\`;`); + } + } + } + + // check if some tables referencing current for pragma + const tablesReferencingCurrent: string[] = []; + + for (const table of Object.values(json2.tables)) { + const tablesRefs = Object.values(json2.tables[table.name].foreignKeys) + .filter((t) => SQLiteSquasher.unsquashPushFK(t).tableTo === tableName) + .map((it) => SQLiteSquasher.unsquashPushFK(it).tableFrom); + + tablesReferencingCurrent.push(...tablesRefs); + } + + if (!tablesReferencingCurrent.length) { + statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss)); + continue; + } + + // recreate table + statementsToExecute.push( + ..._moveDataStatements(tableName, json2, dataLoss), + ); + } else if ( + statement.type === 'alter_table_alter_column_set_generated' + || statement.type === 'alter_table_alter_column_drop_generated' + ) { + const tableName = statement.tableName; + + const res = await connection.query<{ count: string }>( + `select count("${statement.columnName}") as count from \`${tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.columnName, + ) + } column in ${tableName} table with ${count} items`, + ); + columnsToRemove.push(`${tableName}_${statement.columnName}`); + shouldAskForApprove = true; + } + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else { + const fromJsonStatement = fromJson([statement], 'turso', 'push', json2); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } + } + + return { + statementsToExecute: [...new Set(statementsToExecute)], + shouldAskForApprove, + infoToPrint, + columnsToRemove: [...new Set(columnsToRemove)], + tablesToTruncate: [...new Set(tablesToTruncate)], + tablesToRemove: [...new Set(tablesToRemove)], + }; +}; diff --git a/drizzle-kit/src/cli/commands/migrate.ts b/drizzle-kit/src/cli/commands/migrate.ts index 8ef469fa1..6fd9120ae 100644 --- a/drizzle-kit/src/cli/commands/migrate.ts +++ b/drizzle-kit/src/cli/commands/migrate.ts @@ -4,6 +4,8 @@ import { prepareMySqlMigrationSnapshot, preparePgDbPushSnapshot, preparePgMigrationSnapshot, + prepareSingleStoreDbPushSnapshot, + prepareSingleStoreMigrationSnapshot, prepareSQLiteDbPushSnapshot, prepareSqliteMigrationSnapshot, } from '../../migrationPreparator'; @@ -11,14 +13,17 @@ import { import chalk from 'chalk'; import { render } from 'hanji'; import path, { join } from 'path'; +import { SingleStoreSchema, singlestoreSchema, squashSingleStoreScheme } from 'src/serializer/singlestoreSchema'; import { TypeOf } from 'zod'; import type { CommonSchema } from '../../schemaValidator'; import { MySqlSchema, mysqlSchema, squashMysqlScheme } from '../../serializer/mysqlSchema'; import { PgSchema, pgSchema, squashPgScheme } from '../../serializer/pgSchema'; import { SQLiteSchema, sqliteSchema, squashSqliteScheme } from '../../serializer/sqliteSchema'; import { + applyLibSQLSnapshotsDiff, applyMysqlSnapshotsDiff, applyPgSnapshotsDiff, + applySingleStoreSnapshotsDiff, applySqliteSnapshotsDiff, Column, ColumnsResolverInput, @@ -391,6 +396,150 @@ export const prepareAndMigrateMysql = async (config: GenerateConfig) => { } }; +// Not needed for now +function mySingleStoreSchemaSuggestions( + curSchema: TypeOf, + prevSchema: TypeOf, +) { + const suggestions: string[] = []; + const usedSuggestions: string[] = []; + const suggestionTypes = { + // TODO: Check if SingleStore has serial type + serial: withStyle.errorWarning( + `We deprecated the use of 'serial' for SingleStore starting from version 0.20.0. In SingleStore, 'serial' is simply an alias for 'bigint unsigned not null auto_increment unique,' which creates all constraints and indexes for you. This may make the process less explicit for both users and drizzle-kit push commands`, + ), + }; + + for (const table of Object.values(curSchema.tables)) { + for (const column of Object.values(table.columns)) { + if (column.type === 'serial') { + if (!usedSuggestions.includes('serial')) { + suggestions.push(suggestionTypes['serial']); + } + + const uniqueForSerial = Object.values( + prevSchema.tables[table.name].uniqueConstraints, + ).find((it) => it.columns[0] === column.name); + + suggestions.push( + `\n` + + withStyle.suggestion( + `We are suggesting to change ${ + chalk.blue( + column.name, + ) + } column in ${ + chalk.blueBright( + table.name, + ) + } table from serial to bigint unsigned\n\n${ + chalk.blueBright( + `bigint("${column.name}", { mode: "number", unsigned: true }).notNull().autoincrement().unique(${ + uniqueForSerial?.name ? `"${uniqueForSerial?.name}"` : '' + })`, + ) + }`, + ), + ); + } + } + } + + return suggestions; +} + +// Intersect with prepareAnMigrate +export const prepareSingleStorePush = async ( + schemaPath: string | string[], + snapshot: SingleStoreSchema, +) => { + try { + const { prev, cur } = await prepareSingleStoreDbPushSnapshot( + snapshot, + schemaPath, + ); + + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); + + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + validatedPrev, + validatedCur, + 'push', + ); + + return { sqlStatements, statements, validatedCur, validatedPrev }; + } catch (e) { + console.error(e); + process.exit(1); + } +}; + +export const prepareAndMigrateSingleStore = async (config: GenerateConfig) => { + const outFolder = config.out; + const schemaPath = config.schema; + + try { + // TODO: remove + assertV1OutFolder(outFolder); + + const { snapshots, journal } = prepareMigrationFolder(outFolder, 'singlestore'); + const { prev, cur, custom } = await prepareSingleStoreMigrationSnapshot( + snapshots, + schemaPath, + ); + + const validatedPrev = singlestoreSchema.parse(prev); + const validatedCur = singlestoreSchema.parse(cur); + + if (config.custom) { + writeResult({ + cur: custom, + sqlStatements: [], + journal, + outFolder, + name: config.name, + breakpoints: config.breakpoints, + type: 'custom', + prefixMode: config.prefix, + }); + return; + } + + const squashedPrev = squashSingleStoreScheme(validatedPrev); + const squashedCur = squashSingleStoreScheme(validatedCur); + + const { sqlStatements, statements, _meta } = await applySingleStoreSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + validatedPrev, + validatedCur, + ); + + writeResult({ + cur, + sqlStatements, + journal, + _meta, + outFolder, + name: config.name, + breakpoints: config.breakpoints, + prefixMode: config.prefix, + }); + } catch (e) { + console.error(e); + } +}; + export const prepareAndMigrateSqlite = async (config: GenerateConfig) => { const outFolder = config.out; const schemaPath = config.schema; @@ -450,6 +599,65 @@ export const prepareAndMigrateSqlite = async (config: GenerateConfig) => { } }; +export const prepareAndMigrateLibSQL = async (config: GenerateConfig) => { + const outFolder = config.out; + const schemaPath = config.schema; + + try { + assertV1OutFolder(outFolder); + + const { snapshots, journal } = prepareMigrationFolder(outFolder, 'sqlite'); + const { prev, cur, custom } = await prepareSqliteMigrationSnapshot( + snapshots, + schemaPath, + ); + + const validatedPrev = sqliteSchema.parse(prev); + const validatedCur = sqliteSchema.parse(cur); + + if (config.custom) { + writeResult({ + cur: custom, + sqlStatements: [], + journal, + outFolder, + name: config.name, + breakpoints: config.breakpoints, + bundle: config.bundle, + type: 'custom', + prefixMode: config.prefix, + }); + return; + } + + const squashedPrev = squashSqliteScheme(validatedPrev); + const squashedCur = squashSqliteScheme(validatedCur); + + const { sqlStatements, _meta } = await applyLibSQLSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + validatedPrev, + validatedCur, + ); + + writeResult({ + cur, + sqlStatements, + journal, + _meta, + outFolder, + name: config.name, + breakpoints: config.breakpoints, + bundle: config.bundle, + prefixMode: config.prefix, + }); + } catch (e) { + console.error(e); + } +}; + export const prepareSQLitePush = async ( schemaPath: string | string[], snapshot: SQLiteSchema, @@ -481,6 +689,37 @@ export const prepareSQLitePush = async ( }; }; +export const prepareLibSQLPush = async ( + schemaPath: string | string[], + snapshot: SQLiteSchema, +) => { + const { prev, cur } = await prepareSQLiteDbPushSnapshot(snapshot, schemaPath); + + const validatedPrev = sqliteSchema.parse(prev); + const validatedCur = sqliteSchema.parse(cur); + + const squashedPrev = squashSqliteScheme(validatedPrev, 'push'); + const squashedCur = squashSqliteScheme(validatedCur, 'push'); + + const { sqlStatements, statements, _meta } = await applyLibSQLSnapshotsDiff( + squashedPrev, + squashedCur, + tablesResolver, + columnsResolver, + validatedPrev, + validatedCur, + 'push', + ); + + return { + sqlStatements, + statements, + squashedPrev, + squashedCur, + meta: _meta, + }; +}; + const freeeeeeze = (obj: any) => { Object.freeze(obj); for (let key in obj) { diff --git a/drizzle-kit/src/cli/commands/push.ts b/drizzle-kit/src/cli/commands/push.ts index e48a5da9e..0cb3a8d28 100644 --- a/drizzle-kit/src/cli/commands/push.ts +++ b/drizzle-kit/src/cli/commands/push.ts @@ -2,12 +2,22 @@ import chalk from 'chalk'; import { render } from 'hanji'; import { fromJson } from '../../sqlgenerator'; import { Select } from '../selector-ui'; +import { LibSQLCredentials } from '../validations/libsql'; import type { MysqlCredentials } from '../validations/mysql'; import { withStyle } from '../validations/outputs'; import type { PostgresCredentials } from '../validations/postgres'; +import { SingleStoreCredentials } from '../validations/singlestore'; import type { SqliteCredentials } from '../validations/sqlite'; -import { filterStatements, logSuggestionsAndReturn } from './mysqlPushUtils'; +import { libSqlLogSuggestionsAndReturn } from './libSqlPushUtils'; +import { + filterStatements as mySqlFilterStatements, + logSuggestionsAndReturn as mySqlLogSuggestionsAndReturn, +} from './mysqlPushUtils'; import { pgSuggestions } from './pgPushUtils'; +import { + filterStatements as singleStoreFilterStatements, + logSuggestionsAndReturn as singleStoreLogSuggestionsAndReturn, +} from './singlestorePushUtils'; import { logSuggestionsAndReturn as sqliteSuggestions } from './sqlitePushUtils'; export const mysqlPush = async ( @@ -28,7 +38,7 @@ export const mysqlPush = async ( const statements = await prepareMySQLPush(schemaPath, schema); - const filteredStatements = filterStatements( + const filteredStatements = mySqlFilterStatements( statements.statements ?? [], statements.validatedCur, statements.validatedPrev, @@ -46,7 +56,7 @@ export const mysqlPush = async ( tablesToTruncate, infoToPrint, schemasToRemove, - } = await logSuggestionsAndReturn( + } = await mySqlLogSuggestionsAndReturn( db, filteredStatements, statements.validatedCur, @@ -67,6 +77,145 @@ export const mysqlPush = async ( } }); + if (verbose) { + console.log(); + console.log( + withStyle.warning('You are about to execute current statements:'), + ); + console.log(); + console.log( + [...uniqueSqlStatementsToExecute, ...uniqueFilteredSqlStatements] + .map((s) => chalk.blue(s)) + .join('\n'), + ); + console.log(); + } + + if (!force && strict) { + if (!shouldAskForApprove) { + const { status, data } = await render( + new Select(['No, abort', `Yes, I want to execute all statements`]), + ); + if (data?.index === 0) { + render(`[${chalk.red('x')}] All changes were aborted`); + process.exit(0); + } + } + } + + if (!force && shouldAskForApprove) { + console.log(withStyle.warning('Found data-loss statements:')); + console.log(infoToPrint.join('\n')); + console.log(); + console.log( + chalk.red.bold( + 'THIS ACTION WILL CAUSE DATA LOSS AND CANNOT BE REVERTED\n', + ), + ); + + console.log(chalk.white('Do you still want to push changes?')); + + const { status, data } = await render( + new Select([ + 'No, abort', + `Yes, I want to${ + tablesToRemove.length > 0 + ? ` remove ${tablesToRemove.length} ${tablesToRemove.length > 1 ? 'tables' : 'table'},` + : ' ' + }${ + columnsToRemove.length > 0 + ? ` remove ${columnsToRemove.length} ${columnsToRemove.length > 1 ? 'columns' : 'column'},` + : ' ' + }${ + tablesToTruncate.length > 0 + ? ` truncate ${tablesToTruncate.length} ${tablesToTruncate.length > 1 ? 'tables' : 'table'}` + : '' + }` + .replace(/(^,)|(,$)/g, '') + .replace(/ +(?= )/g, ''), + ]), + ); + if (data?.index === 0) { + render(`[${chalk.red('x')}] All changes were aborted`); + process.exit(0); + } + } + + for (const dStmnt of uniqueSqlStatementsToExecute) { + await db.query(dStmnt); + } + + for (const statement of uniqueFilteredSqlStatements) { + await db.query(statement); + } + if (filteredStatements.length > 0) { + render(`[${chalk.green('✓')}] Changes applied`); + } else { + render(`[${chalk.blue('i')}] No changes detected`); + } + } + } catch (e) { + console.log(e); + } +}; + +export const singlestorePush = async ( + schemaPath: string | string[], + credentials: SingleStoreCredentials, + tablesFilter: string[], + strict: boolean, + verbose: boolean, + force: boolean, +) => { + const { connectToSingleStore } = await import('../connections'); + const { singlestorePushIntrospect } = await import('./singlestoreIntrospect'); + + const { db, database } = await connectToSingleStore(credentials); + + const { schema } = await singlestorePushIntrospect(db, database, tablesFilter); + const { prepareSingleStorePush } = await import('./migrate'); + + const statements = await prepareSingleStorePush(schemaPath, schema); + + const filteredStatements = singleStoreFilterStatements( + statements.statements ?? [], + statements.validatedCur, + statements.validatedPrev, + ); + + try { + if (filteredStatements.length === 0) { + render(`[${chalk.blue('i')}] No changes detected`); + } else { + const { + shouldAskForApprove, + statementsToExecute, + columnsToRemove, + tablesToRemove, + tablesToTruncate, + infoToPrint, + schemasToRemove, + } = await singleStoreLogSuggestionsAndReturn( + db, + filteredStatements, + statements.validatedCur, + ); + + const filteredSqlStatements = fromJson(filteredStatements, 'singlestore'); + + const uniqueSqlStatementsToExecute: string[] = []; + statementsToExecute.forEach((ss) => { + if (!uniqueSqlStatementsToExecute.includes(ss)) { + uniqueSqlStatementsToExecute.push(ss); + } + }); + const uniqueFilteredSqlStatements: string[] = []; + filteredSqlStatements.forEach((ss) => { + if (!uniqueFilteredSqlStatements.includes(ss)) { + uniqueFilteredSqlStatements.push(ss); + } + }); + if (verbose) { console.log(); // console.log(chalk.gray('Verbose logs:')); @@ -291,8 +440,8 @@ export const sqlitePush = async ( } = await sqliteSuggestions( db, statements.statements, - statements.squashedCur, statements.squashedPrev, + statements.squashedCur, statements.meta!, ); @@ -372,10 +521,114 @@ export const sqlitePush = async ( await db.query('rollback'); process.exit(1); } - } else if (credentials.driver === 'turso') { - await db.batch!(statementsToExecute.map((it) => ({ query: it }))); } render(`[${chalk.green('✓')}] Changes applied`); } } }; + +export const libSQLPush = async ( + schemaPath: string | string[], + verbose: boolean, + strict: boolean, + credentials: LibSQLCredentials, + tablesFilter: string[], + force: boolean, +) => { + const { connectToLibSQL } = await import('../connections'); + const { sqlitePushIntrospect } = await import('./sqliteIntrospect'); + + const db = await connectToLibSQL(credentials); + const { schema } = await sqlitePushIntrospect(db, tablesFilter); + + const { prepareLibSQLPush } = await import('./migrate'); + + const statements = await prepareLibSQLPush(schemaPath, schema); + + if (statements.sqlStatements.length === 0) { + render(`\n[${chalk.blue('i')}] No changes detected`); + } else { + const { + shouldAskForApprove, + statementsToExecute, + columnsToRemove, + tablesToRemove, + tablesToTruncate, + infoToPrint, + } = await libSqlLogSuggestionsAndReturn( + db, + statements.statements, + statements.squashedPrev, + statements.squashedCur, + statements.meta!, + ); + + if (verbose && statementsToExecute.length > 0) { + console.log(); + console.log( + withStyle.warning('You are about to execute current statements:'), + ); + console.log(); + console.log(statementsToExecute.map((s) => chalk.blue(s)).join('\n')); + console.log(); + } + + if (!force && strict) { + if (!shouldAskForApprove) { + const { status, data } = await render( + new Select(['No, abort', `Yes, I want to execute all statements`]), + ); + if (data?.index === 0) { + render(`[${chalk.red('x')}] All changes were aborted`); + process.exit(0); + } + } + } + + if (!force && shouldAskForApprove) { + console.log(withStyle.warning('Found data-loss statements:')); + console.log(infoToPrint.join('\n')); + console.log(); + console.log( + chalk.red.bold( + 'THIS ACTION WILL CAUSE DATA LOSS AND CANNOT BE REVERTED\n', + ), + ); + + console.log(chalk.white('Do you still want to push changes?')); + + const { status, data } = await render( + new Select([ + 'No, abort', + `Yes, I want to${ + tablesToRemove.length > 0 + ? ` remove ${tablesToRemove.length} ${tablesToRemove.length > 1 ? 'tables' : 'table'},` + : ' ' + }${ + columnsToRemove.length > 0 + ? ` remove ${columnsToRemove.length} ${columnsToRemove.length > 1 ? 'columns' : 'column'},` + : ' ' + }${ + tablesToTruncate.length > 0 + ? ` truncate ${tablesToTruncate.length} ${tablesToTruncate.length > 1 ? 'tables' : 'table'}` + : '' + }` + .trimEnd() + .replace(/(^,)|(,$)/g, '') + .replace(/ +(?= )/g, ''), + ]), + ); + if (data?.index === 0) { + render(`[${chalk.red('x')}] All changes were aborted`); + process.exit(0); + } + } + + if (statementsToExecute.length === 0) { + render(`\n[${chalk.blue('i')}] No changes detected`); + } else { + await db.batchWithPragma!(statementsToExecute); + render(`[${chalk.green('✓')}] Changes applied`); + } + } +}; diff --git a/drizzle-kit/src/cli/commands/singlestoreIntrospect.ts b/drizzle-kit/src/cli/commands/singlestoreIntrospect.ts new file mode 100644 index 000000000..27d8c59c5 --- /dev/null +++ b/drizzle-kit/src/cli/commands/singlestoreIntrospect.ts @@ -0,0 +1,53 @@ +import { renderWithTask } from 'hanji'; +import { Minimatch } from 'minimatch'; +import { originUUID } from '../../global'; +import type { SingleStoreSchema } from '../../serializer/singlestoreSchema'; +import { fromDatabase } from '../../serializer/singlestoreSerializer'; +import type { DB } from '../../utils'; +import { ProgressView } from '../views'; + +export const singlestorePushIntrospect = async ( + db: DB, + databaseName: string, + filters: string[], +) => { + const matchers = filters.map((it) => { + return new Minimatch(it); + }); + + const filter = (tableName: string) => { + if (matchers.length === 0) return true; + + let flags: boolean[] = []; + + for (let matcher of matchers) { + if (matcher.negate) { + if (!matcher.match(tableName)) { + flags.push(false); + } + } + + if (matcher.match(tableName)) { + flags.push(true); + } + } + + if (flags.length > 0) { + return flags.every(Boolean); + } + return false; + }; + + const progress = new ProgressView( + 'Pulling schema from database...', + 'Pulling schema from database...', + ); + const res = await renderWithTask( + progress, + fromDatabase(db, databaseName, filter), + ); + + const schema = { id: originUUID, prevId: '', ...res } as SingleStoreSchema; + const { internal, ...schemaWithoutInternals } = schema; + return { schema: schemaWithoutInternals }; +}; diff --git a/drizzle-kit/src/cli/commands/singlestorePushUtils.ts b/drizzle-kit/src/cli/commands/singlestorePushUtils.ts new file mode 100644 index 000000000..80fad9b2d --- /dev/null +++ b/drizzle-kit/src/cli/commands/singlestorePushUtils.ts @@ -0,0 +1,352 @@ +import chalk from 'chalk'; +import { render } from 'hanji'; +import { TypeOf } from 'zod'; +import { JsonAlterColumnTypeStatement, JsonStatement } from '../../jsonStatements'; +import { singlestoreSchema, SingleStoreSquasher } from '../../serializer/singlestoreSchema'; +import type { DB } from '../../utils'; +import { Select } from '../selector-ui'; +import { withStyle } from '../validations/outputs'; + +export const filterStatements = ( + statements: JsonStatement[], + currentSchema: TypeOf, + prevSchema: TypeOf, +) => { + return statements.filter((statement) => { + if (statement.type === 'alter_table_alter_column_set_type') { + // Don't need to handle it on migrations step and introspection + // but for both it should be skipped + if ( + statement.oldDataType.startsWith('tinyint') + && statement.newDataType.startsWith('boolean') + ) { + return false; + } + + if ( + statement.oldDataType.startsWith('bigint unsigned') + && statement.newDataType.startsWith('serial') + ) { + return false; + } + + if ( + statement.oldDataType.startsWith('serial') + && statement.newDataType.startsWith('bigint unsigned') + ) { + return false; + } + } else if (statement.type === 'alter_table_alter_column_set_default') { + if ( + statement.newDefaultValue === false + && statement.oldDefaultValue === 0 + && statement.newDataType === 'boolean' + ) { + return false; + } + if ( + statement.newDefaultValue === true + && statement.oldDefaultValue === 1 + && statement.newDataType === 'boolean' + ) { + return false; + } + } else if (statement.type === 'delete_unique_constraint') { + const unsquashed = SingleStoreSquasher.unsquashUnique(statement.data); + // only if constraint was removed from a serial column, than treat it as removed + // const serialStatement = statements.find( + // (it) => it.type === "alter_table_alter_column_set_type" + // ) as JsonAlterColumnTypeStatement; + // if ( + // serialStatement?.oldDataType.startsWith("bigint unsigned") && + // serialStatement?.newDataType.startsWith("serial") && + // serialStatement.columnName === + // SingleStoreSquasher.unsquashUnique(statement.data).columns[0] + // ) { + // return false; + // } + // Check if uniqueindex was only on this column, that is serial + + // if now serial and was not serial and was unique index + if ( + unsquashed.columns.length === 1 + && currentSchema.tables[statement.tableName].columns[unsquashed.columns[0]] + .type === 'serial' + && prevSchema.tables[statement.tableName].columns[unsquashed.columns[0]] + .type === 'serial' + && currentSchema.tables[statement.tableName].columns[unsquashed.columns[0]] + .name === unsquashed.columns[0] + ) { + return false; + } + } else if (statement.type === 'alter_table_alter_column_drop_notnull') { + // only if constraint was removed from a serial column, than treat it as removed + const serialStatement = statements.find( + (it) => it.type === 'alter_table_alter_column_set_type', + ) as JsonAlterColumnTypeStatement; + if ( + serialStatement?.oldDataType.startsWith('bigint unsigned') + && serialStatement?.newDataType.startsWith('serial') + && serialStatement.columnName === statement.columnName + && serialStatement.tableName === statement.tableName + ) { + return false; + } + if (statement.newDataType === 'serial' && !statement.columnNotNull) { + return false; + } + if (statement.columnAutoIncrement) { + return false; + } + } + + return true; + }); +}; + +export const logSuggestionsAndReturn = async ( + db: DB, + statements: JsonStatement[], + json2: TypeOf, +) => { + let shouldAskForApprove = false; + const statementsToExecute: string[] = []; + const infoToPrint: string[] = []; + + const tablesToRemove: string[] = []; + const columnsToRemove: string[] = []; + const schemasToRemove: string[] = []; + const tablesToTruncate: string[] = []; + + for (const statement of statements) { + if (statement.type === 'drop_table') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.tableName, + ) + } table with ${count} items`, + ); + tablesToRemove.push(statement.tableName); + shouldAskForApprove = true; + } + } else if (statement.type === 'alter_table_drop_column') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.columnName, + ) + } column in ${statement.tableName} table with ${count} items`, + ); + columnsToRemove.push(`${statement.tableName}_${statement.columnName}`); + shouldAskForApprove = true; + } + } else if (statement.type === 'drop_schema') { + const res = await db.query( + `select count(*) as count from information_schema.tables where table_schema = \`${statement.name}\`;`, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to delete ${ + chalk.underline( + statement.name, + ) + } schema with ${count} tables`, + ); + schemasToRemove.push(statement.name); + shouldAskForApprove = true; + } + } else if (statement.type === 'alter_table_alter_column_set_type') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to change ${ + chalk.underline( + statement.columnName, + ) + } column type from ${ + chalk.underline( + statement.oldDataType, + ) + } to ${chalk.underline(statement.newDataType)} with ${count} items`, + ); + statementsToExecute.push(`truncate table ${statement.tableName};`); + tablesToTruncate.push(statement.tableName); + shouldAskForApprove = true; + } + } else if (statement.type === 'alter_table_alter_column_drop_default') { + if (statement.columnNotNull) { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to remove default value from ${ + chalk.underline( + statement.columnName, + ) + } not-null column with ${count} items`, + ); + + tablesToTruncate.push(statement.tableName); + statementsToExecute.push(`truncate table ${statement.tableName};`); + + shouldAskForApprove = true; + } + } + // shouldAskForApprove = true; + } else if (statement.type === 'alter_table_alter_column_set_notnull') { + if (typeof statement.columnDefault === 'undefined') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to set not-null constraint to ${ + chalk.underline( + statement.columnName, + ) + } column without default, which contains ${count} items`, + ); + + tablesToTruncate.push(statement.tableName); + statementsToExecute.push(`truncate table ${statement.tableName};`); + + shouldAskForApprove = true; + } + } + } else if (statement.type === 'alter_table_alter_column_drop_pk') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + + // if drop pk and json2 has autoincrement in table -> exit process with error + if ( + Object.values(json2.tables[statement.tableName].columns).filter( + (column) => column.autoincrement, + ).length > 0 + ) { + console.log( + `${ + withStyle.errorWarning( + `You have removed the primary key from a ${statement.tableName} table without removing the auto-increment property from this table. As the database error states: 'there can be only one auto column, and it must be defined as a key. Make sure to remove autoincrement from ${statement.tableName} table`, + ) + }`, + ); + process.exit(1); + } + + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to change ${ + chalk.underline( + statement.tableName, + ) + } primary key. This statements may fail and you table may left without primary key`, + ); + + tablesToTruncate.push(statement.tableName); + shouldAskForApprove = true; + } + } else if (statement.type === 'delete_composite_pk') { + // if drop pk and json2 has autoincrement in table -> exit process with error + if ( + Object.values(json2.tables[statement.tableName].columns).filter( + (column) => column.autoincrement, + ).length > 0 + ) { + console.log( + `${ + withStyle.errorWarning( + `You have removed the primary key from a ${statement.tableName} table without removing the auto-increment property from this table. As the database error states: 'there can be only one auto column, and it must be defined as a key. Make sure to remove autoincrement from ${statement.tableName} table`, + ) + }`, + ); + process.exit(1); + } + } else if (statement.type === 'alter_table_add_column') { + if ( + statement.column.notNull + && typeof statement.column.default === 'undefined' + ) { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + statement.column.name, + ) + } column without default value, which contains ${count} items`, + ); + + tablesToTruncate.push(statement.tableName); + statementsToExecute.push(`truncate table ${statement.tableName};`); + + shouldAskForApprove = true; + } + } + } else if (statement.type === 'create_unique_constraint') { + const res = await db.query( + `select count(*) as count from \`${statement.tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + const unsquashedUnique = SingleStoreSquasher.unsquashUnique(statement.data); + console.log( + `· You're about to add ${ + chalk.underline( + unsquashedUnique.name, + ) + } unique constraint to the table, which contains ${count} items. If this statement fails, you will receive an error from the database. Do you want to truncate ${ + chalk.underline( + statement.tableName, + ) + } table?\n`, + ); + const { status, data } = await render( + new Select([ + 'No, add the constraint without truncating the table', + `Yes, truncate the table`, + ]), + ); + if (data?.index === 1) { + tablesToTruncate.push(statement.tableName); + statementsToExecute.push(`truncate table ${statement.tableName};`); + shouldAskForApprove = true; + } + } + } + } + + return { + statementsToExecute, + shouldAskForApprove, + infoToPrint, + columnsToRemove: [...new Set(columnsToRemove)], + schemasToRemove: [...new Set(schemasToRemove)], + tablesToTruncate: [...new Set(tablesToTruncate)], + tablesToRemove: [...new Set(tablesToRemove)], + }; +}; diff --git a/drizzle-kit/src/cli/commands/singlestoreUp.ts b/drizzle-kit/src/cli/commands/singlestoreUp.ts new file mode 100644 index 000000000..dc5004ed0 --- /dev/null +++ b/drizzle-kit/src/cli/commands/singlestoreUp.ts @@ -0,0 +1 @@ +export const upSinglestoreHandler = (out: string) => {}; diff --git a/drizzle-kit/src/cli/commands/sqlitePushUtils.ts b/drizzle-kit/src/cli/commands/sqlitePushUtils.ts index 451f035a7..bcc2d19db 100644 --- a/drizzle-kit/src/cli/commands/sqlitePushUtils.ts +++ b/drizzle-kit/src/cli/commands/sqlitePushUtils.ts @@ -10,7 +10,7 @@ import { } from '../../sqlgenerator'; import type { JsonStatement } from '../../jsonStatements'; -import type { DB, SQLiteDB } from '../../utils'; +import { findAddedAndRemoved, type SQLiteDB } from '../../utils'; export const _moveDataStatements = ( tableName: string, @@ -19,16 +19,7 @@ export const _moveDataStatements = ( ) => { const statements: string[] = []; - // rename table to __old_${tablename} - statements.push( - new SqliteRenameTableConvertor().convert({ - type: 'rename_table', - tableNameFrom: tableName, - tableNameTo: `__old_push_${tableName}`, - fromSchema: '', - toSchema: '', - }), - ); + const newTableName = `__new_${tableName}`; // create table statement from a new json2 with proper name const tableColumns = Object.values(json.tables[tableName].columns); @@ -39,10 +30,11 @@ export const _moveDataStatements = ( const fks = referenceData.map((it) => SQLiteSquasher.unsquashPushFK(it)); + // create new table statements.push( new SQLiteCreateTableConvertor().convert({ type: 'sqlite_create_table', - tableName: tableName, + tableName: newTableName, columns: tableColumns, referenceData: fks, compositePKs, @@ -51,19 +43,38 @@ export const _moveDataStatements = ( // move data if (!dataLoss) { + const columns = Object.keys(json.tables[tableName].columns).map( + (c) => `"${c}"`, + ); + statements.push( - `INSERT INTO "${tableName}" SELECT * FROM "__old_push_${tableName}";`, + `INSERT INTO \`${newTableName}\`(${ + columns.join( + ', ', + ) + }) SELECT ${columns.join(', ')} FROM \`${tableName}\`;`, ); } - // drop table with name __old_${tablename} + statements.push( new SQLiteDropTableConvertor().convert({ type: 'drop_table', - tableName: `__old_push_${tableName}`, + tableName: tableName, schema: '', }), ); + // rename table + statements.push( + new SqliteRenameTableConvertor().convert({ + fromSchema: '', + tableNameFrom: newTableName, + tableNameTo: tableName, + toSchema: '', + type: 'rename_table', + }), + ); + for (const idx of Object.values(json.tables[tableName].indexes)) { statements.push( new CreateSqliteIndexConvertor().convert({ @@ -120,8 +131,6 @@ export const logSuggestionsAndReturn = async ( const schemasToRemove: string[] = []; const tablesToTruncate: string[] = []; - const tablesContext: Record = {}; - for (const statement of statements) { if (statement.type === 'drop_table') { const res = await connection.query<{ count: string }>( @@ -139,248 +148,159 @@ export const logSuggestionsAndReturn = async ( tablesToRemove.push(statement.tableName); shouldAskForApprove = true; } - const stmnt = fromJson([statement], 'sqlite')[0]; - statementsToExecute.push(stmnt); - } else if (statement.type === 'alter_table_drop_column') { - const newTableName = getOldTableName(statement.tableName, meta); - const columnIsPartOfPk = Object.values( - json1.tables[newTableName].compositePrimaryKeys, - ).find((c) => SQLiteSquasher.unsquashPK(c).includes(statement.columnName)); - - const columnIsPartOfIndex = Object.values( - json1.tables[newTableName].indexes, - ).find((c) => SQLiteSquasher.unsquashIdx(c).columns.includes(statement.columnName)); - - const columnIsPk = json1.tables[newTableName].columns[statement.columnName].primaryKey; - - const columnIsPartOfFk = Object.values( - json1.tables[newTableName].foreignKeys, - ).find((t) => - SQLiteSquasher.unsquashPushFK(t).columnsFrom.includes( - statement.columnName, - ) + const fromJsonStatement = fromJson([statement], 'sqlite', 'push'); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), ); + } else if (statement.type === 'alter_table_drop_column') { + const tableName = statement.tableName; + const columnName = statement.columnName; const res = await connection.query<{ count: string }>( - `select count(*) as count from \`${newTableName}\``, + `select count(\`${tableName}\`.\`${columnName}\`) as count from \`${tableName}\``, ); const count = Number(res[0].count); if (count > 0) { infoToPrint.push( `· You're about to delete ${ chalk.underline( - statement.columnName, + columnName, ) - } column in ${newTableName} table with ${count} items`, + } column in ${tableName} table with ${count} items`, ); - columnsToRemove.push(`${newTableName}_${statement.columnName}`); + columnsToRemove.push(`${tableName}_${statement.columnName}`); shouldAskForApprove = true; } - if ( - columnIsPk - || columnIsPartOfPk - || columnIsPartOfIndex - || columnIsPartOfFk - ) { - tablesContext[newTableName] = [ - ..._moveDataStatements(statement.tableName, json2, true), - ]; - // check table that have fk to this table - - const tablesReferncingCurrent: string[] = []; - - for (const table of Object.values(json1.tables)) { - const tablesRefs = Object.values(json1.tables[table.name].foreignKeys) - .filter( - (t) => SQLiteSquasher.unsquashPushFK(t).tableTo === newTableName, + const fromJsonStatement = fromJson([statement], 'sqlite', 'push'); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if ( + statement.type === 'sqlite_alter_table_add_column' + && (statement.column.notNull && !statement.column.default) + ) { + const tableName = statement.tableName; + const columnName = statement.column.name; + const res = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, + ); + const count = Number(res[0].count); + if (count > 0) { + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + columnName, ) - .map((t) => SQLiteSquasher.unsquashPushFK(t).tableFrom); - - tablesReferncingCurrent.push(...tablesRefs); - } - - const uniqueTableRefs = [...new Set(tablesReferncingCurrent)]; - - for (const table of uniqueTableRefs) { - if (typeof tablesContext[table] === 'undefined') { - tablesContext[table] = [..._moveDataStatements(table, json2)]; - } - } - } else { - if (typeof tablesContext[newTableName] === 'undefined') { - const stmnt = fromJson([statement], 'sqlite')[0]; - statementsToExecute.push(stmnt); - } - } - } else if (statement.type === 'sqlite_alter_table_add_column') { - const newTableName = getOldTableName(statement.tableName, meta); - if (statement.column.notNull && !statement.column.default) { - const res = await connection.query<{ count: string }>( - `select count(*) as count from \`${newTableName}\``, + } column without default value, which contains ${count} items`, ); - const count = Number(res[0].count); - if (count > 0) { - infoToPrint.push( - `· You're about to add not-null ${ - chalk.underline( - statement.column.name, - ) - } column without default value, which contains ${count} items`, - ); - tablesToTruncate.push(newTableName); - statementsToExecute.push(`delete from ${newTableName};`); + tablesToTruncate.push(tableName); + statementsToExecute.push(`delete from ${tableName};`); - shouldAskForApprove = true; - } + shouldAskForApprove = true; } - if (statement.column.primaryKey) { - tablesContext[newTableName] = [ - ..._moveDataStatements(statement.tableName, json2, true), - ]; - const tablesReferncingCurrent: string[] = []; - - for (const table of Object.values(json1.tables)) { - const tablesRefs = Object.values(json1.tables[table.name].foreignKeys) - .filter( - (t) => SQLiteSquasher.unsquashPushFK(t).tableTo === newTableName, - ) - .map((t) => SQLiteSquasher.unsquashPushFK(t).tableFrom); - tablesReferncingCurrent.push(...tablesRefs); - } + const fromJsonStatement = fromJson([statement], 'sqlite', 'push'); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); + } else if (statement.type === 'recreate_table') { + const tableName = statement.tableName; + const oldTableName = getOldTableName(tableName, meta); - const uniqueTableRefs = [...new Set(tablesReferncingCurrent)]; + let dataLoss = false; - for (const table of uniqueTableRefs) { - if (typeof tablesContext[table] === 'undefined') { - tablesContext[table] = [..._moveDataStatements(table, json2)]; - } - } - } else { - if (typeof tablesContext[newTableName] === 'undefined') { - const stmnt = fromJson([statement], 'sqlite')[0]; - statementsToExecute.push(stmnt); - } - } - } else if ( - statement.type === 'alter_table_alter_column_set_type' - || statement.type === 'alter_table_alter_column_set_default' - || statement.type === 'alter_table_alter_column_drop_default' - || statement.type === 'alter_table_alter_column_set_notnull' - || statement.type === 'alter_table_alter_column_drop_notnull' - || statement.type === 'alter_table_alter_column_drop_autoincrement' - || statement.type === 'alter_table_alter_column_set_autoincrement' - || statement.type === 'alter_table_alter_column_drop_pk' - || statement.type === 'alter_table_alter_column_set_pk' - ) { - if ( - !( - statement.type === 'alter_table_alter_column_set_notnull' - && statement.columnPk - ) - ) { - const newTableName = getOldTableName(statement.tableName, meta); - if ( - statement.type === 'alter_table_alter_column_set_notnull' - && typeof statement.columnDefault === 'undefined' - ) { + const prevColumnNames = Object.keys(json1.tables[oldTableName].columns); + const currentColumnNames = Object.keys(json2.tables[tableName].columns); + const { removedColumns, addedColumns } = findAddedAndRemoved( + prevColumnNames, + currentColumnNames, + ); + + if (removedColumns.length) { + for (const removedColumn of removedColumns) { const res = await connection.query<{ count: string }>( - `select count(*) as count from \`${newTableName}\``, + `select count(\`${tableName}\`.\`${removedColumn}\`) as count from \`${tableName}\``, ); + const count = Number(res[0].count); if (count > 0) { infoToPrint.push( - `· You're about to add not-null constraint to ${ + `· You're about to delete ${ chalk.underline( - statement.columnName, + removedColumn, ) - } column without default value, which contains ${count} items`, + } column in ${tableName} table with ${count} items`, ); - - tablesToTruncate.push(newTableName); + columnsToRemove.push(removedColumn); shouldAskForApprove = true; } - tablesContext[newTableName] = _moveDataStatements( - statement.tableName, - json1, - true, + } + } + + if (addedColumns.length) { + for (const addedColumn of addedColumns) { + const [res] = await connection.query<{ count: string }>( + `select count(*) as count from \`${tableName}\``, ); - } else { - if (typeof tablesContext[newTableName] === 'undefined') { - tablesContext[newTableName] = _moveDataStatements( - statement.tableName, - json1, + + const columnConf = json2.tables[tableName].columns[addedColumn]; + + const count = Number(res.count); + if (count > 0 && columnConf.notNull && !columnConf.default) { + dataLoss = true; + infoToPrint.push( + `· You're about to add not-null ${ + chalk.underline( + addedColumn, + ) + } column without default value to table, which contains ${count} items`, ); + shouldAskForApprove = true; + tablesToTruncate.push(tableName); + + statementsToExecute.push(`DELETE FROM \`${tableName}\`;`); } } + } - const tablesReferncingCurrent: string[] = []; + // check if some tables referencing current for pragma + const tablesReferencingCurrent: string[] = []; - for (const table of Object.values(json1.tables)) { - const tablesRefs = Object.values(json1.tables[table.name].foreignKeys) - .filter( - (t) => SQLiteSquasher.unsquashPushFK(t).tableTo === newTableName, - ) - .map((t) => { - return getNewTableName( - SQLiteSquasher.unsquashPushFK(t).tableFrom, - meta, - ); - }); - - tablesReferncingCurrent.push(...tablesRefs); - } + for (const table of Object.values(json2.tables)) { + const tablesRefs = Object.values(json2.tables[table.name].foreignKeys) + .filter((t) => SQLiteSquasher.unsquashPushFK(t).tableTo === tableName) + .map((it) => SQLiteSquasher.unsquashPushFK(it).tableFrom); - const uniqueTableRefs = [...new Set(tablesReferncingCurrent)]; + tablesReferencingCurrent.push(...tablesRefs); + } - for (const table of uniqueTableRefs) { - if (typeof tablesContext[table] === 'undefined') { - tablesContext[table] = [..._moveDataStatements(table, json1)]; - } - } + if (!tablesReferencingCurrent.length) { + statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss)); + continue; } - } else if ( - statement.type === 'create_reference' - || statement.type === 'delete_reference' - || statement.type === 'alter_reference' - ) { - const fk = SQLiteSquasher.unsquashPushFK(statement.data); - if (typeof tablesContext[statement.tableName] === 'undefined') { - tablesContext[statement.tableName] = _moveDataStatements( - statement.tableName, - json2, - ); + const [{ foreign_keys: pragmaState }] = await connection.query<{ + foreign_keys: number; + }>(`PRAGMA foreign_keys;`); + + if (pragmaState) { + statementsToExecute.push(`PRAGMA foreign_keys=OFF;`); } - } else if ( - statement.type === 'create_composite_pk' - || statement.type === 'alter_composite_pk' - || statement.type === 'delete_composite_pk' - || statement.type === 'create_unique_constraint' - || statement.type === 'delete_unique_constraint' - ) { - const newTableName = getOldTableName(statement.tableName, meta); - if (typeof tablesContext[newTableName] === 'undefined') { - tablesContext[newTableName] = _moveDataStatements( - statement.tableName, - json2, - ); + statementsToExecute.push(..._moveDataStatements(tableName, json2, dataLoss)); + if (pragmaState) { + statementsToExecute.push(`PRAGMA foreign_keys=ON;`); } } else { - const stmnt = fromJson([statement], 'sqlite'); - if (typeof stmnt !== 'undefined') { - statementsToExecute.push(...stmnt); - } + const fromJsonStatement = fromJson([statement], 'sqlite', 'push'); + statementsToExecute.push( + ...(Array.isArray(fromJsonStatement) ? fromJsonStatement : [fromJsonStatement]), + ); } } - for (const context of Object.values(tablesContext)) { - statementsToExecute.push(...context); - } - return { statementsToExecute, shouldAskForApprove, diff --git a/drizzle-kit/src/cli/commands/utils.ts b/drizzle-kit/src/cli/commands/utils.ts index fbfeede70..25e80ed4d 100644 --- a/drizzle-kit/src/cli/commands/utils.ts +++ b/drizzle-kit/src/cli/commands/utils.ts @@ -16,6 +16,11 @@ import { Prefix, wrapParam, } from '../validations/common'; +import { + LibSQLCredentials, + libSQLCredentials, + printConfigConnectionIssues as printIssuesLibSql, +} from '../validations/libsql'; import { MysqlCredentials, mysqlCredentials, @@ -27,13 +32,18 @@ import { postgresCredentials, printConfigConnectionIssues as printIssuesPg, } from '../validations/postgres'; +import { + printConfigConnectionIssues as printIssuesSingleStore, + SingleStoreCredentials, + singlestoreCredentials, +} from '../validations/singlestore'; import { printConfigConnectionIssues as printIssuesSqlite, SqliteCredentials, sqliteCredentials, } from '../validations/sqlite'; import { studioCliParams, studioConfig } from '../validations/studio'; -import { error, grey } from '../views'; +import { error } from '../views'; // NextJs default config is target: es5, which esbuild-register can't consume const assertES5 = async (unregister: () => void) => { @@ -211,6 +221,18 @@ export const preparePushConfig = async ( dialect: 'sqlite'; credentials: SqliteCredentials; } + | { + dialect: 'turso'; + credentials: LibSQLCredentials; + } + | { + dialect: 'singlestore'; + credentials: SingleStoreCredentials; + } + | { + dialect: 'singlestore'; + credentials: SingleStoreCredentials; + } ) & { schemaPath: string | string[]; verbose: boolean; @@ -309,6 +331,25 @@ export const preparePushConfig = async ( }; } + if (config.dialect === 'singlestore') { + const parsed = singlestoreCredentials.safeParse(config); + if (!parsed.success) { + printIssuesSingleStore(config); + process.exit(1); + } + + return { + dialect: 'singlestore', + schemaPath: config.schema, + strict: config.strict ?? false, + verbose: config.verbose ?? false, + force: (options.force as boolean) ?? false, + credentials: parsed.data, + tablesFilter, + schemasFilter, + }; + } + if (config.dialect === 'sqlite') { const parsed = sqliteCredentials.safeParse(config); if (!parsed.success) { @@ -327,6 +368,24 @@ export const preparePushConfig = async ( }; } + if (config.dialect === 'turso') { + const parsed = libSQLCredentials.safeParse(config); + if (!parsed.success) { + printIssuesSqlite(config, 'pull'); + process.exit(1); + } + return { + dialect: 'turso', + schemaPath: config.schema, + strict: config.strict ?? false, + verbose: config.verbose ?? false, + force: (options.force as boolean) ?? false, + credentials: parsed.data, + tablesFilter, + schemasFilter, + }; + } + assertUnreachable(config.dialect); }; @@ -347,6 +406,18 @@ export const preparePullConfig = async ( dialect: 'sqlite'; credentials: SqliteCredentials; } + | { + dialect: 'singlestore'; + credentials: SingleStoreCredentials; + } + | { + dialect: 'turso'; + credentials: LibSQLCredentials; + } + | { + dialect: 'singlestore'; + credentials: SingleStoreCredentials; + } ) & { out: string; breakpoints: boolean; @@ -434,6 +505,25 @@ export const preparePullConfig = async ( }; } + if (dialect === 'singlestore') { + const parsed = singlestoreCredentials.safeParse(config); + if (!parsed.success) { + printIssuesSingleStore(config); + process.exit(1); + } + + return { + dialect: 'singlestore', + out: config.out, + breakpoints: config.breakpoints, + casing: config.casing, + credentials: parsed.data, + tablesFilter, + schemasFilter, + prefix: config.migrations?.prefix || 'index', + }; + } + if (dialect === 'sqlite') { const parsed = sqliteCredentials.safeParse(config); if (!parsed.success) { @@ -452,6 +542,24 @@ export const preparePullConfig = async ( }; } + if (dialect === 'turso') { + const parsed = libSQLCredentials.safeParse(config); + if (!parsed.success) { + printIssuesLibSql(config, 'pull'); + process.exit(1); + } + return { + dialect, + out: config.out, + breakpoints: config.breakpoints, + casing: config.casing, + credentials: parsed.data, + tablesFilter, + schemasFilter, + prefix: config.migrations?.prefix || 'index', + }; + } + assertUnreachable(dialect); }; @@ -505,6 +613,23 @@ export const prepareStudioConfig = async (options: Record) => { credentials, }; } + + if (dialect === 'singlestore') { + const parsed = singlestoreCredentials.safeParse(flattened); + if (!parsed.success) { + printIssuesSingleStore(flattened as Record); + process.exit(1); + } + const credentials = parsed.data; + return { + dialect, + schema, + host, + port, + credentials, + }; + } + if (dialect === 'sqlite') { const parsed = sqliteCredentials.safeParse(flattened); if (!parsed.success) { @@ -521,6 +646,22 @@ export const prepareStudioConfig = async (options: Record) => { }; } + if (dialect === 'turso') { + const parsed = libSQLCredentials.safeParse(flattened); + if (!parsed.success) { + printIssuesLibSql(flattened as Record, 'studio'); + process.exit(1); + } + const credentials = parsed.data; + return { + dialect, + schema, + host, + port, + credentials, + }; + } + assertUnreachable(dialect); }; @@ -574,6 +715,23 @@ export const prepareMigrateConfig = async (configPath: string | undefined) => { table, }; } + + if (dialect === 'singlestore') { + const parsed = singlestoreCredentials.safeParse(flattened); + if (!parsed.success) { + printIssuesSingleStore(flattened as Record); + process.exit(1); + } + const credentials = parsed.data; + return { + dialect, + out, + credentials, + schema, + table, + }; + } + if (dialect === 'sqlite') { const parsed = sqliteCredentials.safeParse(flattened); if (!parsed.success) { @@ -589,6 +747,21 @@ export const prepareMigrateConfig = async (configPath: string | undefined) => { table, }; } + if (dialect === 'turso') { + const parsed = libSQLCredentials.safeParse(flattened); + if (!parsed.success) { + printIssuesLibSql(flattened as Record, 'migrate'); + process.exit(1); + } + const credentials = parsed.data; + return { + dialect, + out, + credentials, + schema, + table, + }; + } assertUnreachable(dialect); }; diff --git a/drizzle-kit/src/cli/connections.ts b/drizzle-kit/src/cli/connections.ts index ba741bfed..fccc7def8 100644 --- a/drizzle-kit/src/cli/connections.ts +++ b/drizzle-kit/src/cli/connections.ts @@ -5,11 +5,21 @@ import fetch from 'node-fetch'; import ws from 'ws'; import { assertUnreachable } from '../global'; import type { ProxyParams } from '../serializer/studio'; -import { type DB, normalisePGliteUrl, normaliseSQLiteUrl, type Proxy, type SQLiteDB, type SqliteProxy } from '../utils'; +import { + type DB, + LibSQLDB, + normalisePGliteUrl, + normaliseSQLiteUrl, + type Proxy, + type SQLiteDB, + type SqliteProxy, +} from '../utils'; import { assertPackages, checkPackage } from './utils'; +import { LibSQLCredentials } from './validations/libsql'; import type { MysqlCredentials } from './validations/mysql'; import { withStyle } from './validations/outputs'; import type { PostgresCredentials } from './validations/postgres'; +import { SingleStoreCredentials } from './validations/singlestore'; import type { SqliteCredentials } from './validations/sqlite'; export const preparePostgresDB = async ( @@ -482,56 +492,7 @@ export const connectToSQLite = async ( > => { if ('driver' in credentials) { const { driver } = credentials; - if (driver === 'turso') { - assertPackages('@libsql/client'); - const { createClient } = await import('@libsql/client'); - const { drizzle } = await import('drizzle-orm/libsql'); - const { migrate } = await import('drizzle-orm/libsql/migrator'); - - const client = createClient({ - url: credentials.url, - authToken: credentials.authToken, - }); - - const drzl = drizzle(client); - const migrateFn = async (config: MigrationConfig) => { - return migrate(drzl, config); - }; - - const db: SQLiteDB = { - query: async (sql: string, params?: any[]) => { - const res = await client.execute({ sql, args: params || [] }); - return res.rows as T[]; - }, - run: async (query: string) => { - await client.execute(query); - }, - batch: async ( - queries: { query: string; values?: any[] | undefined }[], - ) => { - await client.batch( - queries.map((it) => ({ sql: it.query, args: it.values ?? [] })), - ); - }, - }; - const proxy: SqliteProxy = { - proxy: async (params: ProxyParams) => { - const preparedParams = prepareSqliteParams(params.params); - const result = await client.execute({ - sql: params.sql, - args: preparedParams, - }); - - if (params.mode === 'array') { - return result.rows.map((row) => Object.values(row)); - } else { - return result.rows; - } - }, - }; - - return { ...db, ...proxy, migrate: migrateFn }; - } else if (driver === 'd1-http') { + if (driver === 'd1-http') { const { drizzle } = await import('drizzle-orm/sqlite-proxy'); const { migrate } = await import('drizzle-orm/sqlite-proxy/migrator'); @@ -708,8 +669,145 @@ export const connectToSQLite = async ( }; return { ...db, ...proxy, migrate: migrateFn }; } + console.log( "Please install either 'better-sqlite3' or '@libsql/client' for Drizzle Kit to connect to SQLite databases", ); process.exit(1); }; + +export const connectToLibSQL = async (credentials: LibSQLCredentials): Promise< + & LibSQLDB + & SqliteProxy + & { migrate: (config: MigrationConfig) => Promise } +> => { + if (await checkPackage('@libsql/client')) { + const { createClient } = await import('@libsql/client'); + const { drizzle } = await import('drizzle-orm/libsql'); + const { migrate } = await import('drizzle-orm/libsql/migrator'); + + const client = createClient({ + url: normaliseSQLiteUrl(credentials.url, 'libsql'), + authToken: credentials.authToken, + }); + const drzl = drizzle(client); + const migrateFn = async (config: MigrationConfig) => { + return migrate(drzl, config); + }; + + const db: LibSQLDB = { + query: async (sql: string, params?: any[]) => { + const res = await client.execute({ sql, args: params || [] }); + return res.rows as T[]; + }, + run: async (query: string) => { + await client.execute(query); + }, + batchWithPragma: async (queries: string[]) => { + await client.migrate(queries); + }, + }; + + const proxy: SqliteProxy = { + proxy: async (params: ProxyParams) => { + const preparedParams = prepareSqliteParams(params.params); + const result = await client.execute({ + sql: params.sql, + args: preparedParams, + }); + + if (params.mode === 'array') { + return result.rows.map((row) => Object.values(row)); + } else { + return result.rows; + } + }, + }; + + return { ...db, ...proxy, migrate: migrateFn }; + } + + console.log( + "Please install '@libsql/client' for Drizzle Kit to connect to LibSQL databases", + ); + process.exit(1); +}; + +const parseSingleStoreCredentials = (credentials: SingleStoreCredentials) => { + if ('url' in credentials) { + const url = credentials.url; + + const connectionUrl = new URL(url); + const pathname = connectionUrl.pathname; + + const database = pathname.split('/')[pathname.split('/').length - 1]; + if (!database) { + console.error( + 'You should specify a database name in connection string (singlestore://USER:PASSWORD@HOST:PORT/DATABASE)', + ); + process.exit(1); + } + return { database, url }; + } else { + return { + database: credentials.database, + credentials, + }; + } +}; + +export const connectToSingleStore = async ( + it: SingleStoreCredentials, +): Promise<{ + db: DB; + proxy: Proxy; + database: string; + migrate: (config: MigrationConfig) => Promise; +}> => { + const result = parseSingleStoreCredentials(it); + + if (await checkPackage('singlestore')) { + const { createConnection } = await import('mysql2/promise'); + const { drizzle } = await import('drizzle-orm/singlestore'); + const { migrate } = await import('drizzle-orm/singlestore/migrator'); + + const connection = result.url + ? await createConnection(result.url) + : await createConnection(result.credentials!); // needed for some reason! + + const db = drizzle(connection); + const migrateFn = async (config: MigrationConfig) => { + return migrate(db, config); + }; + + await connection.connect(); + const query: DB['query'] = async ( + sql: string, + params?: any[], + ): Promise => { + const res = await connection.execute(sql, params); + return res[0] as any; + }; + + const proxy: Proxy = async (params: ProxyParams) => { + const result = await connection.query({ + sql: params.sql, + values: params.params, + rowsAsArray: params.mode === 'array', + }); + return result[0] as any[]; + }; + + return { + db: { query }, + proxy, + database: result.database, + migrate: migrateFn, + }; + } + + console.error( + "To connect to SingleStore database - please install 'mysql2' drivers", + ); + process.exit(1); +}; diff --git a/drizzle-kit/src/cli/schema.ts b/drizzle-kit/src/cli/schema.ts index 4da8af0ac..6b7bcb560 100644 --- a/drizzle-kit/src/cli/schema.ts +++ b/drizzle-kit/src/cli/schema.ts @@ -1,11 +1,20 @@ +import { boolean, command, number, string } from '@drizzle-team/brocli'; import chalk from 'chalk'; -import { checkHandler } from './commands/check'; -import { assertOrmCoreVersion, assertPackages, assertStudioNodeVersion, ormVersionGt } from './utils'; +import 'dotenv/config'; +import { mkdirSync } from 'fs'; +import { renderWithTask } from 'hanji'; +import { dialects } from 'src/schemaValidator'; import '../@types/utils'; +import { assertUnreachable } from '../global'; +import { drizzleForLibSQL, drizzleForSingleStore, prepareSingleStoreSchema, type Setup } from '../serializer/studio'; import { assertV1OutFolder } from '../utils'; +import { certs } from '../utils/certs'; +import { checkHandler } from './commands/check'; import { dropMigration } from './commands/drop'; +import { prepareAndMigrateSingleStore } from './commands/migrate'; import { upMysqlHandler } from './commands/mysqlUp'; import { upPgHandler } from './commands/pgUp'; +import { upSinglestoreHandler } from './commands/singlestoreUp'; import { upSqliteHandler } from './commands/sqliteUp'; import { prepareCheckParams, @@ -16,21 +25,14 @@ import { preparePushConfig, prepareStudioConfig, } from './commands/utils'; +import { assertOrmCoreVersion, assertPackages, assertStudioNodeVersion, ormVersionGt } from './utils'; import { assertCollisions, drivers, prefixes } from './validations/common'; import { withStyle } from './validations/outputs'; -import 'dotenv/config'; -import { boolean, command, number, string } from '@drizzle-team/brocli'; -import { mkdirSync } from 'fs'; -import { renderWithTask } from 'hanji'; -import { dialects } from 'src/schemaValidator'; -import { assertUnreachable } from '../global'; -import type { Setup } from '../serializer/studio'; -import { certs } from '../utils/certs'; import { grey, MigrateProgress } from './views'; const optionDialect = string('dialect') .enum(...dialects) - .desc(`Database dialect: 'postgresql', 'mysql' or 'sqlite'`); + .desc(`Database dialect: 'postgresql', 'mysql', 'sqlite' or 'turso'`); const optionOut = string().desc("Output folder, 'drizzle' by default"); const optionConfig = string().desc('Path to drizzle config file'); const optionBreakpoints = boolean().desc( @@ -77,6 +79,7 @@ export const generate = command({ prepareAndMigratePg, prepareAndMigrateMysql, prepareAndMigrateSqlite, + prepareAndMigrateLibSQL, } = await import('./commands/migrate'); const dialect = opts.dialect; @@ -86,6 +89,10 @@ export const generate = command({ await prepareAndMigrateMysql(opts); } else if (dialect === 'sqlite') { await prepareAndMigrateSqlite(opts); + } else if (dialect === 'turso') { + await prepareAndMigrateLibSQL(opts); + } else if (dialect === 'singlestore') { + await prepareAndMigrateSingleStore(opts); } else { assertUnreachable(dialect); } @@ -159,6 +166,28 @@ export const migrate = command({ migrationsSchema: schema, }), ); + } else if (dialect === 'turso') { + const { connectToLibSQL } = await import('./connections'); + const { migrate } = await connectToLibSQL(credentials); + await renderWithTask( + new MigrateProgress(), + migrate({ + migrationsFolder: opts.out, + migrationsTable: table, + migrationsSchema: schema, + }), + ); + } else if (dialect === 'singlestore') { + const { connectToSingleStore } = await import('./connections'); + const { migrate } = await connectToSingleStore(credentials); + await renderWithTask( + new MigrateProgress(), + migrate({ + migrationsFolder: out, + migrationsTable: table, + migrationsSchema: schema, + }), + ); } else { assertUnreachable(dialect); } @@ -304,6 +333,26 @@ export const push = command({ tablesFilter, force, ); + } else if (dialect === 'turso') { + const { libSQLPush } = await import('./commands/push'); + await libSQLPush( + schemaPath, + verbose, + strict, + credentials, + tablesFilter, + force, + ); + } else if (dialect === 'singlestore') { + const { singlestorePush } = await import('./commands/push'); + await singlestorePush( + schemaPath, + credentials, + tablesFilter, + strict, + verbose, + force, + ); } else { assertUnreachable(dialect); } @@ -359,7 +408,11 @@ export const up = command({ upMysqlHandler(out); } - if (dialect === 'sqlite') { + if (dialect === 'singlestore') { + upSinglestoreHandler(out); + } + + if (dialect === 'sqlite' || dialect === 'turso') { upSqliteHandler(out); } }, @@ -483,6 +536,26 @@ export const pull = command({ tablesFilter, prefix, ); + } else if (dialect === 'turso') { + const { introspectLibSQL } = await import('./commands/introspect'); + await introspectLibSQL( + casing, + out, + breakpoints, + credentials, + tablesFilter, + prefix, + ); + } else if (dialect === 'singlestore') { + const { introspectSingleStore } = await import('./commands/introspect'); + await introspectSingleStore( + casing, + out, + breakpoints, + credentials, + tablesFilter, + prefix, + ); } else { assertUnreachable(dialect); } @@ -583,6 +656,16 @@ export const studio = command({ ? await prepareSQLiteSchema(schemaPath) : { schema: {}, relations: {}, files: [] }; setup = await drizzleForSQLite(credentials, schema, relations, files); + } else if (dialect === 'turso') { + const { schema, relations, files } = schemaPath + ? await prepareSQLiteSchema(schemaPath) + : { schema: {}, relations: {}, files: [] }; + setup = await drizzleForLibSQL(credentials, schema, relations, files); + } else if (dialect === 'singlestore') { + const { schema, relations, files } = schemaPath + ? await prepareSingleStoreSchema(schemaPath) + : { schema: {}, relations: {}, files: [] }; + setup = await drizzleForSingleStore(credentials, schema, relations, files); } else { assertUnreachable(dialect); } diff --git a/drizzle-kit/src/cli/utils.ts b/drizzle-kit/src/cli/utils.ts index f7e7a2ae9..0a5d7862e 100644 --- a/drizzle-kit/src/cli/utils.ts +++ b/drizzle-kit/src/cli/utils.ts @@ -74,7 +74,7 @@ export const assertEitherPackage = async ( process.exit(1); }; -const requiredApiVersion = 7; +const requiredApiVersion = 8; export const assertOrmCoreVersion = async () => { try { const { compatibilityVersion } = await import('drizzle-orm/version'); diff --git a/drizzle-kit/src/cli/validations/common.ts b/drizzle-kit/src/cli/validations/common.ts index a7307f4d6..3a2701e37 100644 --- a/drizzle-kit/src/cli/validations/common.ts +++ b/drizzle-kit/src/cli/validations/common.ts @@ -61,7 +61,6 @@ export const assertCollisions = < }; export const sqliteDriversLiterals = [ - literal('turso'), literal('d1-http'), literal('expo'), ] as const; @@ -156,7 +155,7 @@ export const configPushSchema = object({ }); export type CliConfig = TypeOf; -export const drivers = ['turso', 'd1-http', 'expo', 'aws-data-api', 'pglite'] as const; +export const drivers = ['d1-http', 'expo', 'aws-data-api', 'pglite'] as const; export type Driver = (typeof drivers)[number]; const _: Driver = '' as TypeOf; diff --git a/drizzle-kit/src/cli/validations/libsql.ts b/drizzle-kit/src/cli/validations/libsql.ts new file mode 100644 index 000000000..a9b03c168 --- /dev/null +++ b/drizzle-kit/src/cli/validations/libsql.ts @@ -0,0 +1,27 @@ +import { softAssertUnreachable } from 'src/global'; +import { object, string, TypeOf } from 'zod'; +import { error } from '../views'; +import { wrapParam } from './common'; + +export const libSQLCredentials = object({ + url: string().min(1), + authToken: string().min(1).optional(), +}); + +export type LibSQLCredentials = { + url: string; + authToken?: string; +}; + +const _: LibSQLCredentials = {} as TypeOf; + +export const printConfigConnectionIssues = ( + options: Record, + command: 'generate' | 'migrate' | 'push' | 'pull' | 'studio', +) => { + let text = `Please provide required params for 'turso' dialect:\n`; + console.log(error(text)); + console.log(wrapParam('url', options.url)); + console.log(wrapParam('authToken', options.authToken, true, 'secret')); + process.exit(1); +}; diff --git a/drizzle-kit/src/cli/validations/outputs.ts b/drizzle-kit/src/cli/validations/outputs.ts index 6b92829d5..8bb0f08ef 100644 --- a/drizzle-kit/src/cli/validations/outputs.ts +++ b/drizzle-kit/src/cli/validations/outputs.ts @@ -26,7 +26,7 @@ export const outputs = { ), noDialect: () => withStyle.error( - `Please specify 'dialect' param in config, either of 'pg', 'mysql' or 'sqlite'`, + `Please specify 'dialect' param in config, either of 'pg', 'mysql', 'sqlite' of 'singlestore'. It will help drizzle to know how to query you database. You can read more about drizzle.config: https://orm.drizzle.team/kit-docs/config-reference`, ), }, common: { @@ -79,4 +79,13 @@ export const outputs = { introspect: {}, push: {}, }, + singlestore: { + connection: { + driver: () => withStyle.error(`Only "mysql2" is available options for "--driver"`), + required: () => + withStyle.error( + `Either "url" or "host", "database" are required for database connection`, + ), + }, + }, }; diff --git a/drizzle-kit/src/cli/validations/singlestore.ts b/drizzle-kit/src/cli/validations/singlestore.ts new file mode 100644 index 000000000..ebe0cc5f0 --- /dev/null +++ b/drizzle-kit/src/cli/validations/singlestore.ts @@ -0,0 +1,61 @@ +import { boolean, coerce, object, string, TypeOf, union } from 'zod'; +import { error } from '../views'; +import { wrapParam } from './common'; +import { outputs } from './outputs'; + +export const singlestoreCredentials = union([ + object({ + host: string().min(1), + port: coerce.number().min(1).optional(), + user: string().min(1).optional(), + password: string().min(1).optional(), + database: string().min(1), + ssl: union([ + string(), + object({ + pfx: string().optional(), + key: string().optional(), + passphrase: string().optional(), + cert: string().optional(), + ca: union([string(), string().array()]).optional(), + crl: union([string(), string().array()]).optional(), + ciphers: string().optional(), + rejectUnauthorized: boolean().optional(), + }), + ]).optional(), + }), + object({ + url: string().min(1), + }), +]); + +export type SingleStoreCredentials = TypeOf; + +export const printCliConnectionIssues = (options: any) => { + const { uri, host, database } = options || {}; + + if (!uri && (!host || !database)) { + console.log(outputs.singlestore.connection.required()); + } +}; + +export const printConfigConnectionIssues = ( + options: Record, +) => { + if ('url' in options) { + let text = `Please provide required params for SingleStore driver:\n`; + console.log(error(text)); + console.log(wrapParam('url', options.url, false, 'url')); + process.exit(1); + } + + let text = `Please provide required params for SingleStore driver:\n`; + console.log(error(text)); + console.log(wrapParam('host', options.host)); + console.log(wrapParam('port', options.port, true)); + console.log(wrapParam('user', options.user, true)); + console.log(wrapParam('password', options.password, true, 'secret')); + console.log(wrapParam('database', options.database)); + console.log(wrapParam('ssl', options.ssl, true)); + process.exit(1); +}; diff --git a/drizzle-kit/src/cli/validations/sqlite.ts b/drizzle-kit/src/cli/validations/sqlite.ts index b6ad062d5..54178fd4a 100644 --- a/drizzle-kit/src/cli/validations/sqlite.ts +++ b/drizzle-kit/src/cli/validations/sqlite.ts @@ -25,11 +25,6 @@ export const sqliteCredentials = union([ ]); export type SqliteCredentials = - | { - driver: 'turso'; - url: string; - authToken: string; - } | { driver: 'd1-http'; accountId: string; diff --git a/drizzle-kit/src/index.ts b/drizzle-kit/src/index.ts index 3d29b5c85..dc0c6274c 100644 --- a/drizzle-kit/src/index.ts +++ b/drizzle-kit/src/index.ts @@ -23,7 +23,7 @@ type Verify = U; * **Config** usage: * * `dialect` - mandatory and is responsible for explicitly providing a databse dialect you are using for all the commands - * *Possible values*: `postgresql`, `mysql`, `sqlite` + * *Possible values*: `postgresql`, `mysql`, `sqlite`, `singlestore * * See https://orm.drizzle.team/kit-docs/config-reference#dialect * @@ -64,7 +64,7 @@ type Verify = U; * * `breakpoints` - param lets you enable/disable SQL statement breakpoints in generated migrations. * It’s optional and true by default, it’s necessary to properly apply migrations on databases, - * that do not support multiple DDL alternation statements in one transaction(MySQL, SQLite) and + * that do not support multiple DDL alternation statements in one transaction(MySQL, SQLite, SingleStore) and * Drizzle ORM has to apply them sequentially one by one. * * See https://orm.drizzle.team/kit-docs/config-reference#breakpoints @@ -128,8 +128,7 @@ export type Config = } & ( | { - dialect: Verify; - driver: Verify; + dialect: Verify; dbCredentials: { url: string; authToken?: string; @@ -207,6 +206,21 @@ export type Config = driver: Verify; } | {} + | { + dialect: Verify; + dbCredentials: + | { + host: string; + port?: number; + user?: string; + password?: string; + database: string; + ssl?: string | SslOptions; + } + | { + url: string; + }; + } ); /** diff --git a/drizzle-kit/src/introspect-singlestore.ts b/drizzle-kit/src/introspect-singlestore.ts new file mode 100644 index 000000000..8aa6e3dd7 --- /dev/null +++ b/drizzle-kit/src/introspect-singlestore.ts @@ -0,0 +1,780 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +import './@types/utils'; +import type { Casing } from './cli/validations/common'; +import { Column, Index, PrimaryKey, SingleStoreSchemaInternal, UniqueConstraint } from './serializer/singlestoreSchema'; +import { indexName } from './serializer/singlestoreSerializer'; + +// time precision to fsp +// {mode: "string"} for timestamp by default + +const singlestoreImportsList = new Set([ + 'singlestoreTable', + 'singlestoreEnum', + 'bigint', + 'binary', + 'boolean', + 'char', + 'date', + 'datetime', + 'decimal', + 'double', + 'float', + 'int', + 'json', + // TODO: add new type BSON + // TODO: add new type Blob + // TODO: add new type UUID + // TODO: add new type GUID + // TODO: add new type Vector + // TODO: add new type GeoPoint + 'mediumint', + 'real', + 'serial', + 'smallint', + 'text', + 'tinytext', + 'mediumtext', + 'longtext', + 'time', + 'timestamp', + 'tinyint', + 'varbinary', + 'varchar', + 'year', + 'enum', +]); + +const objToStatement = (json: any) => { + json = Object.fromEntries(Object.entries(json).filter((it) => it[1])); + + const keys = Object.keys(json); + if (keys.length === 0) return; + + let statement = '{ '; + statement += keys.map((it) => `"${it}": "${json[it]}"`).join(', '); + statement += ' }'; + return statement; +}; + +const objToStatement2 = (json: any) => { + json = Object.fromEntries(Object.entries(json).filter((it) => it[1])); + + const keys = Object.keys(json); + if (keys.length === 0) return; + + let statement = '{ '; + statement += keys.map((it) => `${it}: "${json[it]}"`).join(', '); // no "" for keys + statement += ' }'; + return statement; +}; + +const timeConfig = (json: any) => { + json = Object.fromEntries(Object.entries(json).filter((it) => it[1])); + + const keys = Object.keys(json); + if (keys.length === 0) return; + + let statement = '{ '; + statement += keys.map((it) => `${it}: ${json[it]}`).join(', '); + statement += ' }'; + return statement; +}; + +const binaryConfig = (json: any) => { + json = Object.fromEntries(Object.entries(json).filter((it) => it[1])); + + const keys = Object.keys(json); + if (keys.length === 0) return; + + let statement = '{ '; + statement += keys.map((it) => `${it}: ${json[it]}`).join(', '); + statement += ' }'; + return statement; +}; + +const importsPatch = { + 'double precision': 'doublePrecision', + 'timestamp without time zone': 'timestamp', +} as Record; + +const relations = new Set(); + +const prepareCasing = (casing?: Casing) => (value: string) => { + if (typeof casing === 'undefined') { + return value; + } + if (casing === 'camel') { + return value.camelCase(); + } + + return value; +}; + +export const schemaToTypeScript = ( + schema: SingleStoreSchemaInternal, + casing: Casing, +) => { + const withCasing = prepareCasing(casing); + + const imports = Object.values(schema.tables).reduce( + (res, it) => { + const idxImports = Object.values(it.indexes).map((idx) => idx.isUnique ? 'uniqueIndex' : 'index'); + const pkImports = Object.values(it.compositePrimaryKeys).map( + (it) => 'primaryKey', + ); + const uniqueImports = Object.values(it.uniqueConstraints).map( + (it) => 'unique', + ); + + res.singlestore.push(...idxImports); + res.singlestore.push(...pkImports); + res.singlestore.push(...uniqueImports); + + const columnImports = Object.values(it.columns) + .map((col) => { + let patched = importsPatch[col.type] ?? col.type; + patched = patched.startsWith('varchar(') ? 'varchar' : patched; + patched = patched.startsWith('char(') ? 'char' : patched; + patched = patched.startsWith('binary(') ? 'binary' : patched; + patched = patched.startsWith('decimal(') ? 'decimal' : patched; + patched = patched.startsWith('smallint(') ? 'smallint' : patched; + patched = patched.startsWith('enum(') ? 'singlestoreEnum' : patched; + patched = patched.startsWith('datetime(') ? 'datetime' : patched; + patched = patched.startsWith('varbinary(') ? 'varbinary' : patched; + patched = patched.startsWith('int(') ? 'int' : patched; + return patched; + }) + .filter((type) => { + return singlestoreImportsList.has(type); + }); + + res.singlestore.push(...columnImports); + return res; + }, + { singlestore: [] as string[] }, + ); + + const tableStatements = Object.values(schema.tables).map((table) => { + const func = 'singlestoreTable'; + let statement = ''; + if (imports.singlestore.includes(withCasing(table.name))) { + statement = `// Table name is in conflict with ${ + withCasing( + table.name, + ) + } import.\n// Please change to any other name, that is not in imports list\n`; + } + statement += `export const ${withCasing(table.name)} = ${func}("${table.name}", {\n`; + statement += createTableColumns( + Object.values(table.columns), + withCasing, + table.name, + schema, + ); + statement += '}'; + + if ( + Object.keys(table.indexes).length > 0 + || Object.keys(table.compositePrimaryKeys).length > 0 + || Object.keys(table.uniqueConstraints).length > 0 + ) { + statement += ',\n'; + statement += '(table) => {\n'; + statement += '\treturn {\n'; + statement += createTableIndexes( + table.name, + Object.values(table.indexes), + withCasing, + ); + statement += createTablePKs( + Object.values(table.compositePrimaryKeys), + withCasing, + ); + statement += createTableUniques( + Object.values(table.uniqueConstraints), + withCasing, + ); + statement += '\t}\n'; + statement += '}'; + } + + statement += ');'; + return statement; + }); + + const uniqueSingleStoreImports = [ + 'singlestoreTable', + 'singlestoreSchema', + 'AnySingleStoreColumn', + ...new Set(imports.singlestore), + ]; + const importsTs = `import { ${ + uniqueSingleStoreImports.join( + ', ', + ) + } } from "drizzle-orm/singlestore-core"\nimport { sql } from "drizzle-orm"\n\n`; + + let decalrations = ''; + decalrations += tableStatements.join('\n\n'); + + const file = importsTs + decalrations; + + const schemaEntry = ` + { + ${ + Object.values(schema.tables) + .map((it) => withCasing(it.name)) + .join(',') + } + } + `; + + return { + file, // backward compatible, print to file + imports: importsTs, + decalrations, + schemaEntry, + }; +}; + +const mapColumnDefault = (defaultValue: any, isExpression?: boolean) => { + if (isExpression) { + return `sql\`${defaultValue}\``; + } + + return defaultValue; +}; + +const mapColumnDefaultForJson = (defaultValue: any) => { + if ( + typeof defaultValue === 'string' + && defaultValue.startsWith("('") + && defaultValue.endsWith("')") + ) { + return defaultValue.substring(2, defaultValue.length - 2); + } + + return defaultValue; +}; + +const column = ( + type: string, + name: string, + casing: (value: string) => string, + defaultValue?: any, + autoincrement?: boolean, + onUpdate?: boolean, + isExpression?: boolean, +) => { + let lowered = type; + if (!type.startsWith('enum(')) { + lowered = type.toLowerCase(); + } + + if (lowered === 'serial') { + return `${casing(name)}: serial("${name}")`; + } + + if (lowered.startsWith('int')) { + const isUnsigned = lowered.startsWith('int unsigned'); + let out = `${casing(name)}: int("${name}"${isUnsigned ? ', { unsigned: true }' : ''})`; + out += autoincrement ? `.autoincrement()` : ''; + out += typeof defaultValue !== 'undefined' + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('tinyint')) { + const isUnsigned = lowered.startsWith('tinyint unsigned'); + // let out = `${name.camelCase()}: tinyint("${name}")`; + let out: string = `${casing(name)}: tinyint("${name}"${isUnsigned ? ', { unsigned: true }' : ''})`; + out += autoincrement ? `.autoincrement()` : ''; + out += typeof defaultValue !== 'undefined' + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('smallint')) { + const isUnsigned = lowered.startsWith('smallint unsigned'); + let out = `${casing(name)}: smallint("${name}"${isUnsigned ? ', { unsigned: true }' : ''})`; + out += autoincrement ? `.autoincrement()` : ''; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('mediumint')) { + const isUnsigned = lowered.startsWith('mediumint unsigned'); + let out = `${casing(name)}: mediumint("${name}"${isUnsigned ? ', { unsigned: true }' : ''})`; + out += autoincrement ? `.autoincrement()` : ''; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('bigint')) { + const isUnsigned = lowered.startsWith('bigint unsigned'); + let out = `${casing(name)}: bigint("${name}", { mode: "number"${isUnsigned ? ', unsigned: true' : ''} })`; + out += autoincrement ? `.autoincrement()` : ''; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered === 'boolean') { + let out = `${casing(name)}: boolean("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('double')) { + let params: + | { precision: string | undefined; scale: string | undefined } + | undefined; + + if (lowered.length > 6) { + const [precision, scale] = lowered + .slice(7, lowered.length - 1) + .split(','); + params = { precision, scale }; + } + + let out = params + ? `${casing(name)}: double("${name}", ${timeConfig(params)})` + : `${casing(name)}: double("${name}")`; + + // let out = `${name.camelCase()}: double("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered === 'float') { + let out = `${casing(name)}: float("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered === 'real') { + let out = `${casing(name)}: real("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('timestamp')) { + const keyLength = 'timestamp'.length + 1; + let fsp = lowered.length > keyLength + ? Number(lowered.substring(keyLength, lowered.length - 1)) + : null; + fsp = fsp ? fsp : null; + + const params = timeConfig({ fsp, mode: "'string'" }); + + let out = params + ? `${casing(name)}: timestamp("${name}", ${params})` + : `${casing(name)}: timestamp("${name}")`; + + // TODO: check if SingleStore has defaultNow() or now() + defaultValue = defaultValue === 'now()' || defaultValue === '(CURRENT_TIMESTAMP)' + ? '.defaultNow()' + : defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + + // TODO: check if SingleStore has onUpdateNow() + let onUpdateNow = onUpdate ? '.onUpdateNow()' : ''; + out += onUpdateNow; + + return out; + } + + if (lowered.startsWith('time')) { + const keyLength = 'time'.length + 1; + let fsp = lowered.length > keyLength + ? Number(lowered.substring(keyLength, lowered.length - 1)) + : null; + fsp = fsp ? fsp : null; + + const params = timeConfig({ fsp }); + + let out = params + ? `${casing(name)}: time("${name}", ${params})` + : `${casing(name)}: time("${name}")`; + + defaultValue = defaultValue === 'now()' + ? '.defaultNow()' + : defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered === 'date') { + let out = `// you can use { mode: 'date' }, if you want to have Date as type for this column\n\t${ + casing( + name, + ) + }: date("${name}", { mode: 'string' })`; + + defaultValue = defaultValue === 'now()' + ? '.defaultNow()' + : defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + // in mysql text can't have default value. Will leave it in case smth ;) + // TODO: check if SingleStore has text can't have default value + if (lowered === 'text') { + let out = `${casing(name)}: text("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + // in mysql text can't have default value. Will leave it in case smth ;) + // TODO: check if SingleStore has tinytext can't have default value + if (lowered === 'tinytext') { + let out = `${casing(name)}: tinytext("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + // in mysql text can't have default value. Will leave it in case smth ;) + // TODO: check if SingleStore has mediumtext can't have default value + if (lowered === 'mediumtext') { + let out = `${casing(name)}: mediumtext("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + // in mysql text can't have default value. Will leave it in case smth ;) + // TODO: check if SingleStore has longtext can't have default value + if (lowered === 'longtext') { + let out = `${casing(name)}: longtext("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered === 'year') { + let out = `${casing(name)}: year("${name}")`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + // in mysql json can't have default value. Will leave it in case smth ;) + // TODO: check if SingleStore has json can't have default value + if (lowered === 'json') { + let out = `${casing(name)}: json("${name}")`; + + out += defaultValue + ? `.default(${mapColumnDefaultForJson(defaultValue)})` + : ''; + + return out; + } + + // TODO: add new type BSON + + // TODO: add new type Blob + + // TODO: add new type UUID + + // TODO: add new type GUID + + // TODO: add new type Vector + + // TODO: add new type GeoPoint + + if (lowered.startsWith('varchar')) { + let out: string = `${ + casing( + name, + ) + }: varchar("${name}", { length: ${ + lowered.substring( + 'varchar'.length + 1, + lowered.length - 1, + ) + } })`; + + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('char')) { + let out: string = `${ + casing( + name, + ) + }: char("${name}", { length: ${ + lowered.substring( + 'char'.length + 1, + lowered.length - 1, + ) + } })`; + + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('datetime')) { + let out = `// you can use { mode: 'date' }, if you want to have Date as type for this column\n\t`; + + const fsp = lowered.startsWith('datetime(') + ? lowered.substring('datetime'.length + 1, lowered.length - 1) + : undefined; + + out = fsp + ? `${ + casing( + name, + ) + }: datetime("${name}", { mode: 'string', fsp: ${ + lowered.substring( + 'datetime'.length + 1, + lowered.length - 1, + ) + } })` + : `${casing(name)}: datetime("${name}", { mode: 'string'})`; + + defaultValue = defaultValue === 'now()' + ? '.defaultNow()' + : defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered.startsWith('decimal')) { + let params: + | { precision: string | undefined; scale: string | undefined } + | undefined; + + if (lowered.length > 7) { + const [precision, scale] = lowered + .slice(8, lowered.length - 1) + .split(','); + params = { precision, scale }; + } + + let out = params + ? `${casing(name)}: decimal("${name}", ${timeConfig(params)})` + : `${casing(name)}: decimal("${name}")`; + + defaultValue = typeof defaultValue !== 'undefined' + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered.startsWith('binary')) { + const keyLength = 'binary'.length + 1; + let length = lowered.length > keyLength + ? Number(lowered.substring(keyLength, lowered.length - 1)) + : null; + length = length ? length : null; + + const params = binaryConfig({ length }); + + let out = params + ? `${casing(name)}: binary("${name}", ${params})` + : `${casing(name)}: binary("${name}")`; + + defaultValue = defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + if (lowered.startsWith('enum')) { + const values = lowered.substring('enum'.length + 1, lowered.length - 1); + let out = `${casing(name)}: singlestoreEnum("${name}", [${values}])`; + out += defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + return out; + } + + if (lowered.startsWith('varbinary')) { + const keyLength = 'varbinary'.length + 1; + let length = lowered.length > keyLength + ? Number(lowered.substring(keyLength, lowered.length - 1)) + : null; + length = length ? length : null; + + const params = binaryConfig({ length }); + + let out = params + ? `${casing(name)}: varbinary("${name}", ${params})` + : `${casing(name)}: varbinary("${name}")`; + + defaultValue = defaultValue + ? `.default(${mapColumnDefault(defaultValue, isExpression)})` + : ''; + + out += defaultValue; + return out; + } + + console.log('uknown', type); + return `// Warning: Can't parse ${type} from database\n\t// ${type}Type: ${type}("${name}")`; +}; + +const createTableColumns = ( + columns: Column[], + casing: (val: string) => string, + tableName: string, + schema: SingleStoreSchemaInternal, +): string => { + let statement = ''; + + columns.forEach((it) => { + statement += '\t'; + statement += column( + it.type, + it.name, + casing, + it.default, + it.autoincrement, + it.onUpdate, + schema.internal?.tables![tableName]?.columns[it.name] + ?.isDefaultAnExpression ?? false, + ); + statement += it.primaryKey ? '.primaryKey()' : ''; + statement += it.notNull ? '.notNull()' : ''; + + statement += it.generated + ? `.generatedAlwaysAs(sql\`${ + it.generated.as.replace( + /`/g, + '\\`', + ) + }\`, { mode: "${it.generated.type}" })` + : ''; + + statement += ',\n'; + }); + + return statement; +}; + +const createTableIndexes = ( + tableName: string, + idxs: Index[], + casing: (value: string) => string, +): string => { + let statement = ''; + + idxs.forEach((it) => { + let idxKey = it.name.startsWith(tableName) && it.name !== tableName + ? it.name.slice(tableName.length + 1) + : it.name; + idxKey = idxKey.endsWith('_index') + ? idxKey.slice(0, -'_index'.length) + '_idx' + : idxKey; + + idxKey = casing(idxKey); + + const indexGeneratedName = indexName(tableName, it.columns); + const escapedIndexName = indexGeneratedName === it.name ? '' : `"${it.name}"`; + + statement += `\t\t${idxKey}: `; + statement += it.isUnique ? 'uniqueIndex(' : 'index('; + statement += `${escapedIndexName})`; + statement += `.on(${ + it.columns + .map((it) => `table.${casing(it)}`) + .join(', ') + }),`; + statement += `\n`; + }); + + return statement; +}; + +const createTableUniques = ( + unqs: UniqueConstraint[], + casing: (value: string) => string, +): string => { + let statement = ''; + + unqs.forEach((it) => { + const idxKey = casing(it.name); + + statement += `\t\t${idxKey}: `; + statement += 'unique('; + statement += `"${it.name}")`; + statement += `.on(${ + it.columns + .map((it) => `table.${casing(it)}`) + .join(', ') + }),`; + statement += `\n`; + }); + + return statement; +}; + +const createTablePKs = ( + pks: PrimaryKey[], + casing: (value: string) => string, +): string => { + let statement = ''; + + pks.forEach((it) => { + let idxKey = casing(it.name); + + statement += `\t\t${idxKey}: `; + statement += 'primaryKey({ columns: ['; + statement += `${ + it.columns + .map((c) => { + return `table.${casing(c)}`; + }) + .join(', ') + }]${it.name ? `, name: "${it.name}"` : ''}}`; + statement += '),'; + statement += `\n`; + }); + + return statement; +}; diff --git a/drizzle-kit/src/jsonStatements.ts b/drizzle-kit/src/jsonStatements.ts index ad2afea7f..b27785d9a 100644 --- a/drizzle-kit/src/jsonStatements.ts +++ b/drizzle-kit/src/jsonStatements.ts @@ -1,10 +1,16 @@ import chalk from 'chalk'; -import { table } from 'console'; +import { getNewTableName } from './cli/commands/sqlitePushUtils'; import { warning } from './cli/views'; -import { CommonSquashedSchema, Dialect } from './schemaValidator'; +import { CommonSquashedSchema } from './schemaValidator'; import { MySqlKitInternals, MySqlSchema, MySqlSquasher } from './serializer/mysqlSchema'; import { Index, PgSchema, PgSquasher } from './serializer/pgSchema'; -import { SQLiteKitInternals, SQLiteSquasher } from './serializer/sqliteSchema'; +import { SingleStoreKitInternals, SingleStoreSchema, SingleStoreSquasher } from './serializer/singlestoreSchema'; +import { + SQLiteKitInternals, + SQLiteSchemaInternal, + SQLiteSchemaSquashed, + SQLiteSquasher, +} from './serializer/sqliteSchema'; import { AlteredColumn, Column, Sequence, Table } from './snapshotsDiffer'; export interface JsonSqliteCreateTableStatement { @@ -32,7 +38,24 @@ export interface JsonCreateTableStatement { compositePKs: string[]; compositePkName?: string; uniqueConstraints?: string[]; - internals?: MySqlKitInternals; + internals?: MySqlKitInternals | SingleStoreKitInternals; +} + +export interface JsonRecreateTableStatement { + type: 'recreate_table'; + tableName: string; + columns: Column[]; + referenceData: { + name: string; + tableFrom: string; + columnsFrom: string[]; + tableTo: string; + columnsTo: string[]; + onUpdate?: string | undefined; + onDelete?: string | undefined; + }[]; + compositePKs: string[][]; + uniqueConstraints?: string[]; } export interface JsonDropTableStatement { @@ -158,7 +181,7 @@ export interface JsonCreateIndexStatement { tableName: string; data: string; schema: string; - internal?: MySqlKitInternals | SQLiteKitInternals; + internal?: MySqlKitInternals | SQLiteKitInternals | SingleStoreKitInternals; } export interface JsonPgCreateIndexStatement { @@ -173,6 +196,10 @@ export interface JsonReferenceStatement { data: string; schema: string; tableName: string; + isMulticolumn?: boolean; + columnNotNull?: boolean; + columnDefault?: string; + columnType?: string; // fromTable: string; // fromColumns: string[]; // toTable: string; @@ -519,6 +546,7 @@ export type JsonAlterColumnStatement = | JsonAlterColumnDropIdentityStatement; export type JsonStatement = + | JsonRecreateTableStatement | JsonAlterColumnStatement | JsonCreateTableStatement | JsonDropTableStatement @@ -611,6 +639,34 @@ export const prepareMySqlCreateTableJson = ( }; }; +export const prepareSingleStoreCreateTableJson = ( + table: Table, + // TODO: remove? + json2: SingleStoreSchema, + // we need it to know if some of the indexes(and in future other parts) are expressions or columns + // didn't change singlestoreserialaizer, because it will break snapshots and diffs and it's hard to detect + // if previously it was an expression or column + internals: SingleStoreKitInternals, +): JsonCreateTableStatement => { + const { name, schema, columns, compositePrimaryKeys, uniqueConstraints } = table; + + return { + type: 'create_table', + tableName: name, + schema, + columns: Object.values(columns), + compositePKs: Object.values(compositePrimaryKeys), + compositePkName: Object.values(compositePrimaryKeys).length > 0 + ? json2.tables[name].compositePrimaryKeys[ + SingleStoreSquasher.unsquashPK(Object.values(compositePrimaryKeys)[0]) + .name + ].name + : '', + uniqueConstraints: Object.values(uniqueConstraints), + internals, + }; +}; + export const prepareSQLiteCreateTable = ( table: Table, action?: 'push' | undefined, @@ -830,7 +886,7 @@ export const prepareDeleteSchemasJson = ( export const prepareRenameColumns = ( tableName: string, - // TODO: split for pg and mysql+sqlite without schema + // TODO: split for pg and mysql+sqlite and singlestore without schema schema: string, pairs: { from: Column; to: Column }[], ): JsonRenameColumnStatement[] => { @@ -1260,15 +1316,15 @@ export const prepareAlterColumnsMysql = ( return [...dropPkStatements, ...setPkStatements, ...statements]; }; -export const preparePgAlterColumns = ( - _tableName: string, +export const prepareAlterColumnsSingleStore = ( + tableName: string, schema: string, columns: AlteredColumn[], // TODO: remove? + json1: CommonSquashedSchema, json2: CommonSquashedSchema, action?: 'push' | undefined, ): JsonAlterColumnStatement[] => { - const tableKey = `${schema || 'public'}.${_tableName}`; let statements: JsonAlterColumnStatement[] = []; let dropPkStatements: JsonAlterColumnDropPrimaryKeyStatement[] = []; let setPkStatements: JsonAlterColumnSetPrimaryKeyStatement[] = []; @@ -1276,23 +1332,90 @@ export const preparePgAlterColumns = ( for (const column of columns) { const columnName = typeof column.name !== 'string' ? column.name.new : column.name; - const tableName = json2.tables[tableKey].name; + const table = json2.tables[tableName]; + const snapshotColumn = table.columns[columnName]; - // I used any, because those fields are available only for mysql dialect + const columnType = snapshotColumn.type; + const columnDefault = snapshotColumn.default; + const columnOnUpdate = 'onUpdate' in snapshotColumn ? snapshotColumn.onUpdate : undefined; + const columnNotNull = table.columns[columnName].notNull; + + const columnAutoIncrement = 'autoincrement' in snapshotColumn + ? snapshotColumn.autoincrement ?? false + : false; + + const columnPk = table.columns[columnName].primaryKey; + + if (column.autoincrement?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_autoincrement', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.autoincrement?.type === 'changed') { + const type = column.autoincrement.new + ? 'alter_table_alter_column_set_autoincrement' + : 'alter_table_alter_column_drop_autoincrement'; + + statements.push({ + type, + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.autoincrement?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_autoincrement', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + } + + for (const column of columns) { + const columnName = typeof column.name !== 'string' ? column.name.new : column.name; + + // I used any, because those fields are available only for mysql and singlestore dialect // For other dialects it will become undefined, that is fine for json statements - const columnType = json2.tables[tableKey].columns[columnName].type; - const columnDefault = json2.tables[tableKey].columns[columnName].default; - const columnGenerated = json2.tables[tableKey].columns[columnName].generated; - const columnOnUpdate = (json2.tables[tableKey].columns[columnName] as any) + const columnType = json2.tables[tableName].columns[columnName].type; + const columnDefault = json2.tables[tableName].columns[columnName].default; + const columnGenerated = json2.tables[tableName].columns[columnName].generated; + const columnOnUpdate = (json2.tables[tableName].columns[columnName] as any) .onUpdate; - const columnNotNull = json2.tables[tableKey].columns[columnName].notNull; + const columnNotNull = json2.tables[tableName].columns[columnName].notNull; const columnAutoIncrement = ( - json2.tables[tableKey].columns[columnName] as any + json2.tables[tableName].columns[columnName] as any ).autoincrement; - const columnPk = (json2.tables[tableKey].columns[columnName] as any) + const columnPk = (json2.tables[tableName].columns[columnName] as any) .primaryKey; - const compositePk = json2.tables[tableKey].compositePrimaryKeys[`${tableName}_${columnName}`]; + const compositePk = json2.tables[tableName].compositePrimaryKeys[ + `${tableName}_${columnName}` + ]; if (typeof column.name !== 'string') { statements.push({ @@ -1317,6 +1440,7 @@ export const preparePgAlterColumns = ( columnNotNull, columnAutoIncrement, columnPk, + columnGenerated, }); } @@ -1429,37 +1553,17 @@ export const preparePgAlterColumns = ( }); } - if (column.identity?.type === 'added') { - statements.push({ - type: 'alter_table_alter_column_set_identity', - tableName, - columnName, - schema, - identity: column.identity.value, - }); - } - - if (column.identity?.type === 'changed') { - statements.push({ - type: 'alter_table_alter_column_change_identity', - tableName, - columnName, - schema, - identity: column.identity.new, - oldIdentity: column.identity.old, - }); - } - - if (column.identity?.type === 'deleted') { - statements.push({ - type: 'alter_table_alter_column_drop_identity', - tableName, - columnName, - schema, - }); - } - if (column.generated?.type === 'added') { + if (columnGenerated?.type === 'virtual') { + // TODO: Change warning message according to SingleStore docs + warning( + `You are trying to add virtual generated constraint to ${ + chalk.blue( + columnName, + ) + } column. As MySQL docs mention: "Nongenerated columns can be altered to stored but not virtual generated columns". We will drop an existing column and add it with a virtual generated statement. This means that the data previously stored in this column will be wiped, and new data will be generated on each read for this column\n`, + ); + } statements.push({ type: 'alter_table_alter_column_set_generated', tableName, @@ -1492,6 +1596,16 @@ export const preparePgAlterColumns = ( } if (column.generated?.type === 'deleted') { + if (columnGenerated?.type === 'virtual') { + // TODO: Change warning message according to SingleStore docs + warning( + `You are trying to remove virtual generated constraint from ${ + chalk.blue( + columnName, + ) + } column. As MySQL docs mention: "Stored but not virtual generated columns can be altered to nongenerated columns. The stored generated values become the values of the nongenerated column". We will drop an existing column and add it without a virtual generated statement. This means that this column will have no data after migration\n`, + ); + } statements.push({ type: 'alter_table_alter_column_drop_generated', tableName, @@ -1504,6 +1618,7 @@ export const preparePgAlterColumns = ( columnAutoIncrement, columnPk, columnGenerated, + oldColumn: json1.tables[tableName].columns[columnName], }); } @@ -1524,52 +1639,6 @@ export const preparePgAlterColumns = ( } } - // if (column.primaryKey?.type === "added") { - // statements.push({ - // type: "alter_table_alter_column_set_primarykey", - // tableName, - // columnName, - // schema, - // newDataType: columnType, - // columnDefault, - // columnOnUpdate, - // columnNotNull, - // columnAutoIncrement, - // }); - // } - - // if (column.primaryKey?.type === "changed") { - // const type = column.primaryKey.new - // ? "alter_table_alter_column_set_primarykey" - // : "alter_table_alter_column_drop_primarykey"; - - // statements.push({ - // type, - // tableName, - // columnName, - // schema, - // newDataType: columnType, - // columnDefault, - // columnOnUpdate, - // columnNotNull, - // columnAutoIncrement, - // }); - // } - - // if (column.primaryKey?.type === "deleted") { - // statements.push({ - // type: "alter_table_alter_column_drop_primarykey", - // tableName, - // columnName, - // schema, - // newDataType: columnType, - // columnDefault, - // columnOnUpdate, - // columnNotNull, - // columnAutoIncrement, - // }); - // } - if (column.onUpdate?.type === 'added') { statements.push({ type: 'alter_table_alter_column_set_on_update', @@ -1604,13 +1673,15 @@ export const preparePgAlterColumns = ( return [...dropPkStatements, ...setPkStatements, ...statements]; }; -export const prepareSqliteAlterColumns = ( - tableName: string, +export const preparePgAlterColumns = ( + _tableName: string, schema: string, columns: AlteredColumn[], // TODO: remove? json2: CommonSquashedSchema, + action?: 'push' | undefined, ): JsonAlterColumnStatement[] => { + const tableKey = `${schema || 'public'}.${_tableName}`; let statements: JsonAlterColumnStatement[] = []; let dropPkStatements: JsonAlterColumnDropPrimaryKeyStatement[] = []; let setPkStatements: JsonAlterColumnSetPrimaryKeyStatement[] = []; @@ -1618,24 +1689,23 @@ export const prepareSqliteAlterColumns = ( for (const column of columns) { const columnName = typeof column.name !== 'string' ? column.name.new : column.name; + const tableName = json2.tables[tableKey].name; + // I used any, because those fields are available only for mysql dialect // For other dialects it will become undefined, that is fine for json statements - const columnType = json2.tables[tableName].columns[columnName].type; - const columnDefault = json2.tables[tableName].columns[columnName].default; - const columnOnUpdate = (json2.tables[tableName].columns[columnName] as any) + const columnType = json2.tables[tableKey].columns[columnName].type; + const columnDefault = json2.tables[tableKey].columns[columnName].default; + const columnGenerated = json2.tables[tableKey].columns[columnName].generated; + const columnOnUpdate = (json2.tables[tableKey].columns[columnName] as any) .onUpdate; - const columnNotNull = json2.tables[tableName].columns[columnName].notNull; + const columnNotNull = json2.tables[tableKey].columns[columnName].notNull; const columnAutoIncrement = ( - json2.tables[tableName].columns[columnName] as any + json2.tables[tableKey].columns[columnName] as any ).autoincrement; - const columnPk = (json2.tables[tableName].columns[columnName] as any) + const columnPk = (json2.tables[tableKey].columns[columnName] as any) .primaryKey; - const columnGenerated = json2.tables[tableName].columns[columnName].generated; - - const compositePk = json2.tables[tableName].compositePrimaryKeys[ - `${tableName}_${columnName}` - ]; + const compositePk = json2.tables[tableKey].compositePrimaryKeys[`${tableName}_${columnName}`]; if (typeof column.name !== 'string') { statements.push({ @@ -1772,10 +1842,402 @@ export const prepareSqliteAlterColumns = ( }); } - if (column.generated?.type === 'added') { - if (columnGenerated?.type === 'virtual') { - statements.push({ - type: 'alter_table_alter_column_set_generated', + if (column.identity?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_identity', + tableName, + columnName, + schema, + identity: column.identity.value, + }); + } + + if (column.identity?.type === 'changed') { + statements.push({ + type: 'alter_table_alter_column_change_identity', + tableName, + columnName, + schema, + identity: column.identity.new, + oldIdentity: column.identity.old, + }); + } + + if (column.identity?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_identity', + tableName, + columnName, + schema, + }); + } + + if (column.generated?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_generated', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + columnGenerated, + }); + } + + if (column.generated?.type === 'changed' && action !== 'push') { + statements.push({ + type: 'alter_table_alter_column_alter_generated', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + columnGenerated, + }); + } + + if (column.generated?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_generated', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + columnGenerated, + }); + } + + if ( + column.primaryKey?.type === 'added' + || (column.primaryKey?.type === 'changed' && column.primaryKey.new) + ) { + const wasAutoincrement = statements.filter( + (it) => it.type === 'alter_table_alter_column_set_autoincrement', + ); + if (wasAutoincrement.length === 0) { + setPkStatements.push({ + type: 'alter_table_alter_column_set_pk', + tableName, + schema, + columnName, + }); + } + } + + // if (column.primaryKey?.type === "added") { + // statements.push({ + // type: "alter_table_alter_column_set_primarykey", + // tableName, + // columnName, + // schema, + // newDataType: columnType, + // columnDefault, + // columnOnUpdate, + // columnNotNull, + // columnAutoIncrement, + // }); + // } + + // if (column.primaryKey?.type === "changed") { + // const type = column.primaryKey.new + // ? "alter_table_alter_column_set_primarykey" + // : "alter_table_alter_column_drop_primarykey"; + + // statements.push({ + // type, + // tableName, + // columnName, + // schema, + // newDataType: columnType, + // columnDefault, + // columnOnUpdate, + // columnNotNull, + // columnAutoIncrement, + // }); + // } + + // if (column.primaryKey?.type === "deleted") { + // statements.push({ + // type: "alter_table_alter_column_drop_primarykey", + // tableName, + // columnName, + // schema, + // newDataType: columnType, + // columnDefault, + // columnOnUpdate, + // columnNotNull, + // columnAutoIncrement, + // }); + // } + + if (column.onUpdate?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_on_update', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.onUpdate?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_on_update', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + } + + return [...dropPkStatements, ...setPkStatements, ...statements]; +}; + +export const prepareSqliteAlterColumns = ( + tableName: string, + schema: string, + columns: AlteredColumn[], + // TODO: remove? + json2: CommonSquashedSchema, +): JsonAlterColumnStatement[] => { + let statements: JsonAlterColumnStatement[] = []; + let dropPkStatements: JsonAlterColumnDropPrimaryKeyStatement[] = []; + let setPkStatements: JsonAlterColumnSetPrimaryKeyStatement[] = []; + + for (const column of columns) { + const columnName = typeof column.name !== 'string' ? column.name.new : column.name; + + // I used any, because those fields are available only for mysql dialect + // For other dialects it will become undefined, that is fine for json statements + const columnType = json2.tables[tableName].columns[columnName].type; + const columnDefault = json2.tables[tableName].columns[columnName].default; + const columnOnUpdate = (json2.tables[tableName].columns[columnName] as any) + .onUpdate; + const columnNotNull = json2.tables[tableName].columns[columnName].notNull; + const columnAutoIncrement = ( + json2.tables[tableName].columns[columnName] as any + ).autoincrement; + const columnPk = (json2.tables[tableName].columns[columnName] as any) + .primaryKey; + + const columnGenerated = json2.tables[tableName].columns[columnName].generated; + + const compositePk = json2.tables[tableName].compositePrimaryKeys[ + `${tableName}_${columnName}` + ]; + + if (column.autoincrement?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_autoincrement', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.autoincrement?.type === 'changed') { + const type = column.autoincrement.new + ? 'alter_table_alter_column_set_autoincrement' + : 'alter_table_alter_column_drop_autoincrement'; + + statements.push({ + type, + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.autoincrement?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_autoincrement', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (typeof column.name !== 'string') { + statements.push({ + type: 'alter_table_rename_column', + tableName, + oldColumnName: column.name.old, + newColumnName: column.name.new, + schema, + }); + } + + if (column.type?.type === 'changed') { + statements.push({ + type: 'alter_table_alter_column_set_type', + tableName, + columnName, + newDataType: column.type.new, + oldDataType: column.type.old, + schema, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if ( + column.primaryKey?.type === 'deleted' + || (column.primaryKey?.type === 'changed' + && !column.primaryKey.new + && typeof compositePk === 'undefined') + ) { + dropPkStatements.push({ + //// + type: 'alter_table_alter_column_drop_pk', + tableName, + columnName, + schema, + }); + } + + if (column.default?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_default', + tableName, + columnName, + newDefaultValue: column.default.value, + schema, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + newDataType: columnType, + columnPk, + }); + } + + if (column.default?.type === 'changed') { + statements.push({ + type: 'alter_table_alter_column_set_default', + tableName, + columnName, + newDefaultValue: column.default.new, + oldDefaultValue: column.default.old, + schema, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + newDataType: columnType, + columnPk, + }); + } + + if (column.default?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_default', + tableName, + columnName, + schema, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + newDataType: columnType, + columnPk, + }); + } + + if (column.notNull?.type === 'added') { + statements.push({ + type: 'alter_table_alter_column_set_notnull', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.notNull?.type === 'changed') { + const type = column.notNull.new + ? 'alter_table_alter_column_set_notnull' + : 'alter_table_alter_column_drop_notnull'; + statements.push({ + type: type, + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.notNull?.type === 'deleted') { + statements.push({ + type: 'alter_table_alter_column_drop_notnull', + tableName, + columnName, + schema, + newDataType: columnType, + columnDefault, + columnOnUpdate, + columnNotNull, + columnAutoIncrement, + columnPk, + }); + } + + if (column.generated?.type === 'added') { + if (columnGenerated?.type === 'virtual') { + statements.push({ + type: 'alter_table_alter_column_set_generated', tableName, columnName, schema, @@ -1944,6 +2406,54 @@ export const prepareCreateReferencesJson = ( }; }); }; +export const prepareLibSQLCreateReferencesJson = ( + tableName: string, + schema: string, + foreignKeys: Record, + json2: SQLiteSchemaSquashed, + action?: 'push', +): JsonCreateReferenceStatement[] => { + return Object.values(foreignKeys).map((fkData) => { + const { columnsFrom, tableFrom, columnsTo } = action === 'push' + ? SQLiteSquasher.unsquashPushFK(fkData) + : SQLiteSquasher.unsquashFK(fkData); + + // When trying to alter table in lib sql it is necessary to pass all config for column like "NOT NULL", "DEFAULT", etc. + // If it is multicolumn reference it is not possible to pass this data for all columns + // Pass multicolumn flag for sql statements to not generate migration + let isMulticolumn = false; + + if (columnsFrom.length > 1 || columnsTo.length > 1) { + isMulticolumn = true; + + return { + type: 'create_reference', + tableName, + data: fkData, + schema, + isMulticolumn, + }; + } + + const columnFrom = columnsFrom[0]; + + const { + notNull: columnNotNull, + default: columnDefault, + type: columnType, + } = json2.tables[tableFrom].columns[columnFrom]; + + return { + type: 'create_reference', + tableName, + data: fkData, + schema, + columnNotNull, + columnDefault, + columnType, + }; + }); +}; export const prepareDropReferencesJson = ( tableName: string, @@ -1959,6 +2469,77 @@ export const prepareDropReferencesJson = ( }; }); }; +export const prepareLibSQLDropReferencesJson = ( + tableName: string, + schema: string, + foreignKeys: Record, + json2: SQLiteSchemaSquashed, + meta: SQLiteSchemaInternal['_meta'], + action?: 'push', +): JsonDeleteReferenceStatement[] => { + const statements = Object.values(foreignKeys).map((fkData) => { + const { columnsFrom, tableFrom, columnsTo, name, tableTo, onDelete, onUpdate } = action === 'push' + ? SQLiteSquasher.unsquashPushFK(fkData) + : SQLiteSquasher.unsquashFK(fkData); + + // If all columns from where were references were deleted -> skip this logic + // Drop columns will cover this scenario + const keys = Object.keys(json2.tables[tableName].columns); + const filtered = columnsFrom.filter((it) => keys.includes(it)); + const fullDrop = filtered.length === 0; + if (fullDrop) return; + + // When trying to alter table in lib sql it is necessary to pass all config for column like "NOT NULL", "DEFAULT", etc. + // If it is multicolumn reference it is not possible to pass this data for all columns + // Pass multicolumn flag for sql statements to not generate migration + let isMulticolumn = false; + + if (columnsFrom.length > 1 || columnsTo.length > 1) { + isMulticolumn = true; + + return { + type: 'delete_reference', + tableName, + data: fkData, + schema, + isMulticolumn, + }; + } + + const columnFrom = columnsFrom[0]; + const newTableName = getNewTableName(tableFrom, meta); + + const { + notNull: columnNotNull, + default: columnDefault, + type: columnType, + } = json2.tables[newTableName].columns[columnFrom]; + + const fkToSquash = { + columnsFrom, + columnsTo, + name, + tableFrom: newTableName, + tableTo, + onDelete, + onUpdate, + }; + const foreignKey = action === 'push' + ? SQLiteSquasher.squashPushFK(fkToSquash) + : SQLiteSquasher.squashFK(fkToSquash); + return { + type: 'delete_reference', + tableName, + data: foreignKey, + schema, + columnNotNull, + columnDefault, + columnType, + }; + }); + + return statements.filter((it) => it) as JsonDeleteReferenceStatement[]; +}; // alter should create 2 statements. It's important to make only 1 sql per statement(for breakpoints) export const prepareAlterReferencesJson = ( @@ -2230,3 +2811,72 @@ export const prepareAlterCompositePrimaryKeyMySql = ( } as JsonAlterCompositePK; }); }; + +export const prepareAddCompositePrimaryKeySingleStore = ( + tableName: string, + pks: Record, + // TODO: remove? + json1: SingleStoreSchema, + json2: SingleStoreSchema, +): JsonCreateCompositePK[] => { + const res: JsonCreateCompositePK[] = []; + for (const it of Object.values(pks)) { + const unsquashed = SingleStoreSquasher.unsquashPK(it); + + if ( + unsquashed.columns.length === 1 + && json1.tables[tableName]?.columns[unsquashed.columns[0]]?.primaryKey + ) { + continue; + } + + res.push({ + type: 'create_composite_pk', + tableName, + data: it, + constraintName: json2.tables[tableName].compositePrimaryKeys[unsquashed.name].name, + } as JsonCreateCompositePK); + } + return res; +}; + +export const prepareDeleteCompositePrimaryKeySingleStore = ( + tableName: string, + pks: Record, + // TODO: remove? + json1: SingleStoreSchema, +): JsonDeleteCompositePK[] => { + return Object.values(pks).map((it) => { + return { + type: 'delete_composite_pk', + tableName, + data: it, + constraintName: json1.tables[tableName].compositePrimaryKeys[ + SingleStoreSquasher.unsquashPK(it).name + ].name, + } as JsonDeleteCompositePK; + }); +}; + +export const prepareAlterCompositePrimaryKeySingleStore = ( + tableName: string, + pks: Record, + // TODO: remove? + json1: SingleStoreSchema, + json2: SingleStoreSchema, +): JsonAlterCompositePK[] => { + return Object.values(pks).map((it) => { + return { + type: 'alter_composite_pk', + tableName, + old: it.__old, + new: it.__new, + oldConstraintName: json1.tables[tableName].compositePrimaryKeys[ + SingleStoreSquasher.unsquashPK(it.__old).name + ].name, + newConstraintName: json2.tables[tableName].compositePrimaryKeys[ + SingleStoreSquasher.unsquashPK(it.__new).name + ].name, + } as JsonAlterCompositePK; + }); +}; diff --git a/drizzle-kit/src/migrationPreparator.ts b/drizzle-kit/src/migrationPreparator.ts index 687cfdb7c..4e5664290 100644 --- a/drizzle-kit/src/migrationPreparator.ts +++ b/drizzle-kit/src/migrationPreparator.ts @@ -1,8 +1,9 @@ import { randomUUID } from 'crypto'; import fs from 'fs'; -import { serializeMySql, serializePg, serializeSQLite } from './serializer'; +import { serializeMySql, serializePg, serializeSingleStore, serializeSQLite } from './serializer'; import { dryMySql, MySqlSchema, mysqlSchema } from './serializer/mysqlSchema'; import { dryPg, PgSchema, pgSchema, PgSchemaInternal } from './serializer/pgSchema'; +import { drySingleStore, SingleStoreSchema, singlestoreSchema } from './serializer/singlestoreSchema'; import { drySQLite, SQLiteSchema, sqliteSchema } from './serializer/sqliteSchema'; export const prepareMySqlDbPushSnapshot = async ( @@ -20,6 +21,21 @@ export const prepareMySqlDbPushSnapshot = async ( return { prev, cur: result }; }; +export const prepareSingleStoreDbPushSnapshot = async ( + prev: SingleStoreSchema, + schemaPath: string | string[], +): Promise<{ prev: SingleStoreSchema; cur: SingleStoreSchema }> => { + const serialized = await serializeSingleStore(schemaPath); + + const id = randomUUID(); + const idPrev = prev.id; + + const { version, dialect, ...rest } = serialized; + const result: SingleStoreSchema = { version, dialect, id, prevId: idPrev, ...rest }; + + return { prev, cur: result }; +}; + export const prepareSQLiteDbPushSnapshot = async ( prev: SQLiteSchema, schemaPath: string | string[], @@ -84,6 +100,33 @@ export const prepareMySqlMigrationSnapshot = async ( return { prev: prevSnapshot, cur: result, custom }; }; +export const prepareSingleStoreMigrationSnapshot = async ( + migrationFolders: string[], + schemaPath: string | string[], +): Promise<{ prev: SingleStoreSchema; cur: SingleStoreSchema; custom: SingleStoreSchema }> => { + const prevSnapshot = singlestoreSchema.parse( + preparePrevSnapshot(migrationFolders, drySingleStore), + ); + const serialized = await serializeSingleStore(schemaPath); + + const id = randomUUID(); + const idPrev = prevSnapshot.id; + + const { version, dialect, ...rest } = serialized; + const result: SingleStoreSchema = { version, dialect, id, prevId: idPrev, ...rest }; + + const { id: _ignoredId, prevId: _ignoredPrevId, ...prevRest } = prevSnapshot; + + // that's for custom migrations, when we need new IDs, but old snapshot + const custom: SingleStoreSchema = { + id, + prevId: idPrev, + ...prevRest, + }; + + return { prev: prevSnapshot, cur: result, custom }; +}; + export const prepareSqliteMigrationSnapshot = async ( snapshots: string[], schemaPath: string | string[], diff --git a/drizzle-kit/src/schemaValidator.ts b/drizzle-kit/src/schemaValidator.ts index 9c1f4dcfc..e91b5ab11 100644 --- a/drizzle-kit/src/schemaValidator.ts +++ b/drizzle-kit/src/schemaValidator.ts @@ -1,9 +1,10 @@ import { enum as enumType, TypeOf, union } from 'zod'; import { mysqlSchema, mysqlSchemaSquashed } from './serializer/mysqlSchema'; import { pgSchema, pgSchemaSquashed } from './serializer/pgSchema'; +import { singlestoreSchema, singlestoreSchemaSquashed } from './serializer/singlestoreSchema'; import { sqliteSchema, SQLiteSchemaSquashed } from './serializer/sqliteSchema'; -export const dialects = ['postgresql', 'mysql', 'sqlite'] as const; +export const dialects = ['postgresql', 'mysql', 'sqlite', 'turso', 'singlestore'] as const; export const dialect = enumType(dialects); export type Dialect = (typeof dialects)[number]; @@ -13,9 +14,10 @@ const commonSquashedSchema = union([ pgSchemaSquashed, mysqlSchemaSquashed, SQLiteSchemaSquashed, + singlestoreSchemaSquashed, ]); -const commonSchema = union([pgSchema, mysqlSchema, sqliteSchema]); +const commonSchema = union([pgSchema, mysqlSchema, sqliteSchema, singlestoreSchema]); export type CommonSquashedSchema = TypeOf; export type CommonSchema = TypeOf; diff --git a/drizzle-kit/src/serializer/index.ts b/drizzle-kit/src/serializer/index.ts index 214ca38c7..6eb55c6f6 100644 --- a/drizzle-kit/src/serializer/index.ts +++ b/drizzle-kit/src/serializer/index.ts @@ -6,6 +6,7 @@ import Path from 'path'; import { error } from '../cli/views'; import type { MySqlSchemaInternal } from './mysqlSchema'; import type { PgSchemaInternal } from './pgSchema'; +import { SingleStoreSchemaInternal } from './singlestoreSchema'; import type { SQLiteSchemaInternal } from './sqliteSchema'; export const sqlToStr = (sql: SQL) => { @@ -78,6 +79,21 @@ export const serializeSQLite = async ( return generateSqliteSnapshot(tables); }; +export const serializeSingleStore = async ( + path: string | string[], +): Promise => { + const filenames = prepareFilenames(path); + + console.log(chalk.gray(`Reading schema files:\n${filenames.join('\n')}\n`)); + + const { prepareFromSingleStoreImports } = await import('./singlestoreImports'); + const { generateSingleStoreSnapshot } = await import('./singlestoreSerializer'); + + const { tables } = await prepareFromSingleStoreImports(filenames); + + return generateSingleStoreSnapshot(tables); +}; + export const prepareFilenames = (path: string | string[]) => { if (typeof path === 'string') { path = [path]; diff --git a/drizzle-kit/src/serializer/singlestoreImports.ts b/drizzle-kit/src/serializer/singlestoreImports.ts new file mode 100644 index 000000000..3fcb1e65f --- /dev/null +++ b/drizzle-kit/src/serializer/singlestoreImports.ts @@ -0,0 +1,31 @@ +import { is } from 'drizzle-orm'; +import { AnySingleStoreTable, SingleStoreTable } from 'drizzle-orm/singlestore-core'; +import { safeRegister } from '../cli/commands/utils'; + +export const prepareFromExports = (exports: Record) => { + const tables: AnySingleStoreTable[] = []; + + const i0values = Object.values(exports); + i0values.forEach((t) => { + if (is(t, SingleStoreTable)) { + tables.push(t); + } + }); + + return { tables }; +}; + +export const prepareFromSingleStoreImports = async (imports: string[]) => { + const tables: AnySingleStoreTable[] = []; + + const { unregister } = await safeRegister(); + for (let i = 0; i < imports.length; i++) { + const it = imports[i]; + const i0: Record = require(`${it}`); + const prepared = prepareFromExports(i0); + + tables.push(...prepared.tables); + } + unregister(); + return { tables: Array.from(new Set(tables)) }; +}; diff --git a/drizzle-kit/src/serializer/singlestoreSchema.ts b/drizzle-kit/src/serializer/singlestoreSchema.ts new file mode 100644 index 000000000..a0bbae1bf --- /dev/null +++ b/drizzle-kit/src/serializer/singlestoreSchema.ts @@ -0,0 +1,203 @@ +import { any, boolean, enum as enumType, literal, object, record, string, TypeOf, union } from 'zod'; +import { mapValues, originUUID, snapshotVersion } from '../global'; + +// ------- V3 -------- +const index = object({ + name: string(), + columns: string().array(), + isUnique: boolean(), + using: enumType(['btree', 'hash']).optional(), + algorithm: enumType(['default', 'inplace', 'copy']).optional(), + lock: enumType(['default', 'none', 'shared', 'exclusive']).optional(), +}).strict(); + +const column = object({ + name: string(), + type: string(), + primaryKey: boolean(), + notNull: boolean(), + autoincrement: boolean().optional(), + default: any().optional(), + onUpdate: any().optional(), + generated: object({ + type: enumType(['stored', 'virtual']), + as: string(), + }).optional(), +}).strict(); + +const compositePK = object({ + name: string(), + columns: string().array(), +}).strict(); + +const uniqueConstraint = object({ + name: string(), + columns: string().array(), +}).strict(); + +const table = object({ + name: string(), + columns: record(string(), column), + indexes: record(string(), index), + compositePrimaryKeys: record(string(), compositePK), + uniqueConstraints: record(string(), uniqueConstraint).default({}), +}).strict(); + +export const kitInternals = object({ + tables: record( + string(), + object({ + columns: record( + string(), + object({ isDefaultAnExpression: boolean().optional() }).optional(), + ), + }).optional(), + ).optional(), + indexes: record( + string(), + object({ + columns: record( + string(), + object({ isExpression: boolean().optional() }).optional(), + ), + }).optional(), + ).optional(), +}).optional(); + +// use main dialect +const dialect = literal('singlestore'); + +const schemaHash = object({ + id: string(), + prevId: string(), +}); + +export const schemaInternal = object({ + version: literal('1'), + dialect: dialect, + tables: record(string(), table), + _meta: object({ + tables: record(string(), string()), + columns: record(string(), string()), + }), + internal: kitInternals, +}).strict(); + +export const schema = schemaInternal.merge(schemaHash); + +const tableSquashed = object({ + name: string(), + columns: record(string(), column), + indexes: record(string(), string()), + compositePrimaryKeys: record(string(), string()), + uniqueConstraints: record(string(), string()).default({}), +}).strict(); + +export const schemaSquashed = object({ + version: literal('1'), + dialect: dialect, + tables: record(string(), tableSquashed), +}).strict(); + +export type Dialect = TypeOf; +export type Column = TypeOf; +export type Table = TypeOf; +export type SingleStoreSchema = TypeOf; +export type SingleStoreSchemaInternal = TypeOf; +export type SingleStoreKitInternals = TypeOf; +export type SingleStoreSchemaSquashed = TypeOf; +export type Index = TypeOf; +export type PrimaryKey = TypeOf; +export type UniqueConstraint = TypeOf; + +export const SingleStoreSquasher = { + squashIdx: (idx: Index) => { + index.parse(idx); + return `${idx.name};${idx.columns.join(',')};${idx.isUnique};${idx.using ?? ''};${idx.algorithm ?? ''};${ + idx.lock ?? '' + }`; + }, + unsquashIdx: (input: string): Index => { + const [name, columnsString, isUnique, using, algorithm, lock] = input.split(';'); + const destructed = { + name, + columns: columnsString.split(','), + isUnique: isUnique === 'true', + using: using ? using : undefined, + algorithm: algorithm ? algorithm : undefined, + lock: lock ? lock : undefined, + }; + return index.parse(destructed); + }, + squashPK: (pk: PrimaryKey) => { + return `${pk.name};${pk.columns.join(',')}`; + }, + unsquashPK: (pk: string): PrimaryKey => { + const splitted = pk.split(';'); + return { name: splitted[0], columns: splitted[1].split(',') }; + }, + squashUnique: (unq: UniqueConstraint) => { + return `${unq.name};${unq.columns.join(',')}`; + }, + unsquashUnique: (unq: string): UniqueConstraint => { + const [name, columns] = unq.split(';'); + return { name, columns: columns.split(',') }; + }, +}; + +export const squashSingleStoreScheme = (json: SingleStoreSchema): SingleStoreSchemaSquashed => { + const mappedTables = Object.fromEntries( + Object.entries(json.tables).map((it) => { + const squashedIndexes = mapValues(it[1].indexes, (index) => { + return SingleStoreSquasher.squashIdx(index); + }); + + const squashedPKs = mapValues(it[1].compositePrimaryKeys, (pk) => { + return SingleStoreSquasher.squashPK(pk); + }); + + const squashedUniqueConstraints = mapValues( + it[1].uniqueConstraints, + (unq) => { + return SingleStoreSquasher.squashUnique(unq); + }, + ); + + return [ + it[0], + { + name: it[1].name, + columns: it[1].columns, + indexes: squashedIndexes, + compositePrimaryKeys: squashedPKs, + uniqueConstraints: squashedUniqueConstraints, + }, + ]; + }), + ); + return { + version: '1', + dialect: json.dialect, + tables: mappedTables, + }; +}; + +export const singlestoreSchema = schema; +export const singlestoreSchemaSquashed = schemaSquashed; + +// no prev version +export const backwardCompatibleSingleStoreSchema = union([singlestoreSchema, schema]); + +export const drySingleStore = singlestoreSchema.parse({ + version: '1', + dialect: 'singlestore', + id: originUUID, + prevId: '', + tables: {}, + schemas: {}, + _meta: { + schemas: {}, + tables: {}, + columns: {}, + }, +}); diff --git a/drizzle-kit/src/serializer/singlestoreSerializer.ts b/drizzle-kit/src/serializer/singlestoreSerializer.ts new file mode 100644 index 000000000..fc91becf2 --- /dev/null +++ b/drizzle-kit/src/serializer/singlestoreSerializer.ts @@ -0,0 +1,604 @@ +import chalk from 'chalk'; +import { is, SQL } from 'drizzle-orm'; +import { + AnySingleStoreTable, + getTableConfig, + type PrimaryKey as PrimaryKeyORM, + SingleStoreDialect, + uniqueKeyName, +} from 'drizzle-orm/singlestore-core'; +import { RowDataPacket } from 'mysql2/promise'; +import { withStyle } from '../cli/validations/outputs'; +import { IntrospectStage, IntrospectStatus } from '../cli/views'; + +import type { DB } from '../utils'; +import { sqlToStr } from '.'; +import { + Column, + Index, + PrimaryKey, + SingleStoreKitInternals, + SingleStoreSchemaInternal, + Table, + UniqueConstraint, +} from './singlestoreSchema'; +// import { SingleStoreColumnWithAutoIncrement } from "drizzle-orm/mysql-core"; +// import { SingleStoreDateBaseColumn } from "drizzle-orm/mysql-core"; + +const dialect = new SingleStoreDialect(); + +export const indexName = (tableName: string, columns: string[]) => { + return `${tableName}_${columns.join('_')}_index`; +}; + +export const generateSingleStoreSnapshot = ( + tables: AnySingleStoreTable[], +): SingleStoreSchemaInternal => { + const result: Record = {}; + const internal: SingleStoreKitInternals = { tables: {}, indexes: {} }; + for (const table of tables) { + const { + name: tableName, + columns, + indexes, + schema, + primaryKeys, + uniqueConstraints, + } = getTableConfig(table); + const columnsObject: Record = {}; + const indexesObject: Record = {}; + const primaryKeysObject: Record = {}; + const uniqueConstraintObject: Record = {}; + + columns.forEach((column) => { + const notNull: boolean = column.notNull; + const sqlTypeLowered = column.getSQLType().toLowerCase(); + const autoIncrement = typeof (column as any).autoIncrement === 'undefined' + ? false + : (column as any).autoIncrement; + + const generated = column.generated; + + const columnToSet: Column = { + name: column.name, + type: column.getSQLType(), + primaryKey: false, + // If field is autoincrement it's notNull by default + // notNull: autoIncrement ? true : notNull, + notNull, + autoincrement: autoIncrement, + onUpdate: (column as any).hasOnUpdateNow, + generated: generated + ? { + as: is(generated.as, SQL) + ? dialect.sqlToQuery(generated.as as SQL).sql + : typeof generated.as === 'function' + ? dialect.sqlToQuery(generated.as() as SQL).sql + : (generated.as as any), + type: generated.mode ?? 'stored', + } + : undefined, + }; + + if (column.primary) { + primaryKeysObject[`${tableName}_${column.name}`] = { + name: `${tableName}_${column.name}`, + columns: [column.name], + }; + } + + if (column.isUnique) { + const existingUnique = uniqueConstraintObject[column.uniqueName!]; + if (typeof existingUnique !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning(`We\'ve found duplicated unique constraint names in ${ + chalk.underline.blue( + tableName, + ) + } table. + The unique constraint ${ + chalk.underline.blue( + column.uniqueName, + ) + } on the ${ + chalk.underline.blue( + column.name, + ) + } column is confilcting with a unique constraint name already defined for ${ + chalk.underline.blue( + existingUnique.columns.join(','), + ) + } columns\n`) + }`, + ); + process.exit(1); + } + uniqueConstraintObject[column.uniqueName!] = { + name: column.uniqueName!, + columns: [columnToSet.name], + }; + } + + if (column.default !== undefined) { + if (is(column.default, SQL)) { + columnToSet.default = sqlToStr(column.default); + } else { + if (typeof column.default === 'string') { + columnToSet.default = `'${column.default}'`; + } else { + if (sqlTypeLowered === 'json') { + columnToSet.default = `'${JSON.stringify(column.default)}'`; + } else if (column.default instanceof Date) { + if (sqlTypeLowered === 'date') { + columnToSet.default = `'${column.default.toISOString().split('T')[0]}'`; + } else if ( + sqlTypeLowered.startsWith('datetime') + || sqlTypeLowered.startsWith('timestamp') + ) { + columnToSet.default = `'${ + column.default + .toISOString() + .replace('T', ' ') + .slice(0, 23) + }'`; + } + } else { + columnToSet.default = column.default; + } + } + if (['blob', 'text', 'json'].includes(column.getSQLType())) { + columnToSet.default = `(${columnToSet.default})`; + } + } + } + columnsObject[column.name] = columnToSet; + }); + + primaryKeys.map((pk: PrimaryKeyORM) => { + const columnNames = pk.columns.map((c: any) => c.name); + primaryKeysObject[pk.getName()] = { + name: pk.getName(), + columns: columnNames, + }; + + // all composite pk's should be treated as notNull + for (const column of pk.columns) { + columnsObject[column.name].notNull = true; + } + }); + + uniqueConstraints?.map((unq) => { + const columnNames = unq.columns.map((c) => c.name); + + const name = unq.name ?? uniqueKeyName(table, columnNames); + + const existingUnique = uniqueConstraintObject[name]; + if (typeof existingUnique !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated unique constraint names in ${ + chalk.underline.blue( + tableName, + ) + } table. \nThe unique constraint ${ + chalk.underline.blue( + name, + ) + } on the ${ + chalk.underline.blue( + columnNames.join(','), + ) + } columns is confilcting with a unique constraint name already defined for ${ + chalk.underline.blue( + existingUnique.columns.join(','), + ) + } columns\n`, + ) + }`, + ); + process.exit(1); + } + + uniqueConstraintObject[name] = { + name: unq.name!, + columns: columnNames, + }; + }); + + indexes.forEach((value) => { + const columns = value.config.columns; + const name = value.config.name; + + let indexColumns = columns.map((it) => { + if (is(it, SQL)) { + const sql = dialect.sqlToQuery(it, 'indexes').sql; + if (typeof internal!.indexes![name] === 'undefined') { + internal!.indexes![name] = { + columns: { + [sql]: { + isExpression: true, + }, + }, + }; + } else { + if (typeof internal!.indexes![name]?.columns[sql] === 'undefined') { + internal!.indexes![name]!.columns[sql] = { + isExpression: true, + }; + } else { + internal!.indexes![name]!.columns[sql]!.isExpression = true; + } + } + return sql; + } else { + return `${it.name}`; + } + }); + + if (value.config.unique) { + if (typeof uniqueConstraintObject[name] !== 'undefined') { + console.log( + `\n${ + withStyle.errorWarning( + `We\'ve found duplicated unique constraint names in ${ + chalk.underline.blue( + tableName, + ) + } table. \nThe unique index ${ + chalk.underline.blue( + name, + ) + } on the ${ + chalk.underline.blue( + indexColumns.join(','), + ) + } columns is confilcting with a unique constraint name already defined for ${ + chalk.underline.blue( + uniqueConstraintObject[name].columns.join(','), + ) + } columns\n`, + ) + }`, + ); + process.exit(1); + } + } + + indexesObject[name] = { + name, + columns: indexColumns, + isUnique: value.config.unique ?? false, + using: value.config.using, + algorithm: value.config.algorythm, + lock: value.config.lock, + }; + }); + + // only handle tables without schemas + if (!schema) { + result[tableName] = { + name: tableName, + columns: columnsObject, + indexes: indexesObject, + compositePrimaryKeys: primaryKeysObject, + uniqueConstraints: uniqueConstraintObject, + }; + } + } + + return { + version: '1', + dialect: 'singlestore', + tables: result, + _meta: { + tables: {}, + columns: {}, + }, + internal, + }; +}; + +function clearDefaults(defaultValue: any, collate: string) { + if (typeof collate === 'undefined' || collate === null) { + collate = `utf8mb4`; + } + + let resultDefault = defaultValue; + collate = `_${collate}`; + if (defaultValue.startsWith(collate)) { + resultDefault = resultDefault + .substring(collate.length, defaultValue.length) + .replace(/\\/g, ''); + if (resultDefault.startsWith("'") && resultDefault.endsWith("'")) { + return `('${resultDefault.substring(1, resultDefault.length - 1)}')`; + } else { + return `'${resultDefault}'`; + } + } else { + return `(${resultDefault})`; + } +} + +export const fromDatabase = async ( + db: DB, + inputSchema: string, + tablesFilter: (table: string) => boolean = (table) => true, + progressCallback?: ( + stage: IntrospectStage, + count: number, + status: IntrospectStatus, + ) => void, +): Promise => { + const result: Record = {}; + const internals: SingleStoreKitInternals = { tables: {}, indexes: {} }; + + const columns = await db.query(`select * from information_schema.columns + where table_schema = '${inputSchema}' and table_name != '__drizzle_migrations' + order by table_name, ordinal_position;`); + + const response = columns as RowDataPacket[]; + + const schemas: string[] = []; + + let columnsCount = 0; + let tablesCount = new Set(); + let indexesCount = 0; + let foreignKeysCount = 0; + + const idxs = await db.query( + `select * from INFORMATION_SCHEMA.STATISTICS + WHERE INFORMATION_SCHEMA.STATISTICS.TABLE_SCHEMA = '${inputSchema}' and INFORMATION_SCHEMA.STATISTICS.INDEX_NAME != 'PRIMARY';`, + ); + + const idxRows = idxs as RowDataPacket[]; + + for (const column of response) { + if (!tablesFilter(column['TABLE_NAME'] as string)) continue; + + columnsCount += 1; + if (progressCallback) { + progressCallback('columns', columnsCount, 'fetching'); + } + const schema: string = column['TABLE_SCHEMA']; + const tableName = column['TABLE_NAME']; + + tablesCount.add(`${schema}.${tableName}`); + if (progressCallback) { + progressCallback('columns', tablesCount.size, 'fetching'); + } + const columnName: string = column['COLUMN_NAME']; + const isNullable = column['IS_NULLABLE'] === 'YES'; // 'YES', 'NO' + const dataType = column['DATA_TYPE']; // varchar + const columnType = column['COLUMN_TYPE']; // varchar(256) + const isPrimary = column['COLUMN_KEY'] === 'PRI'; // 'PRI', '' + const columnDefault: string = column['COLUMN_DEFAULT']; + const collation: string = column['CHARACTER_SET_NAME']; + const geenratedExpression: string = column['GENERATION_EXPRESSION']; + + let columnExtra = column['EXTRA']; + let isAutoincrement = false; // 'auto_increment', '' + let isDefaultAnExpression = false; // 'auto_increment', '' + + if (typeof column['EXTRA'] !== 'undefined') { + columnExtra = column['EXTRA']; + isAutoincrement = column['EXTRA'] === 'auto_increment'; // 'auto_increment', '' + isDefaultAnExpression = column['EXTRA'].includes('DEFAULT_GENERATED'); // 'auto_increment', '' + } + + // if (isPrimary) { + // if (typeof tableToPk[tableName] === "undefined") { + // tableToPk[tableName] = [columnName]; + // } else { + // tableToPk[tableName].push(columnName); + // } + // } + + if (schema !== inputSchema) { + schemas.push(schema); + } + + const table = result[tableName]; + + // let changedType = columnType.replace("bigint unsigned", "serial") + let changedType = columnType; + + if (columnType === 'bigint unsigned' && !isNullable && isAutoincrement) { + // check unique here + const uniqueIdx = idxRows.filter( + (it) => + it['COLUMN_NAME'] === columnName + && it['TABLE_NAME'] === tableName + && it['NON_UNIQUE'] === 0, + ); + if (uniqueIdx && uniqueIdx.length === 1) { + changedType = columnType.replace('bigint unsigned', 'serial'); + } + } + + if (columnType.startsWith('tinyint')) { + changedType = 'tinyint'; + } + + let onUpdate: boolean | undefined = undefined; + if ( + columnType.startsWith('timestamp') + && typeof columnExtra !== 'undefined' + && columnExtra.includes('on update CURRENT_TIMESTAMP') + ) { + onUpdate = true; + } + + const newColumn: Column = { + default: columnDefault === null + ? undefined + : /^-?[\d.]+(?:e-?\d+)?$/.test(columnDefault) + && !columnType.startsWith('decimal') + ? Number(columnDefault) + : isDefaultAnExpression + ? clearDefaults(columnDefault, collation) + : `'${columnDefault}'`, + autoincrement: isAutoincrement, + name: columnName, + type: changedType, + primaryKey: false, + notNull: !isNullable, + onUpdate, + generated: geenratedExpression + ? { + as: geenratedExpression, + type: columnExtra === 'VIRTUAL GENERATED' ? 'virtual' : 'stored', + } + : undefined, + }; + + // Set default to internal object + if (isDefaultAnExpression) { + if (typeof internals!.tables![tableName] === 'undefined') { + internals!.tables![tableName] = { + columns: { + [columnName]: { + isDefaultAnExpression: true, + }, + }, + }; + } else { + if ( + typeof internals!.tables![tableName]!.columns[columnName] + === 'undefined' + ) { + internals!.tables![tableName]!.columns[columnName] = { + isDefaultAnExpression: true, + }; + } else { + internals!.tables![tableName]!.columns[ + columnName + ]!.isDefaultAnExpression = true; + } + } + } + + if (!table) { + result[tableName] = { + name: tableName, + columns: { + [columnName]: newColumn, + }, + compositePrimaryKeys: {}, + indexes: {}, + uniqueConstraints: {}, + }; + } else { + result[tableName]!.columns[columnName] = newColumn; + } + } + + const tablePks = await db.query( + `SELECT table_name, column_name, ordinal_position + FROM information_schema.table_constraints t + LEFT JOIN information_schema.key_column_usage k + USING(constraint_name,table_schema,table_name) + WHERE t.constraint_type='PRIMARY KEY' + and table_name != '__drizzle_migrations' + AND t.table_schema = '${inputSchema}' + ORDER BY ordinal_position`, + ); + + const tableToPk: { [tname: string]: string[] } = {}; + + const tableToPkRows = tablePks as RowDataPacket[]; + for (const tableToPkRow of tableToPkRows) { + const tableName: string = tableToPkRow['TABLE_NAME']; + const columnName: string = tableToPkRow['COLUMN_NAME']; + const position: string = tableToPkRow['ordinal_position']; + + if (typeof result[tableName] === 'undefined') { + continue; + } + + if (typeof tableToPk[tableName] === 'undefined') { + tableToPk[tableName] = [columnName]; + } else { + tableToPk[tableName].push(columnName); + } + } + + for (const [key, value] of Object.entries(tableToPk)) { + // if (value.length > 1) { + result[key].compositePrimaryKeys = { + [`${key}_${value.join('_')}`]: { + name: `${key}_${value.join('_')}`, + columns: value, + }, + }; + // } else if (value.length === 1) { + // result[key].columns[value[0]].primaryKey = true; + // } else { + // } + } + if (progressCallback) { + progressCallback('columns', columnsCount, 'done'); + progressCallback('tables', tablesCount.size, 'done'); + } + + for (const idxRow of idxRows) { + const tableSchema = idxRow['TABLE_SCHEMA']; + const tableName = idxRow['TABLE_NAME']; + const constraintName = idxRow['INDEX_NAME']; + const columnName: string = idxRow['COLUMN_NAME']; + const isUnique = idxRow['NON_UNIQUE'] === 0; + + const tableInResult = result[tableName]; + if (typeof tableInResult === 'undefined') continue; + + // if (tableInResult.columns[columnName].type === "serial") continue; + + indexesCount += 1; + if (progressCallback) { + progressCallback('indexes', indexesCount, 'fetching'); + } + + if (isUnique) { + if ( + typeof tableInResult.uniqueConstraints[constraintName] !== 'undefined' + ) { + tableInResult.uniqueConstraints[constraintName]!.columns.push( + columnName, + ); + } else { + tableInResult.uniqueConstraints[constraintName] = { + name: constraintName, + columns: [columnName], + }; + } + } else { + if (typeof tableInResult.indexes[constraintName] !== 'undefined') { + tableInResult.indexes[constraintName]!.columns.push(columnName); + } else { + tableInResult.indexes[constraintName] = { + name: constraintName, + columns: [columnName], + isUnique: isUnique, + }; + } + } + } + + if (progressCallback) { + progressCallback('indexes', indexesCount, 'done'); + // progressCallback("enums", 0, "fetching"); + progressCallback('enums', 0, 'done'); + } + + return { + version: '1', + dialect: 'singlestore', + tables: result, + _meta: { + tables: {}, + columns: {}, + }, + internal: internals, + }; +}; diff --git a/drizzle-kit/src/serializer/sqliteSerializer.ts b/drizzle-kit/src/serializer/sqliteSerializer.ts index ce544235b..41edd78a9 100644 --- a/drizzle-kit/src/serializer/sqliteSerializer.ts +++ b/drizzle-kit/src/serializer/sqliteSerializer.ts @@ -363,7 +363,6 @@ export const fromDatabase = async ( ) => void, ): Promise => { const result: Record = {}; - const columns = await db.query<{ tableName: string; columnName: string; diff --git a/drizzle-kit/src/serializer/studio.ts b/drizzle-kit/src/serializer/studio.ts index dc78c6c2c..12ea8207c 100644 --- a/drizzle-kit/src/serializer/studio.ts +++ b/drizzle-kit/src/serializer/studio.ts @@ -15,17 +15,24 @@ import { } from 'drizzle-orm'; import { AnyMySqlTable, getTableConfig as mysqlTableConfig, MySqlTable } from 'drizzle-orm/mysql-core'; import { AnyPgTable, getTableConfig as pgTableConfig, PgTable } from 'drizzle-orm/pg-core'; +import { + AnySingleStoreTable, + getTableConfig as singlestoreTableConfig, + SingleStoreTable, +} from 'drizzle-orm/singlestore-core'; import { AnySQLiteTable, getTableConfig as sqliteTableConfig, SQLiteTable } from 'drizzle-orm/sqlite-core'; import fs from 'fs'; import { Hono } from 'hono'; import { cors } from 'hono/cors'; import { createServer } from 'node:https'; +import { LibSQLCredentials } from 'src/cli/validations/libsql'; import { assertUnreachable } from 'src/global'; import superjson from 'superjson'; import { z } from 'zod'; import { safeRegister } from '../cli/commands/utils'; import type { MysqlCredentials } from '../cli/validations/mysql'; import type { PostgresCredentials } from '../cli/validations/postgres'; +import type { SingleStoreCredentials } from '../cli/validations/singlestore'; import type { SqliteCredentials } from '../cli/validations/sqlite'; import { prepareFilenames } from '.'; @@ -43,7 +50,7 @@ type SchemaFile = { export type Setup = { dbHash: string; - dialect: 'postgresql' | 'mysql' | 'sqlite'; + dialect: 'postgresql' | 'mysql' | 'sqlite' | 'singlestore'; driver?: 'aws-data-api' | 'd1-http' | 'turso' | 'pglite'; proxy: (params: ProxyParams) => Promise; customDefaults: CustomDefault[]; @@ -170,6 +177,43 @@ export const prepareSQLiteSchema = async (path: string | string[]) => { return { schema: sqliteSchema, relations, files }; }; +export const prepareSingleStoreSchema = async (path: string | string[]) => { + const imports = prepareFilenames(path); + const singlestoreSchema: Record> = { + public: {}, + }; + const relations: Record = {}; + + // files content as string + const files = imports.map((it, index) => ({ + // get the file name from the path + name: it.split('/').pop() || `schema${index}.ts`, + content: fs.readFileSync(it, 'utf-8'), + })); + + const { unregister } = await safeRegister(); + for (let i = 0; i < imports.length; i++) { + const it = imports[i]; + + const i0: Record = require(`${it}`); + const i0values = Object.entries(i0); + + i0values.forEach(([k, t]) => { + if (is(t, SingleStoreTable)) { + const schema = singlestoreTableConfig(t).schema || 'public'; + singlestoreSchema[schema][k] = t; + } + + if (is(t, Relations)) { + relations[k] = t; + } + }); + } + unregister(); + + return { schema: singlestoreSchema, relations, files }; +}; + const getCustomDefaults = >( schema: Record>, ): CustomDefault[] => { @@ -185,8 +229,10 @@ const getCustomDefaults = >( tableConfig = pgTableConfig(table); } else if (is(table, MySqlTable)) { tableConfig = mysqlTableConfig(table); - } else { + } else if (is(table, SQLiteTable)) { tableConfig = sqliteTableConfig(table); + } else { + tableConfig = singlestoreTableConfig(table); } tableConfig.columns.map((column) => { @@ -297,8 +343,6 @@ export const drizzleForSQLite = async ( const { driver } = credentials; if (driver === 'd1-http') { dbUrl = `d1-http://${credentials.accountId}/${credentials.databaseId}/${credentials.token}`; - } else if (driver === 'turso') { - dbUrl = `turso://${credentials.url}/${credentials.authToken}`; } else { assertUnreachable(driver); } @@ -319,6 +363,65 @@ export const drizzleForSQLite = async ( schemaFiles, }; }; +export const drizzleForLibSQL = async ( + credentials: LibSQLCredentials, + sqliteSchema: Record>, + relations: Record, + schemaFiles?: SchemaFile[], +): Promise => { + const { connectToLibSQL } = await import('../cli/connections'); + + const sqliteDB = await connectToLibSQL(credentials); + const customDefaults = getCustomDefaults(sqliteSchema); + + let dbUrl: string = `turso://${credentials.url}/${credentials.authToken}`; + + const dbHash = createHash('sha256').update(dbUrl).digest('hex'); + + return { + dbHash, + dialect: 'sqlite', + driver: undefined, + proxy: sqliteDB.proxy, + customDefaults, + schema: sqliteSchema, + relations, + schemaFiles, + }; +}; + +export const drizzleForSingleStore = async ( + credentials: SingleStoreCredentials, + singlestoreSchema: Record>, + relations: Record, + schemaFiles?: SchemaFile[], +): Promise => { + const { connectToSingleStore } = await import('../cli/connections'); + const { proxy } = await connectToSingleStore(credentials); + + const customDefaults = getCustomDefaults(singlestoreSchema); + + let dbUrl: string; + + if ('url' in credentials) { + dbUrl = credentials.url; + } else { + dbUrl = + `singlestore://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}/${credentials.database}`; + } + + const dbHash = createHash('sha256').update(dbUrl).digest('hex'); + + return { + dbHash, + dialect: 'singlestore', + proxy, + customDefaults, + schema: singlestoreSchema, + relations, + schemaFiles, + }; +}; export const extractRelations = (tablesConfig: { tables: TablesRelationalConfig; @@ -345,6 +448,8 @@ export const extractRelations = (tablesConfig: { refSchema = mysqlTableConfig(refTable).schema; } else if (is(refTable, SQLiteTable)) { refSchema = undefined; + } else if (is(refTable, SingleStoreTable)) { + refSchema = singlestoreTableConfig(refTable).schema; } else { throw new Error('unsupported dialect'); } diff --git a/drizzle-kit/src/snapshotsDiffer.ts b/drizzle-kit/src/snapshotsDiffer.ts index 9ad2d9e32..6f27a2505 100644 --- a/drizzle-kit/src/snapshotsDiffer.ts +++ b/drizzle-kit/src/snapshotsDiffer.ts @@ -5,7 +5,6 @@ import { enum as enumType, literal, never, - number, object, record, string, @@ -36,12 +35,15 @@ import { JsonStatement, prepareAddCompositePrimaryKeyMySql, prepareAddCompositePrimaryKeyPg, + prepareAddCompositePrimaryKeySingleStore, prepareAddCompositePrimaryKeySqlite, prepareAddUniqueConstraintPg as prepareAddUniqueConstraint, prepareAddValuesToEnumJson, prepareAlterColumnsMysql, + prepareAlterColumnsSingleStore, prepareAlterCompositePrimaryKeyMySql, prepareAlterCompositePrimaryKeyPg, + prepareAlterCompositePrimaryKeySingleStore, prepareAlterCompositePrimaryKeySqlite, prepareAlterReferencesJson, prepareAlterSequenceJson, @@ -52,6 +54,7 @@ import { prepareCreateSequenceJson, prepareDeleteCompositePrimaryKeyMySql, prepareDeleteCompositePrimaryKeyPg, + prepareDeleteCompositePrimaryKeySingleStore, prepareDeleteCompositePrimaryKeySqlite, prepareDeleteSchemasJson as prepareDropSchemasJson, prepareDeleteUniqueConstraintPg as prepareDeleteUniqueConstraint, @@ -60,6 +63,8 @@ import { prepareDropReferencesJson, prepareDropSequenceJson, prepareDropTableJson, + prepareLibSQLCreateReferencesJson, + prepareLibSQLDropReferencesJson, prepareMoveEnumJson, prepareMoveSequenceJson, prepareMySqlCreateTableJson, @@ -71,6 +76,7 @@ import { prepareRenameSchemasJson, prepareRenameSequenceJson, prepareRenameTableJson, + prepareSingleStoreCreateTableJson, prepareSqliteAlterColumns, prepareSQLiteCreateTable, } from './jsonStatements'; @@ -78,8 +84,10 @@ import { import { Named, NamedWithSchema } from './cli/commands/migrate'; import { mapEntries, mapKeys, mapValues } from './global'; import { MySqlSchema, MySqlSchemaSquashed, MySqlSquasher } from './serializer/mysqlSchema'; -import { PgSchema, PgSchemaSquashed, PgSquasher, sequenceSchema, sequenceSquashed } from './serializer/pgSchema'; +import { PgSchema, PgSchemaSquashed, sequenceSquashed } from './serializer/pgSchema'; +import { SingleStoreSchema, SingleStoreSchemaSquashed, SingleStoreSquasher } from './serializer/singlestoreSchema'; import { SQLiteSchema, SQLiteSchemaSquashed, SQLiteSquasher } from './serializer/sqliteSchema'; +import { libSQLCombineStatements, sqliteCombineStatements } from './statementCombiner'; import { copy, prepareMigrationMeta } from './utils'; const makeChanged = (schema: T) => { @@ -259,6 +267,11 @@ export const diffResultSchemeMysql = object({ alteredEnums: never().array(), }); +export const diffResultSchemeSingleStore = object({ + alteredTablesWithColumns: alteredTableScheme.array(), + alteredEnums: never().array(), +}); + export const diffResultSchemeSQLite = object({ alteredTablesWithColumns: alteredTableScheme.array(), alteredEnums: never().array(), @@ -272,6 +285,7 @@ export type Table = TypeOf; export type AlteredTable = TypeOf; export type DiffResult = TypeOf; export type DiffResultMysql = TypeOf; +export type DiffResultSingleStore = TypeOf; export type DiffResultSQLite = TypeOf; export interface ResolverInput { @@ -1651,17 +1665,17 @@ export const applyMysqlSnapshotsDiff = async ( }; }; -export const applySqliteSnapshotsDiff = async ( - json1: SQLiteSchemaSquashed, - json2: SQLiteSchemaSquashed, +export const applySingleStoreSnapshotsDiff = async ( + json1: SingleStoreSchemaSquashed, + json2: SingleStoreSchemaSquashed, tablesResolver: ( input: ResolverInput, ) => Promise>, columnsResolver: ( input: ColumnsResolverInput, ) => Promise>, - prevFull: SQLiteSchema, - curFull: SQLiteSchema, + prevFull: SingleStoreSchema, + curFull: SingleStoreSchema, action?: 'push' | undefined, ): Promise<{ statements: JsonStatement[]; @@ -1674,12 +1688,44 @@ export const applySqliteSnapshotsDiff = async ( } | undefined; }> => { + // squash indexes and fks + + // squash uniqueIndexes and uniqueConstraint into constraints object + // it should be done for singlestore only because it has no diffs for it + for (const tableName in json1.tables) { + const table = json1.tables[tableName]; + for (const indexName in table.indexes) { + const index = SingleStoreSquasher.unsquashIdx(table.indexes[indexName]); + if (index.isUnique) { + table.uniqueConstraints[indexName] = SingleStoreSquasher.squashUnique({ + name: index.name, + columns: index.columns, + }); + delete json1.tables[tableName].indexes[index.name]; + } + } + } + + for (const tableName in json2.tables) { + const table = json2.tables[tableName]; + for (const indexName in table.indexes) { + const index = SingleStoreSquasher.unsquashIdx(table.indexes[indexName]); + if (index.isUnique) { + table.uniqueConstraints[indexName] = SingleStoreSquasher.squashUnique({ + name: index.name, + columns: index.columns, + }); + delete json2.tables[tableName].indexes[index.name]; + } + } + } + const tablesDiff = diffSchemasOrTables(json1.tables, json2.tables); const { created: createdTables, deleted: deletedTables, - renamed: renamedTables, + renamed: renamedTables, // renamed or moved } = await tablesResolver({ created: tablesDiff.added, deleted: tablesDiff.deleted, @@ -1693,7 +1739,6 @@ export const applySqliteSnapshotsDiff = async ( }); const res = diffColumns(tablesPatchedSnap1.tables, json2.tables); - const columnRenames = [] as { table: string; renames: { from: Column; to: Column }[]; @@ -1774,20 +1819,9 @@ export const applySqliteSnapshotsDiff = async ( const diffResult = applyJsonDiff(columnsPatchedSnap1, json2); - const typedResult = diffResultSchemeSQLite.parse(diffResult); - - // Map array of objects to map - const tablesMap: { - [key: string]: (typeof typedResult.alteredTablesWithColumns)[number]; - } = {}; - - typedResult.alteredTablesWithColumns.forEach((obj) => { - tablesMap[obj.name] = obj; - }); + const typedResult: DiffResultSingleStore = diffResultSchemeSingleStore.parse(diffResult); - const jsonCreateTables = createdTables.map((it) => { - return prepareSQLiteCreateTable(it, action); - }); + const jsonStatements: JsonStatement[] = []; const jsonCreateIndexesForCreatedTables = createdTables .map((it) => { @@ -1808,27 +1842,7 @@ export const applySqliteSnapshotsDiff = async ( return prepareRenameTableJson(it.from, it.to); }); - const jsonRenameColumnsStatements: JsonRenameColumnStatement[] = columnRenames - .map((it) => prepareRenameColumns(it.table, '', it.renames)) - .flat(); - - const jsonDropColumnsStatemets: JsonDropColumnStatement[] = columnDeletes - .map((it) => _prepareDropColumns(it.table, '', it.columns)) - .flat(); - - const jsonAddColumnsStatemets: JsonSqliteAddColumnStatement[] = columnCreates - .map((it) => { - return _prepareSqliteAddColumns( - it.table, - it.columns, - tablesMap[it.table] && tablesMap[it.table].addedForeignKeys - ? Object.values(tablesMap[it.table].addedForeignKeys) - : [], - ); - }) - .flat(); - - const allAltered = typedResult.alteredTablesWithColumns; + const alteredTables = typedResult.alteredTablesWithColumns; const jsonAddedCompositePKs: JsonCreateCompositePK[] = []; const jsonDeletedCompositePKs: JsonDeleteCompositePK[] = []; @@ -1838,44 +1852,60 @@ export const applySqliteSnapshotsDiff = async ( const jsonDeletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; const jsonAlteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; - allAltered.forEach((it) => { + const jsonRenameColumnsStatements: JsonRenameColumnStatement[] = columnRenames + .map((it) => prepareRenameColumns(it.table, '', it.renames)) + .flat(); + + const jsonAddColumnsStatemets: JsonAddColumnStatement[] = columnCreates + .map((it) => _prepareAddColumns(it.table, '', it.columns)) + .flat(); + + const jsonDropColumnsStatemets: JsonDropColumnStatement[] = columnDeletes + .map((it) => _prepareDropColumns(it.table, '', it.columns)) + .flat(); + + alteredTables.forEach((it) => { // This part is needed to make sure that same columns in a table are not triggered for change // there is a case where orm and kit are responsible for pk name generation and one of them is not sorting name // We double-check that pk with same set of columns are both in added and deleted diffs let addedColumns: string[] = []; for (const addedPkName of Object.keys(it.addedCompositePKs)) { const addedPkColumns = it.addedCompositePKs[addedPkName]; - addedColumns = SQLiteSquasher.unsquashPK(addedPkColumns); + addedColumns = SingleStoreSquasher.unsquashPK(addedPkColumns).columns; } let deletedColumns: string[] = []; for (const deletedPkName of Object.keys(it.deletedCompositePKs)) { const deletedPkColumns = it.deletedCompositePKs[deletedPkName]; - deletedColumns = SQLiteSquasher.unsquashPK(deletedPkColumns); + deletedColumns = SingleStoreSquasher.unsquashPK(deletedPkColumns).columns; } // Don't need to sort, but need to add tests for it // addedColumns.sort(); // deletedColumns.sort(); - const doPerformDeleteAndCreate = JSON.stringify(addedColumns) !== JSON.stringify(deletedColumns); let addedCompositePKs: JsonCreateCompositePK[] = []; let deletedCompositePKs: JsonDeleteCompositePK[] = []; let alteredCompositePKs: JsonAlterCompositePK[] = []; - if (doPerformDeleteAndCreate) { - addedCompositePKs = prepareAddCompositePrimaryKeySqlite( - it.name, - it.addedCompositePKs, - ); - deletedCompositePKs = prepareDeleteCompositePrimaryKeySqlite( - it.name, - it.deletedCompositePKs, - ); - } - alteredCompositePKs = prepareAlterCompositePrimaryKeySqlite( + + addedCompositePKs = prepareAddCompositePrimaryKeySingleStore( + it.name, + it.addedCompositePKs, + prevFull, + curFull, + ); + deletedCompositePKs = prepareDeleteCompositePrimaryKeySingleStore( + it.name, + it.deletedCompositePKs, + prevFull, + ); + // } + alteredCompositePKs = prepareAlterCompositePrimaryKeySingleStore( it.name, it.alteredCompositePKs, + prevFull, + curFull, ); // add logic for unique constraints @@ -1926,13 +1956,20 @@ export const applySqliteSnapshotsDiff = async ( }; }); - const jsonTableAlternations = allAltered + const jsonTableAlternations = alteredTables .map((it) => { - return prepareSqliteAlterColumns(it.name, it.schema, it.altered, json2); + return prepareAlterColumnsSingleStore( + it.name, + it.schema, + it.altered, + json1, + json2, + action, + ); }) .flat(); - const jsonCreateIndexesForAllAlteredTables = allAltered + const jsonCreateIndexesForAllAlteredTables = alteredTables .map((it) => { return prepareCreateIndexesJson( it.name, @@ -1943,7 +1980,7 @@ export const applySqliteSnapshotsDiff = async ( }) .flat(); - const jsonDropIndexesForAllAlteredTables = allAltered + const jsonDropIndexesForAllAlteredTables = alteredTables .map((it) => { return prepareDropIndexesJson( it.name, @@ -1953,7 +1990,7 @@ export const applySqliteSnapshotsDiff = async ( }) .flat(); - allAltered.forEach((it) => { + alteredTables.forEach((it) => { const droppedIndexes = Object.keys(it.alteredIndexes).reduce( (current, item: string) => { current[item] = it.alteredIndexes[item].__old; @@ -1970,57 +2007,27 @@ export const applySqliteSnapshotsDiff = async ( ); jsonCreateIndexesForAllAlteredTables.push( - ...prepareCreateIndexesJson( - it.name, - it.schema, - createdIndexes || {}, - curFull.internal, - ), + ...prepareCreateIndexesJson(it.name, it.schema, createdIndexes || {}), ); jsonDropIndexesForAllAlteredTables.push( ...prepareDropIndexesJson(it.name, it.schema, droppedIndexes || {}), ); }); - const jsonReferencesForAllAlteredTables: JsonReferenceStatement[] = allAltered - .map((it) => { - const forAdded = prepareCreateReferencesJson( - it.name, - it.schema, - it.addedForeignKeys, - ); - - const forAltered = prepareDropReferencesJson( - it.name, - it.schema, - it.deletedForeignKeys, - ); - - const alteredFKs = prepareAlterReferencesJson( - it.name, - it.schema, - it.alteredForeignKeys, - ); - - return [...forAdded, ...forAltered, ...alteredFKs]; - }) - .flat(); - - const jsonCreatedReferencesForAlteredTables = jsonReferencesForAllAlteredTables.filter( - (t) => t.type === 'create_reference', - ); - const jsonDroppedReferencesForAlteredTables = jsonReferencesForAllAlteredTables.filter( - (t) => t.type === 'delete_reference', - ); - - const jsonStatements: JsonStatement[] = []; - jsonStatements.push(...jsonCreateTables); + const jsonSingleStoreCreateTables = createdTables.map((it) => { + return prepareSingleStoreCreateTableJson( + it, + curFull as SingleStoreSchema, + curFull.internal, + ); + }); + jsonStatements.push(...jsonSingleStoreCreateTables); jsonStatements.push(...jsonDropTables); jsonStatements.push(...jsonRenameTables); jsonStatements.push(...jsonRenameColumnsStatements); - jsonStatements.push(...jsonDroppedReferencesForAlteredTables); + jsonStatements.push(...jsonDeletedUniqueConstraints); // Will need to drop indexes before changing any columns in table // Then should go column alternations and then index creation @@ -2029,12 +2036,15 @@ export const applySqliteSnapshotsDiff = async ( jsonStatements.push(...jsonDeletedCompositePKs); jsonStatements.push(...jsonTableAlternations); jsonStatements.push(...jsonAddedCompositePKs); + + jsonStatements.push(...jsonAddedUniqueConstraints); + jsonStatements.push(...jsonDeletedUniqueConstraints); + jsonStatements.push(...jsonAddColumnsStatemets); jsonStatements.push(...jsonCreateIndexesForCreatedTables); - jsonStatements.push(...jsonCreateIndexesForAllAlteredTables); - jsonStatements.push(...jsonCreatedReferencesForAlteredTables); + jsonStatements.push(...jsonCreateIndexesForAllAlteredTables); jsonStatements.push(...jsonDropColumnsStatemets); @@ -2042,9 +2052,11 @@ export const applySqliteSnapshotsDiff = async ( // jsonStatements.push(...jsonAddedCompositePKs); jsonStatements.push(...jsonAlteredCompositePKs); + jsonStatements.push(...jsonAddedUniqueConstraints); + jsonStatements.push(...jsonAlteredUniqueConstraints); - const sqlStatements = fromJson(jsonStatements, 'sqlite'); + const sqlStatements = fromJson(jsonStatements, 'singlestore'); const uniqueSqlStatements: string[] = []; sqlStatements.forEach((ss) => { @@ -2066,5 +2078,842 @@ export const applySqliteSnapshotsDiff = async ( }; }; +export const applySqliteSnapshotsDiff = async ( + json1: SQLiteSchemaSquashed, + json2: SQLiteSchemaSquashed, + tablesResolver: ( + input: ResolverInput
, + ) => Promise>, + columnsResolver: ( + input: ColumnsResolverInput, + ) => Promise>, + prevFull: SQLiteSchema, + curFull: SQLiteSchema, + action?: 'push' | undefined, +): Promise<{ + statements: JsonStatement[]; + sqlStatements: string[]; + _meta: + | { + schemas: {}; + tables: {}; + columns: {}; + } + | undefined; +}> => { + const tablesDiff = diffSchemasOrTables(json1.tables, json2.tables); + + const { + created: createdTables, + deleted: deletedTables, + renamed: renamedTables, + } = await tablesResolver({ + created: tablesDiff.added, + deleted: tablesDiff.deleted, + }); + + const tablesPatchedSnap1 = copy(json1); + tablesPatchedSnap1.tables = mapEntries(tablesPatchedSnap1.tables, (_, it) => { + const { name } = nameChangeFor(it, renamedTables); + it.name = name; + return [name, it]; + }); + + const res = diffColumns(tablesPatchedSnap1.tables, json2.tables); + + const columnRenames = [] as { + table: string; + renames: { from: Column; to: Column }[]; + }[]; + + const columnCreates = [] as { + table: string; + columns: Column[]; + }[]; + + const columnDeletes = [] as { + table: string; + columns: Column[]; + }[]; + + for (let entry of Object.values(res)) { + const { renamed, created, deleted } = await columnsResolver({ + tableName: entry.name, + schema: entry.schema, + deleted: entry.columns.deleted, + created: entry.columns.added, + }); + + if (created.length > 0) { + columnCreates.push({ + table: entry.name, + columns: created, + }); + } + + if (deleted.length > 0) { + columnDeletes.push({ + table: entry.name, + columns: deleted, + }); + } + + if (renamed.length > 0) { + columnRenames.push({ + table: entry.name, + renames: renamed, + }); + } + } + + const columnRenamesDict = columnRenames.reduce( + (acc, it) => { + acc[it.table] = it.renames; + return acc; + }, + {} as Record< + string, + { + from: Named; + to: Named; + }[] + >, + ); + + const columnsPatchedSnap1 = copy(tablesPatchedSnap1); + columnsPatchedSnap1.tables = mapEntries( + columnsPatchedSnap1.tables, + (tableKey, tableValue) => { + const patchedColumns = mapKeys( + tableValue.columns, + (columnKey, column) => { + const rens = columnRenamesDict[tableValue.name] || []; + const newName = columnChangeFor(columnKey, rens); + column.name = newName; + return newName; + }, + ); + + tableValue.columns = patchedColumns; + return [tableKey, tableValue]; + }, + ); + + const diffResult = applyJsonDiff(columnsPatchedSnap1, json2); + + const typedResult = diffResultSchemeSQLite.parse(diffResult); + + // Map array of objects to map + const tablesMap: { + [key: string]: (typeof typedResult.alteredTablesWithColumns)[number]; + } = {}; + + typedResult.alteredTablesWithColumns.forEach((obj) => { + tablesMap[obj.name] = obj; + }); + + const jsonCreateTables = createdTables.map((it) => { + return prepareSQLiteCreateTable(it, action); + }); + + const jsonCreateIndexesForCreatedTables = createdTables + .map((it) => { + return prepareCreateIndexesJson( + it.name, + it.schema, + it.indexes, + curFull.internal, + ); + }) + .flat(); + + const jsonDropTables = deletedTables.map((it) => { + return prepareDropTableJson(it); + }); + + const jsonRenameTables = renamedTables.map((it) => { + return prepareRenameTableJson(it.from, it.to); + }); + + const jsonRenameColumnsStatements: JsonRenameColumnStatement[] = columnRenames + .map((it) => prepareRenameColumns(it.table, '', it.renames)) + .flat(); + + const jsonDropColumnsStatemets: JsonDropColumnStatement[] = columnDeletes + .map((it) => _prepareDropColumns(it.table, '', it.columns)) + .flat(); + + const jsonAddColumnsStatemets: JsonSqliteAddColumnStatement[] = columnCreates + .map((it) => { + return _prepareSqliteAddColumns( + it.table, + it.columns, + tablesMap[it.table] && tablesMap[it.table].addedForeignKeys + ? Object.values(tablesMap[it.table].addedForeignKeys) + : [], + ); + }) + .flat(); + + const allAltered = typedResult.alteredTablesWithColumns; + + const jsonAddedCompositePKs: JsonCreateCompositePK[] = []; + const jsonDeletedCompositePKs: JsonDeleteCompositePK[] = []; + const jsonAlteredCompositePKs: JsonAlterCompositePK[] = []; + + const jsonAddedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + const jsonDeletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + const jsonAlteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + + allAltered.forEach((it) => { + // This part is needed to make sure that same columns in a table are not triggered for change + // there is a case where orm and kit are responsible for pk name generation and one of them is not sorting name + // We double-check that pk with same set of columns are both in added and deleted diffs + let addedColumns: string[] = []; + for (const addedPkName of Object.keys(it.addedCompositePKs)) { + const addedPkColumns = it.addedCompositePKs[addedPkName]; + addedColumns = SQLiteSquasher.unsquashPK(addedPkColumns); + } + + let deletedColumns: string[] = []; + for (const deletedPkName of Object.keys(it.deletedCompositePKs)) { + const deletedPkColumns = it.deletedCompositePKs[deletedPkName]; + deletedColumns = SQLiteSquasher.unsquashPK(deletedPkColumns); + } + + // Don't need to sort, but need to add tests for it + // addedColumns.sort(); + // deletedColumns.sort(); + + const doPerformDeleteAndCreate = JSON.stringify(addedColumns) !== JSON.stringify(deletedColumns); + + let addedCompositePKs: JsonCreateCompositePK[] = []; + let deletedCompositePKs: JsonDeleteCompositePK[] = []; + let alteredCompositePKs: JsonAlterCompositePK[] = []; + if (doPerformDeleteAndCreate) { + addedCompositePKs = prepareAddCompositePrimaryKeySqlite( + it.name, + it.addedCompositePKs, + ); + deletedCompositePKs = prepareDeleteCompositePrimaryKeySqlite( + it.name, + it.deletedCompositePKs, + ); + } + alteredCompositePKs = prepareAlterCompositePrimaryKeySqlite( + it.name, + it.alteredCompositePKs, + ); + + // add logic for unique constraints + let addedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + + addedUniqueConstraints = prepareAddUniqueConstraint( + it.name, + it.schema, + it.addedUniqueConstraints, + ); + deletedUniqueConstraints = prepareDeleteUniqueConstraint( + it.name, + it.schema, + it.deletedUniqueConstraints, + ); + if (it.alteredUniqueConstraints) { + const added: Record = {}; + const deleted: Record = {}; + for (const k of Object.keys(it.alteredUniqueConstraints)) { + added[k] = it.alteredUniqueConstraints[k].__new; + deleted[k] = it.alteredUniqueConstraints[k].__old; + } + addedUniqueConstraints.push( + ...prepareAddUniqueConstraint(it.name, it.schema, added), + ); + deletedUniqueConstraints.push( + ...prepareDeleteUniqueConstraint(it.name, it.schema, deleted), + ); + } + + jsonAddedCompositePKs.push(...addedCompositePKs); + jsonDeletedCompositePKs.push(...deletedCompositePKs); + jsonAlteredCompositePKs.push(...alteredCompositePKs); + + jsonAddedUniqueConstraints.push(...addedUniqueConstraints); + jsonDeletedUniqueConstraints.push(...deletedUniqueConstraints); + jsonAlteredUniqueConstraints.push(...alteredUniqueConstraints); + }); + + const rColumns = jsonRenameColumnsStatements.map((it) => { + const tableName = it.tableName; + const schema = it.schema; + return { + from: { schema, table: tableName, column: it.oldColumnName }, + to: { schema, table: tableName, column: it.newColumnName }, + }; + }); + + const jsonTableAlternations = allAltered + .map((it) => { + return prepareSqliteAlterColumns(it.name, it.schema, it.altered, json2); + }) + .flat(); + + const jsonCreateIndexesForAllAlteredTables = allAltered + .map((it) => { + return prepareCreateIndexesJson( + it.name, + it.schema, + it.addedIndexes || {}, + curFull.internal, + ); + }) + .flat(); + + const jsonDropIndexesForAllAlteredTables = allAltered + .map((it) => { + return prepareDropIndexesJson( + it.name, + it.schema, + it.deletedIndexes || {}, + ); + }) + .flat(); + + allAltered.forEach((it) => { + const droppedIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__old; + return current; + }, + {} as Record, + ); + const createdIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__new; + return current; + }, + {} as Record, + ); + + jsonCreateIndexesForAllAlteredTables.push( + ...prepareCreateIndexesJson( + it.name, + it.schema, + createdIndexes || {}, + curFull.internal, + ), + ); + jsonDropIndexesForAllAlteredTables.push( + ...prepareDropIndexesJson(it.name, it.schema, droppedIndexes || {}), + ); + }); + + const jsonReferencesForAllAlteredTables: JsonReferenceStatement[] = allAltered + .map((it) => { + const forAdded = prepareCreateReferencesJson( + it.name, + it.schema, + it.addedForeignKeys, + ); + + const forAltered = prepareDropReferencesJson( + it.name, + it.schema, + it.deletedForeignKeys, + ); + + const alteredFKs = prepareAlterReferencesJson( + it.name, + it.schema, + it.alteredForeignKeys, + ); + + return [...forAdded, ...forAltered, ...alteredFKs]; + }) + .flat(); + + const jsonCreatedReferencesForAlteredTables = jsonReferencesForAllAlteredTables.filter( + (t) => t.type === 'create_reference', + ); + const jsonDroppedReferencesForAlteredTables = jsonReferencesForAllAlteredTables.filter( + (t) => t.type === 'delete_reference', + ); + + const jsonStatements: JsonStatement[] = []; + jsonStatements.push(...jsonCreateTables); + + jsonStatements.push(...jsonDropTables); + jsonStatements.push(...jsonRenameTables); + jsonStatements.push(...jsonRenameColumnsStatements); + + jsonStatements.push(...jsonDroppedReferencesForAlteredTables); + + // Will need to drop indexes before changing any columns in table + // Then should go column alternations and then index creation + jsonStatements.push(...jsonDropIndexesForAllAlteredTables); + + jsonStatements.push(...jsonDeletedCompositePKs); + jsonStatements.push(...jsonTableAlternations); + jsonStatements.push(...jsonAddedCompositePKs); + jsonStatements.push(...jsonAddColumnsStatemets); + + jsonStatements.push(...jsonCreateIndexesForCreatedTables); + jsonStatements.push(...jsonCreateIndexesForAllAlteredTables); + + jsonStatements.push(...jsonCreatedReferencesForAlteredTables); + + jsonStatements.push(...jsonDropColumnsStatemets); + + // jsonStatements.push(...jsonDeletedCompositePKs); + // jsonStatements.push(...jsonAddedCompositePKs); + jsonStatements.push(...jsonAlteredCompositePKs); + + jsonStatements.push(...jsonAlteredUniqueConstraints); + + const combinedJsonStatements = sqliteCombineStatements(jsonStatements, json2, action); + const sqlStatements = fromJson(combinedJsonStatements, 'sqlite'); + + const uniqueSqlStatements: string[] = []; + sqlStatements.forEach((ss) => { + if (!uniqueSqlStatements.includes(ss)) { + uniqueSqlStatements.push(ss); + } + }); + + const rTables = renamedTables.map((it) => { + return { from: it.from, to: it.to }; + }); + + const _meta = prepareMigrationMeta([], rTables, rColumns); + + return { + statements: combinedJsonStatements, + sqlStatements: uniqueSqlStatements, + _meta, + }; +}; + +export const applyLibSQLSnapshotsDiff = async ( + json1: SQLiteSchemaSquashed, + json2: SQLiteSchemaSquashed, + tablesResolver: ( + input: ResolverInput
, + ) => Promise>, + columnsResolver: ( + input: ColumnsResolverInput, + ) => Promise>, + prevFull: SQLiteSchema, + curFull: SQLiteSchema, + action?: 'push', +): Promise<{ + statements: JsonStatement[]; + sqlStatements: string[]; + _meta: + | { + schemas: {}; + tables: {}; + columns: {}; + } + | undefined; +}> => { + const tablesDiff = diffSchemasOrTables(json1.tables, json2.tables); + const { + created: createdTables, + deleted: deletedTables, + renamed: renamedTables, + } = await tablesResolver({ + created: tablesDiff.added, + deleted: tablesDiff.deleted, + }); + + const tablesPatchedSnap1 = copy(json1); + tablesPatchedSnap1.tables = mapEntries(tablesPatchedSnap1.tables, (_, it) => { + const { name } = nameChangeFor(it, renamedTables); + it.name = name; + return [name, it]; + }); + + const res = diffColumns(tablesPatchedSnap1.tables, json2.tables); + + const columnRenames = [] as { + table: string; + renames: { from: Column; to: Column }[]; + }[]; + + const columnCreates = [] as { + table: string; + columns: Column[]; + }[]; + + const columnDeletes = [] as { + table: string; + columns: Column[]; + }[]; + + for (let entry of Object.values(res)) { + const { renamed, created, deleted } = await columnsResolver({ + tableName: entry.name, + schema: entry.schema, + deleted: entry.columns.deleted, + created: entry.columns.added, + }); + + if (created.length > 0) { + columnCreates.push({ + table: entry.name, + columns: created, + }); + } + + if (deleted.length > 0) { + columnDeletes.push({ + table: entry.name, + columns: deleted, + }); + } + + if (renamed.length > 0) { + columnRenames.push({ + table: entry.name, + renames: renamed, + }); + } + } + + const columnRenamesDict = columnRenames.reduce( + (acc, it) => { + acc[it.table] = it.renames; + return acc; + }, + {} as Record< + string, + { + from: Named; + to: Named; + }[] + >, + ); + + const columnsPatchedSnap1 = copy(tablesPatchedSnap1); + columnsPatchedSnap1.tables = mapEntries( + columnsPatchedSnap1.tables, + (tableKey, tableValue) => { + const patchedColumns = mapKeys( + tableValue.columns, + (columnKey, column) => { + const rens = columnRenamesDict[tableValue.name] || []; + const newName = columnChangeFor(columnKey, rens); + column.name = newName; + return newName; + }, + ); + + tableValue.columns = patchedColumns; + return [tableKey, tableValue]; + }, + ); + + const diffResult = applyJsonDiff(columnsPatchedSnap1, json2); + + const typedResult = diffResultSchemeSQLite.parse(diffResult); + + // Map array of objects to map + const tablesMap: { + [key: string]: (typeof typedResult.alteredTablesWithColumns)[number]; + } = {}; + + typedResult.alteredTablesWithColumns.forEach((obj) => { + tablesMap[obj.name] = obj; + }); + + const jsonCreateTables = createdTables.map((it) => { + return prepareSQLiteCreateTable(it, action); + }); + + const jsonCreateIndexesForCreatedTables = createdTables + .map((it) => { + return prepareCreateIndexesJson( + it.name, + it.schema, + it.indexes, + curFull.internal, + ); + }) + .flat(); + + const jsonDropTables = deletedTables.map((it) => { + return prepareDropTableJson(it); + }); + + const jsonRenameTables = renamedTables.map((it) => { + return prepareRenameTableJson(it.from, it.to); + }); + + const jsonRenameColumnsStatements: JsonRenameColumnStatement[] = columnRenames + .map((it) => prepareRenameColumns(it.table, '', it.renames)) + .flat(); + + const jsonDropColumnsStatemets: JsonDropColumnStatement[] = columnDeletes + .map((it) => _prepareDropColumns(it.table, '', it.columns)) + .flat(); + + const jsonAddColumnsStatemets: JsonSqliteAddColumnStatement[] = columnCreates + .map((it) => { + return _prepareSqliteAddColumns( + it.table, + it.columns, + tablesMap[it.table] && tablesMap[it.table].addedForeignKeys + ? Object.values(tablesMap[it.table].addedForeignKeys) + : [], + ); + }) + .flat(); + + const rColumns = jsonRenameColumnsStatements.map((it) => { + const tableName = it.tableName; + const schema = it.schema; + return { + from: { schema, table: tableName, column: it.oldColumnName }, + to: { schema, table: tableName, column: it.newColumnName }, + }; + }); + + const rTables = renamedTables.map((it) => { + return { from: it.from, to: it.to }; + }); + + const _meta = prepareMigrationMeta([], rTables, rColumns); + + const allAltered = typedResult.alteredTablesWithColumns; + + const jsonAddedCompositePKs: JsonCreateCompositePK[] = []; + const jsonDeletedCompositePKs: JsonDeleteCompositePK[] = []; + const jsonAlteredCompositePKs: JsonAlterCompositePK[] = []; + + const jsonAddedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + const jsonDeletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + const jsonAlteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + + allAltered.forEach((it) => { + // This part is needed to make sure that same columns in a table are not triggered for change + // there is a case where orm and kit are responsible for pk name generation and one of them is not sorting name + // We double-check that pk with same set of columns are both in added and deleted diffs + let addedColumns: string[] = []; + for (const addedPkName of Object.keys(it.addedCompositePKs)) { + const addedPkColumns = it.addedCompositePKs[addedPkName]; + addedColumns = SQLiteSquasher.unsquashPK(addedPkColumns); + } + + let deletedColumns: string[] = []; + for (const deletedPkName of Object.keys(it.deletedCompositePKs)) { + const deletedPkColumns = it.deletedCompositePKs[deletedPkName]; + deletedColumns = SQLiteSquasher.unsquashPK(deletedPkColumns); + } + + // Don't need to sort, but need to add tests for it + // addedColumns.sort(); + // deletedColumns.sort(); + + const doPerformDeleteAndCreate = JSON.stringify(addedColumns) !== JSON.stringify(deletedColumns); + + let addedCompositePKs: JsonCreateCompositePK[] = []; + let deletedCompositePKs: JsonDeleteCompositePK[] = []; + let alteredCompositePKs: JsonAlterCompositePK[] = []; + if (doPerformDeleteAndCreate) { + addedCompositePKs = prepareAddCompositePrimaryKeySqlite( + it.name, + it.addedCompositePKs, + ); + deletedCompositePKs = prepareDeleteCompositePrimaryKeySqlite( + it.name, + it.deletedCompositePKs, + ); + } + alteredCompositePKs = prepareAlterCompositePrimaryKeySqlite( + it.name, + it.alteredCompositePKs, + ); + + // add logic for unique constraints + let addedUniqueConstraints: JsonCreateUniqueConstraint[] = []; + let deletedUniqueConstraints: JsonDeleteUniqueConstraint[] = []; + let alteredUniqueConstraints: JsonAlterUniqueConstraint[] = []; + + addedUniqueConstraints = prepareAddUniqueConstraint( + it.name, + it.schema, + it.addedUniqueConstraints, + ); + + deletedUniqueConstraints = prepareDeleteUniqueConstraint( + it.name, + it.schema, + it.deletedUniqueConstraints, + ); + if (it.alteredUniqueConstraints) { + const added: Record = {}; + const deleted: Record = {}; + for (const k of Object.keys(it.alteredUniqueConstraints)) { + added[k] = it.alteredUniqueConstraints[k].__new; + deleted[k] = it.alteredUniqueConstraints[k].__old; + } + addedUniqueConstraints.push( + ...prepareAddUniqueConstraint(it.name, it.schema, added), + ); + deletedUniqueConstraints.push( + ...prepareDeleteUniqueConstraint(it.name, it.schema, deleted), + ); + } + + jsonAddedCompositePKs.push(...addedCompositePKs); + jsonDeletedCompositePKs.push(...deletedCompositePKs); + jsonAlteredCompositePKs.push(...alteredCompositePKs); + + jsonAddedUniqueConstraints.push(...addedUniqueConstraints); + jsonDeletedUniqueConstraints.push(...deletedUniqueConstraints); + jsonAlteredUniqueConstraints.push(...alteredUniqueConstraints); + }); + + const jsonTableAlternations = allAltered + .map((it) => { + return prepareSqliteAlterColumns(it.name, it.schema, it.altered, json2); + }) + .flat(); + + const jsonCreateIndexesForAllAlteredTables = allAltered + .map((it) => { + return prepareCreateIndexesJson( + it.name, + it.schema, + it.addedIndexes || {}, + curFull.internal, + ); + }) + .flat(); + + const jsonDropIndexesForAllAlteredTables = allAltered + .map((it) => { + return prepareDropIndexesJson( + it.name, + it.schema, + it.deletedIndexes || {}, + ); + }) + .flat(); + + allAltered.forEach((it) => { + const droppedIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__old; + return current; + }, + {} as Record, + ); + const createdIndexes = Object.keys(it.alteredIndexes).reduce( + (current, item: string) => { + current[item] = it.alteredIndexes[item].__new; + return current; + }, + {} as Record, + ); + + jsonCreateIndexesForAllAlteredTables.push( + ...prepareCreateIndexesJson( + it.name, + it.schema, + createdIndexes || {}, + curFull.internal, + ), + ); + jsonDropIndexesForAllAlteredTables.push( + ...prepareDropIndexesJson(it.name, it.schema, droppedIndexes || {}), + ); + }); + + const jsonReferencesForAllAlteredTables: JsonReferenceStatement[] = allAltered + .map((it) => { + const forAdded = prepareLibSQLCreateReferencesJson( + it.name, + it.schema, + it.addedForeignKeys, + json2, + action, + ); + + const forAltered = prepareLibSQLDropReferencesJson( + it.name, + it.schema, + it.deletedForeignKeys, + json2, + _meta, + action, + ); + + const alteredFKs = prepareAlterReferencesJson(it.name, it.schema, it.alteredForeignKeys); + + return [...forAdded, ...forAltered, ...alteredFKs]; + }) + .flat(); + + const jsonCreatedReferencesForAlteredTables = jsonReferencesForAllAlteredTables.filter( + (t) => t.type === 'create_reference', + ); + const jsonDroppedReferencesForAlteredTables = jsonReferencesForAllAlteredTables.filter( + (t) => t.type === 'delete_reference', + ); + + const jsonStatements: JsonStatement[] = []; + jsonStatements.push(...jsonCreateTables); + + jsonStatements.push(...jsonDropTables); + jsonStatements.push(...jsonRenameTables); + jsonStatements.push(...jsonRenameColumnsStatements); + + jsonStatements.push(...jsonDroppedReferencesForAlteredTables); + + // Will need to drop indexes before changing any columns in table + // Then should go column alternations and then index creation + jsonStatements.push(...jsonDropIndexesForAllAlteredTables); + + jsonStatements.push(...jsonDeletedCompositePKs); + jsonStatements.push(...jsonTableAlternations); + jsonStatements.push(...jsonAddedCompositePKs); + jsonStatements.push(...jsonAddColumnsStatemets); + + jsonStatements.push(...jsonCreateIndexesForCreatedTables); + jsonStatements.push(...jsonCreateIndexesForAllAlteredTables); + + jsonStatements.push(...jsonCreatedReferencesForAlteredTables); + + jsonStatements.push(...jsonDropColumnsStatemets); + + jsonStatements.push(...jsonAlteredCompositePKs); + + jsonStatements.push(...jsonAlteredUniqueConstraints); + + const combinedJsonStatements = libSQLCombineStatements(jsonStatements, json2, action); + + const sqlStatements = fromJson( + combinedJsonStatements, + 'turso', + action, + json2, + ); + + const uniqueSqlStatements: string[] = []; + sqlStatements.forEach((ss) => { + if (!uniqueSqlStatements.includes(ss)) { + uniqueSqlStatements.push(ss); + } + }); + + return { + statements: combinedJsonStatements, + sqlStatements: uniqueSqlStatements, + _meta, + }; +}; + // explicitely ask if tables were renamed, if yes - add those to altered tables, otherwise - deleted // double check if user wants to delete particular table and warn him on data loss diff --git a/drizzle-kit/src/sqlgenerator.ts b/drizzle-kit/src/sqlgenerator.ts index ec1a2d69e..f1e783a50 100644 --- a/drizzle-kit/src/sqlgenerator.ts +++ b/drizzle-kit/src/sqlgenerator.ts @@ -25,7 +25,6 @@ import { JsonAlterTableRemoveFromSchema, JsonAlterTableSetNewSchema, JsonAlterTableSetSchema, - JsonAlterUniqueConstraint, JsonCreateCompositePK, JsonCreateEnumStatement, JsonCreateIndexStatement, @@ -43,6 +42,7 @@ import { JsonDropTableStatement, JsonMoveSequenceStatement, JsonPgCreateIndexStatement, + JsonRecreateTableStatement, JsonRenameColumnStatement, JsonRenameSchema, JsonRenameSequenceStatement, @@ -54,7 +54,8 @@ import { import { Dialect } from './schemaValidator'; import { MySqlSquasher } from './serializer/mysqlSchema'; import { PgSquasher } from './serializer/pgSchema'; -import { SQLiteSquasher } from './serializer/sqliteSchema'; +import { SingleStoreSquasher } from './serializer/singlestoreSchema'; +import { SQLiteSchemaSquashed, SQLiteSquasher } from './serializer/sqliteSchema'; export const pgNativeTypes = new Set([ 'uuid', @@ -127,8 +128,15 @@ const isPgNativeType = (it: string) => { }; abstract class Convertor { - abstract can(statement: JsonStatement, dialect: Dialect): boolean; - abstract convert(statement: JsonStatement): string | string[]; + abstract can( + statement: JsonStatement, + dialect: Dialect, + ): boolean; + abstract convert( + statement: JsonStatement, + json2?: SQLiteSchemaSquashed, + action?: 'push', + ): string | string[]; } class PgCreateTableConvertor extends Convertor { @@ -304,10 +312,85 @@ class MySqlCreateTableConvertor extends Convertor { return statement; } } +class SingleStoreCreateTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_table' && dialect === 'singlestore'; + } + + convert(st: JsonCreateTableStatement) { + const { + tableName, + columns, + schema, + compositePKs, + uniqueConstraints, + internals, + } = st; + + let statement = ''; + statement += `CREATE TABLE \`${tableName}\` (\n`; + for (let i = 0; i < columns.length; i++) { + const column = columns[i]; + + const primaryKeyStatement = column.primaryKey ? ' PRIMARY KEY' : ''; + const notNullStatement = column.notNull ? ' NOT NULL' : ''; + const defaultStatement = column.default !== undefined ? ` DEFAULT ${column.default}` : ''; + + const onUpdateStatement = column.onUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + + const autoincrementStatement = column.autoincrement + ? ' AUTO_INCREMENT' + : ''; + + const generatedStatement = column.generated + ? ` GENERATED ALWAYS AS (${column.generated?.as}) ${column.generated?.type.toUpperCase()}` + : ''; + + statement += '\t' + + `\`${column.name}\` ${column.type}${autoincrementStatement}${primaryKeyStatement}${notNullStatement}${defaultStatement}${onUpdateStatement}${generatedStatement}`; + statement += i === columns.length - 1 ? '' : ',\n'; + } + + if (typeof compositePKs !== 'undefined' && compositePKs.length > 0) { + statement += ',\n'; + const compositePK = SingleStoreSquasher.unsquashPK(compositePKs[0]); + statement += `\tCONSTRAINT \`${st.compositePkName}\` PRIMARY KEY(\`${compositePK.columns.join(`\`,\``)}\`)`; + } + + if ( + typeof uniqueConstraints !== 'undefined' + && uniqueConstraints.length > 0 + ) { + for (const uniqueConstraint of uniqueConstraints) { + statement += ',\n'; + const unsquashedUnique = SingleStoreSquasher.unsquashUnique(uniqueConstraint); + + const uniqueString = unsquashedUnique.columns + .map((it) => { + return internals?.indexes + ? internals?.indexes[unsquashedUnique.name]?.columns[it] + ?.isExpression + ? it + : `\`${it}\`` + : `\`${it}\``; + }) + .join(','); + + statement += `\tCONSTRAINT \`${unsquashedUnique.name}\` UNIQUE(${uniqueString})`; + } + } + + statement += `\n);`; + statement += `\n`; + return statement; + } +} export class SQLiteCreateTableConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'sqlite_create_table' && dialect === 'sqlite'; + return statement.type === 'sqlite_create_table' && (dialect === 'sqlite' || dialect === 'turso'); } convert(st: JsonSqliteCreateTableStatement) { @@ -589,6 +672,29 @@ class MySQLAlterTableDropUniqueConstraintConvertor extends Convertor { } } +class SingleStoreAlterTableAddUniqueConstraintConvertor extends Convertor { + can(statement: JsonCreateUniqueConstraint, dialect: Dialect): boolean { + return statement.type === 'create_unique_constraint' && dialect === 'singlestore'; + } + convert(statement: JsonCreateUniqueConstraint): string { + const unsquashed = SingleStoreSquasher.unsquashUnique(statement.data); + + return `ALTER TABLE \`${statement.tableName}\` ADD CONSTRAINT \`${unsquashed.name}\` UNIQUE(\`${ + unsquashed.columns.join('`,`') + }\`);`; + } +} +class SingleStoreAlterTableDropUniqueConstraintConvertor extends Convertor { + can(statement: JsonDeleteUniqueConstraint, dialect: Dialect): boolean { + return statement.type === 'delete_unique_constraint' && dialect === 'singlestore'; + } + convert(statement: JsonDeleteUniqueConstraint): string { + const unsquashed = SingleStoreSquasher.unsquashUnique(statement.data); + + return `ALTER TABLE \`${statement.tableName}\` DROP INDEX \`${unsquashed.name}\`;`; + } +} + class SQLiteAlterTableAddUniqueConstraintConvertor extends Convertor { can(statement: JsonCreateUniqueConstraint, dialect: Dialect): boolean { return ( @@ -777,9 +883,20 @@ class MySQLDropTableConvertor extends Convertor { } } +class SingleStoreDropTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_table' && dialect === 'singlestore'; + } + + convert(statement: JsonDropTableStatement) { + const { tableName } = statement; + return `DROP TABLE \`${tableName}\`;`; + } +} + export class SQLiteDropTableConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'drop_table' && dialect === 'sqlite'; + return statement.type === 'drop_table' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonDropTableStatement) { @@ -805,7 +922,7 @@ class PgRenameTableConvertor extends Convertor { export class SqliteRenameTableConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'rename_table' && dialect === 'sqlite'; + return statement.type === 'rename_table' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonRenameTableStatement) { @@ -825,6 +942,17 @@ class MySqlRenameTableConvertor extends Convertor { } } +class SingleStoreRenameTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'rename_table' && dialect === 'singlestore'; + } + + convert(statement: JsonRenameTableStatement) { + const { tableNameFrom, tableNameTo } = statement; + return `RENAME TABLE \`${tableNameFrom}\` TO \`${tableNameTo}\`;`; + } +} + class PgAlterTableRenameColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -856,10 +984,10 @@ class MySqlAlterTableRenameColumnConvertor extends Convertor { } } -class SQLiteAlterTableRenameColumnConvertor extends Convertor { +class SingleStoreAlterTableRenameColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( - statement.type === 'alter_table_rename_column' && dialect === 'sqlite' + statement.type === 'alter_table_rename_column' && dialect === 'singlestore' ); } @@ -869,6 +997,19 @@ class SQLiteAlterTableRenameColumnConvertor extends Convertor { } } +class SQLiteAlterTableRenameColumnConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_rename_column' && (dialect === 'sqlite' || dialect === 'turso') + ); + } + + convert(statement: JsonRenameColumnStatement) { + const { tableName, oldColumnName, newColumnName } = statement; + return `ALTER TABLE \`${tableName}\` RENAME COLUMN "${oldColumnName}" TO "${newColumnName}";`; + } +} + class PgAlterTableDropColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -898,9 +1039,20 @@ class MySqlAlterTableDropColumnConvertor extends Convertor { } } +class SingleStoreAlterTableDropColumnConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_table_drop_column' && dialect === 'singlestore'; + } + + convert(statement: JsonDropColumnStatement) { + const { tableName, columnName } = statement; + return `ALTER TABLE \`${tableName}\` DROP COLUMN \`${columnName}\`;`; + } +} + class SQLiteAlterTableDropColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'alter_table_drop_column' && dialect === 'sqlite'; + return statement.type === 'alter_table_drop_column' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonDropColumnStatement) { @@ -1007,10 +1159,41 @@ class MySqlAlterTableAddColumnConvertor extends Convertor { } } +class SingleStoreAlterTableAddColumnConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_table_add_column' && dialect === 'singlestore'; + } + + convert(statement: JsonAddColumnStatement) { + const { tableName, column } = statement; + const { + name, + type, + notNull, + primaryKey, + autoincrement, + onUpdate, + generated, + } = column; + + const defaultStatement = `${column.default !== undefined ? ` DEFAULT ${column.default}` : ''}`; + const notNullStatement = `${notNull ? ' NOT NULL' : ''}`; + const primaryKeyStatement = `${primaryKey ? ' PRIMARY KEY' : ''}`; + const autoincrementStatement = `${autoincrement ? ' AUTO_INCREMENT' : ''}`; + const onUpdateStatement = `${onUpdate ? ' ON UPDATE CURRENT_TIMESTAMP' : ''}`; + + const generatedStatement = generated + ? ` GENERATED ALWAYS AS (${generated?.as}) ${generated?.type.toUpperCase()}` + : ''; + + return `ALTER TABLE \`${tableName}\` ADD \`${name}\` ${type}${primaryKeyStatement}${autoincrementStatement}${defaultStatement}${notNullStatement}${onUpdateStatement}${generatedStatement};`; + } +} + export class SQLiteAlterTableAddColumnConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( - statement.type === 'sqlite_alter_table_add_column' && dialect === 'sqlite' + statement.type === 'sqlite_alter_table_add_column' && (dialect === 'sqlite' || dialect === 'turso') ); } @@ -1057,26 +1240,6 @@ class PgAlterTableAlterColumnSetTypeConvertor extends Convertor { } } -class SQLiteAlterTableAlterColumnSetTypeConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_type' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnTypeStatement) { - return ( - '/*\n SQLite does not support "Changing existing column type" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class PgAlterTableAlterColumnSetDefaultConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -1096,26 +1259,6 @@ class PgAlterTableAlterColumnSetDefaultConvertor extends Convertor { } } -class SqliteAlterTableAlterColumnSetDefaultConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_default' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnSetDefaultStatement) { - return ( - '/*\n SQLite does not support "Set default to column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class PgAlterTableAlterColumnDropDefaultConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -1255,7 +1398,7 @@ class SqliteAlterTableAlterColumnDropGeneratedConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_drop_generated' - && dialect === 'sqlite' + && (dialect === 'sqlite' || dialect === 'turso') ); } @@ -1304,7 +1447,7 @@ class SqliteAlterTableAlterColumnSetExpressionConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_set_generated' - && dialect === 'sqlite' + && (dialect === 'sqlite' || dialect === 'turso') ); } @@ -1353,7 +1496,7 @@ class SqliteAlterTableAlterColumnAlterGeneratedConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( statement.type === 'alter_table_alter_column_alter_generated' - && dialect === 'sqlite' + && (dialect === 'sqlite' || dialect === 'turso') ); } @@ -1500,6 +1643,119 @@ class MySqlAlterTableDropPk extends Convertor { } } +type LibSQLModifyColumnStatement = + | JsonAlterColumnTypeStatement + | JsonAlterColumnDropNotNullStatement + | JsonAlterColumnSetNotNullStatement + | JsonAlterColumnSetDefaultStatement + | JsonAlterColumnDropDefaultStatement; + +export class LibSQLModifyColumn extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + (statement.type === 'alter_table_alter_column_set_type' + || statement.type === 'alter_table_alter_column_drop_notnull' + || statement.type === 'alter_table_alter_column_set_notnull' + || statement.type === 'alter_table_alter_column_set_default' + || statement.type === 'alter_table_alter_column_drop_default') + && dialect === 'turso' + ); + } + + convert(statement: LibSQLModifyColumnStatement, json2: SQLiteSchemaSquashed) { + const { tableName, columnName } = statement; + + let columnType = ``; + let columnDefault: any = ''; + let columnNotNull = ''; + + const sqlStatements: string[] = []; + + // collect index info + const indexes: { + name: string; + tableName: string; + columns: string[]; + isUnique: boolean; + where?: string | undefined; + }[] = []; + for (const table of Object.values(json2.tables)) { + for (const index of Object.values(table.indexes)) { + const unsquashed = SQLiteSquasher.unsquashIdx(index); + sqlStatements.push(`DROP INDEX IF EXISTS "${unsquashed.name}";`); + indexes.push({ ...unsquashed, tableName: table.name }); + } + } + + switch (statement.type) { + case 'alter_table_alter_column_set_type': + columnType = ` ${statement.newDataType}`; + + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + + break; + case 'alter_table_alter_column_drop_notnull': + columnType = ` ${statement.newDataType}`; + + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + + columnNotNull = ''; + break; + case 'alter_table_alter_column_set_notnull': + columnType = ` ${statement.newDataType}`; + + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + + columnNotNull = ` NOT NULL`; + break; + case 'alter_table_alter_column_set_default': + columnType = ` ${statement.newDataType}`; + + columnDefault = ` DEFAULT ${statement.newDefaultValue}`; + + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + break; + case 'alter_table_alter_column_drop_default': + columnType = ` ${statement.newDataType}`; + + columnDefault = ''; + + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + break; + } + + // Seems like getting value from simple json2 shanpshot makes dates be dates + columnDefault = columnDefault instanceof Date + ? columnDefault.toISOString() + : columnDefault; + + sqlStatements.push( + `ALTER TABLE \`${tableName}\` ALTER COLUMN "${columnName}" TO "${columnName}"${columnType}${columnNotNull}${columnDefault};`, + ); + + for (const index of indexes) { + const indexPart = index.isUnique ? 'UNIQUE INDEX' : 'INDEX'; + const whereStatement = index.where ? ` WHERE ${index.where}` : ''; + const uniqueString = index.columns.map((it) => `\`${it}\``).join(','); + const tableName = index.tableName; + + sqlStatements.push( + `CREATE ${indexPart} \`${index.name}\` ON \`${tableName}\` (${uniqueString})${whereStatement};`, + ); + } + + return sqlStatements; + } +} + type MySqlModifyColumnStatement = | JsonAlterColumnDropNotNullStatement | JsonAlterColumnSetNotNullStatement @@ -1735,20 +1991,354 @@ class MySqlModifyColumn extends Convertor { } } -class SqliteAlterTableAlterColumnDropDefaultConvertor extends Convertor { +class SingleStoreAlterTableAlterColumnAlterrGeneratedConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( - statement.type === 'alter_table_alter_column_drop_default' - && dialect === 'sqlite' + statement.type === 'alter_table_alter_column_alter_generated' + && dialect === 'singlestore' ); } - convert(statement: JsonAlterColumnDropDefaultStatement) { - return ( - '/*\n SQLite does not support "Drop default from column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' + convert(statement: JsonAlterColumnAlterGeneratedStatement) { + const { + tableName, + columnName, + schema, + columnNotNull: notNull, + columnDefault, + columnOnUpdate, + columnAutoIncrement, + columnPk, + columnGenerated, + } = statement; + + const tableNameWithSchema = schema + ? `\`${schema}\`.\`${tableName}\`` + : `\`${tableName}\``; + + const addColumnStatement = new SingleStoreAlterTableAddColumnConvertor().convert({ + schema, + tableName, + column: { + name: columnName, + type: statement.newDataType, + notNull, + default: columnDefault, + onUpdate: columnOnUpdate, + autoincrement: columnAutoIncrement, + primaryKey: columnPk, + generated: columnGenerated, + }, + type: 'alter_table_add_column', + }); + + return [ + `ALTER TABLE ${tableNameWithSchema} drop column \`${columnName}\`;`, + addColumnStatement, + ]; + } +} + +class SingleStoreAlterTableAlterColumnSetDefaultConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_alter_column_set_default' + && dialect === 'singlestore' + ); + } + + convert(statement: JsonAlterColumnSetDefaultStatement) { + const { tableName, columnName } = statement; + return `ALTER TABLE \`${tableName}\` ALTER COLUMN \`${columnName}\` SET DEFAULT ${statement.newDefaultValue};`; + } +} + +class SingleStoreAlterTableAlterColumnDropDefaultConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_alter_column_drop_default' + && dialect === 'singlestore' + ); + } + + convert(statement: JsonAlterColumnDropDefaultStatement) { + const { tableName, columnName } = statement; + return `ALTER TABLE \`${tableName}\` ALTER COLUMN \`${columnName}\` DROP DEFAULT;`; + } +} + +class SingleStoreAlterTableAddPk extends Convertor { + can(statement: JsonStatement, dialect: string): boolean { + return ( + statement.type === 'alter_table_alter_column_set_pk' + && dialect === 'singlestore' + ); + } + convert(statement: JsonAlterColumnSetPrimaryKeyStatement): string { + return `ALTER TABLE \`${statement.tableName}\` ADD PRIMARY KEY (\`${statement.columnName}\`);`; + } +} + +class SingleStoreAlterTableDropPk extends Convertor { + can(statement: JsonStatement, dialect: string): boolean { + return ( + statement.type === 'alter_table_alter_column_drop_pk' + && dialect === 'singlestore' + ); + } + convert(statement: JsonAlterColumnDropPrimaryKeyStatement): string { + return `ALTER TABLE \`${statement.tableName}\` DROP PRIMARY KEY`; + } +} + +type SingleStoreModifyColumnStatement = + | JsonAlterColumnDropNotNullStatement + | JsonAlterColumnSetNotNullStatement + | JsonAlterColumnTypeStatement + | JsonAlterColumnDropOnUpdateStatement + | JsonAlterColumnSetOnUpdateStatement + | JsonAlterColumnDropAutoincrementStatement + | JsonAlterColumnSetAutoincrementStatement + | JsonAlterColumnSetDefaultStatement + | JsonAlterColumnDropDefaultStatement + | JsonAlterColumnSetGeneratedStatement + | JsonAlterColumnDropGeneratedStatement; + +class SingleStoreModifyColumn extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + (statement.type === 'alter_table_alter_column_set_type' + || statement.type === 'alter_table_alter_column_set_notnull' + || statement.type === 'alter_table_alter_column_drop_notnull' + || statement.type === 'alter_table_alter_column_drop_on_update' + || statement.type === 'alter_table_alter_column_set_on_update' + || statement.type === 'alter_table_alter_column_set_autoincrement' + || statement.type === 'alter_table_alter_column_drop_autoincrement' + || statement.type === 'alter_table_alter_column_set_default' + || statement.type === 'alter_table_alter_column_drop_default' + || statement.type === 'alter_table_alter_column_set_generated' + || statement.type === 'alter_table_alter_column_drop_generated') + && dialect === 'singlestore' + ); + } + + convert(statement: SingleStoreModifyColumnStatement) { + const { tableName, columnName } = statement; + let columnType = ``; + let columnDefault: any = ''; + let columnNotNull = ''; + let columnOnUpdate = ''; + let columnAutoincrement = ''; + let primaryKey = statement.columnPk ? ' PRIMARY KEY' : ''; + let columnGenerated = ''; + + if (statement.type === 'alter_table_alter_column_drop_notnull') { + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_set_notnull') { + columnNotNull = ` NOT NULL`; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_drop_on_update') { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnOnUpdate = ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_set_on_update') { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = ` ON UPDATE CURRENT_TIMESTAMP`; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if ( + statement.type === 'alter_table_alter_column_set_autoincrement' + ) { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = ' AUTO_INCREMENT'; + } else if ( + statement.type === 'alter_table_alter_column_drop_autoincrement' + ) { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = ''; + } else if (statement.type === 'alter_table_alter_column_set_default') { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = ` DEFAULT ${statement.newDefaultValue}`; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_drop_default') { + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnType = ` ${statement.newDataType}`; + columnDefault = ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + } else if (statement.type === 'alter_table_alter_column_set_generated') { + columnType = ` ${statement.newDataType}`; + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + + if (statement.columnGenerated?.type === 'virtual') { + return [ + new SingleStoreAlterTableDropColumnConvertor().convert({ + type: 'alter_table_drop_column', + tableName: statement.tableName, + columnName: statement.columnName, + schema: statement.schema, + }), + new SingleStoreAlterTableAddColumnConvertor().convert({ + tableName, + column: { + name: columnName, + type: statement.newDataType, + notNull: statement.columnNotNull, + default: statement.columnDefault, + onUpdate: statement.columnOnUpdate, + autoincrement: statement.columnAutoIncrement, + primaryKey: statement.columnPk, + generated: statement.columnGenerated, + }, + schema: statement.schema, + type: 'alter_table_add_column', + }), + ]; + } else { + columnGenerated = statement.columnGenerated + ? ` GENERATED ALWAYS AS (${statement.columnGenerated?.as}) ${statement.columnGenerated?.type.toUpperCase()}` + : ''; + } + } else if (statement.type === 'alter_table_alter_column_drop_generated') { + columnType = ` ${statement.newDataType}`; + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + + if (statement.oldColumn?.generated?.type === 'virtual') { + return [ + new SingleStoreAlterTableDropColumnConvertor().convert({ + type: 'alter_table_drop_column', + tableName: statement.tableName, + columnName: statement.columnName, + schema: statement.schema, + }), + new SingleStoreAlterTableAddColumnConvertor().convert({ + tableName, + column: { + name: columnName, + type: statement.newDataType, + notNull: statement.columnNotNull, + default: statement.columnDefault, + onUpdate: statement.columnOnUpdate, + autoincrement: statement.columnAutoIncrement, + primaryKey: statement.columnPk, + generated: statement.columnGenerated, + }, + schema: statement.schema, + type: 'alter_table_add_column', + }), + ]; + } + } else { + columnType = ` ${statement.newDataType}`; + columnNotNull = statement.columnNotNull ? ` NOT NULL` : ''; + columnOnUpdate = columnOnUpdate = statement.columnOnUpdate + ? ` ON UPDATE CURRENT_TIMESTAMP` + : ''; + columnDefault = statement.columnDefault + ? ` DEFAULT ${statement.columnDefault}` + : ''; + columnAutoincrement = statement.columnAutoIncrement + ? ' AUTO_INCREMENT' + : ''; + columnGenerated = statement.columnGenerated + ? ` GENERATED ALWAYS AS (${statement.columnGenerated?.as}) ${statement.columnGenerated?.type.toUpperCase()}` + : ''; + } + + // Seems like getting value from simple json2 shanpshot makes dates be dates + columnDefault = columnDefault instanceof Date + ? columnDefault.toISOString() + : columnDefault; + + return `ALTER TABLE \`${tableName}\` MODIFY COLUMN \`${columnName}\`${columnType}${columnAutoincrement}${columnNotNull}${columnDefault}${columnOnUpdate}${columnGenerated};`; + } +} +class SqliteAlterTableAlterColumnDropDefaultConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'alter_table_alter_column_drop_default' + && dialect === 'sqlite' + ); + } + + convert(statement: JsonAlterColumnDropDefaultStatement) { + return ( + '/*\n SQLite does not support "Drop default from column" out of the box, we do not generate automatic migration for that, so it has to be done manually' + + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' + + '\n https://www.sqlite.org/lang_altertable.html' + + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' + "\n\n Due to that we don't generate migration automatically and it has to be done manually" + '\n*/' ); @@ -1772,7 +2362,6 @@ class PgAlterTableCreateCompositePrimaryKeyConvertor extends Convertor { }");`; } } - class PgAlterTableDeleteCompositePrimaryKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return statement.type === 'delete_composite_pk' && dialect === 'postgresql'; @@ -1846,6 +2435,42 @@ class MySqlAlterTableAlterCompositePrimaryKeyConvertor extends Convertor { } } +class SingleStoreAlterTableCreateCompositePrimaryKeyConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_composite_pk' && dialect === 'singlestore'; + } + + convert(statement: JsonCreateCompositePK) { + const { name, columns } = SingleStoreSquasher.unsquashPK(statement.data); + return `ALTER TABLE \`${statement.tableName}\` ADD PRIMARY KEY(\`${columns.join('`,`')}\`);`; + } +} + +class SingleStoreAlterTableDeleteCompositePrimaryKeyConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'delete_composite_pk' && dialect === 'singlestore'; + } + + convert(statement: JsonDeleteCompositePK) { + const { name, columns } = SingleStoreSquasher.unsquashPK(statement.data); + return `ALTER TABLE \`${statement.tableName}\` DROP PRIMARY KEY;`; + } +} + +class SingleStoreAlterTableAlterCompositePrimaryKeyConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'alter_composite_pk' && dialect === 'singlestore'; + } + + convert(statement: JsonAlterCompositePK) { + const { name, columns } = SingleStoreSquasher.unsquashPK(statement.old); + const { name: newName, columns: newColumns } = SingleStoreSquasher.unsquashPK( + statement.new, + ); + return `ALTER TABLE \`${statement.tableName}\` DROP PRIMARY KEY, ADD PRIMARY KEY(\`${newColumns.join('`,`')}\`);`; + } +} + class SqliteAlterTableCreateCompositePrimaryKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return statement.type === 'create_composite_pk' && dialect === 'sqlite'; @@ -1996,66 +2621,6 @@ class PgAlterTableAlterColumnSetNotNullConvertor extends Convertor { } } -class SqliteAlterTableAlterColumnSetNotNullConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_notnull' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnSetNotNullStatement) { - return ( - '/*\n SQLite does not support "Set not null to column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - -class SqliteAlterTableAlterColumnSetAutoincrementConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_set_autoincrement' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnSetAutoincrementStatement) { - return ( - '/*\n SQLite does not support "Set autoincrement to a column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - -class SqliteAlterTableAlterColumnDropAutoincrementConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_drop_autoincrement' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnDropAutoincrementStatement) { - return ( - '/*\n SQLite does not support "Drop autoincrement from a column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class PgAlterTableAlterColumnDropNotNullConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return ( @@ -2075,26 +2640,6 @@ class PgAlterTableAlterColumnDropNotNullConvertor extends Convertor { } } -class SqliteAlterTableAlterColumnDropNotNullConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return ( - statement.type === 'alter_table_alter_column_drop_notnull' - && dialect === 'sqlite' - ); - } - - convert(statement: JsonAlterColumnDropNotNullStatement) { - return ( - '/*\n SQLite does not support "Drop not null from column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + '\n https://stackoverflow.com/questions/2083543/modify-a-columns-type-in-sqlite3' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - // FK class PgCreateForeignKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { @@ -2137,20 +2682,37 @@ class PgCreateForeignKeyConvertor extends Convertor { } } -class SqliteCreateForeignKeyConvertor extends Convertor { +class LibSQLCreateForeignKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'create_reference' && dialect === 'sqlite'; - } - - convert(statement: JsonCreateReferenceStatement): string { return ( - '/*\n SQLite does not support "Creating foreign key on existing column" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' + statement.type === 'create_reference' + && dialect === 'turso' ); } + + convert( + statement: JsonCreateReferenceStatement, + json2?: SQLiteSchemaSquashed, + action?: 'push', + ): string { + const { columnsFrom, columnsTo, tableFrom, onDelete, onUpdate, tableTo } = action === 'push' + ? SQLiteSquasher.unsquashPushFK(statement.data) + : SQLiteSquasher.unsquashFK(statement.data); + const { columnDefault, columnNotNull, columnType } = statement; + + const onDeleteStatement = onDelete ? ` ON DELETE ${onDelete}` : ''; + const onUpdateStatement = onUpdate ? ` ON UPDATE ${onUpdate}` : ''; + const columnsDefaultValue = columnDefault + ? ` DEFAULT ${columnDefault}` + : ''; + const columnNotNullValue = columnNotNull ? ` NOT NULL` : ''; + const columnTypeValue = columnType ? ` ${columnType}` : ''; + + const columnFrom = columnsFrom[0]; + const columnTo = columnsTo[0]; + + return `ALTER TABLE \`${tableFrom}\` ALTER COLUMN "${columnFrom}" TO "${columnFrom}"${columnTypeValue}${columnNotNullValue}${columnsDefaultValue} REFERENCES ${tableTo}(${columnTo})${onDeleteStatement}${onUpdateStatement};`; + } } class MySqlCreateForeignKeyConvertor extends Convertor { @@ -2224,22 +2786,6 @@ class PgAlterForeignKeyConvertor extends Convertor { } } -class SqliteAlterForeignKeyConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'alter_reference' && dialect === 'sqlite'; - } - - convert(statement: JsonAlterReferenceStatement): string { - return ( - '/*\n SQLite does not support "Changing existing foreign key" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class PgDeleteForeignKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return statement.type === 'delete_reference' && dialect === 'postgresql'; @@ -2257,22 +2803,6 @@ class PgDeleteForeignKeyConvertor extends Convertor { } } -class SqliteDeleteForeignKeyConvertor extends Convertor { - can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'delete_reference' && dialect === 'sqlite'; - } - - convert(statement: JsonDeleteReferenceStatement): string { - return ( - '/*\n SQLite does not support "Dropping foreign key" out of the box, we do not generate automatic migration for that, so it has to be done manually' - + '\n Please refer to: https://www.techonthenet.com/sqlite/tables/alter_table.php' - + '\n https://www.sqlite.org/lang_altertable.html' - + "\n\n Due to that we don't generate migration automatically and it has to be done manually" - + '\n*/' - ); - } -} - class MySqlDeleteForeignKeyConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { return statement.type === 'delete_reference' && dialect === 'mysql'; @@ -2366,9 +2896,35 @@ class CreateMySqlIndexConvertor extends Convertor { } } +class CreateSingleStoreIndexConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'create_index' && dialect === 'singlestore'; + } + + convert(statement: JsonCreateIndexStatement): string { + // should be changed + const { name, columns, isUnique } = SingleStoreSquasher.unsquashIdx( + statement.data, + ); + const indexPart = isUnique ? 'UNIQUE INDEX' : 'INDEX'; + + const uniqueString = columns + .map((it) => { + return statement.internal?.indexes + ? statement.internal?.indexes[name]?.columns[it]?.isExpression + ? it + : `\`${it}\`` + : `\`${it}\``; + }) + .join(','); + + return `CREATE ${indexPart} \`${name}\` ON \`${statement.tableName}\` (${uniqueString});`; + } +} + export class CreateSqliteIndexConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'create_index' && dialect === 'sqlite'; + return statement.type === 'create_index' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonCreateIndexStatement): string { @@ -2490,7 +3046,7 @@ class PgAlterTableRemoveFromSchemaConvertor extends Convertor { export class SqliteDropIndexConvertor extends Convertor { can(statement: JsonStatement, dialect: Dialect): boolean { - return statement.type === 'drop_index' && dialect === 'sqlite'; + return statement.type === 'drop_index' && (dialect === 'sqlite' || dialect === 'turso'); } convert(statement: JsonDropIndexStatement): string { @@ -2510,10 +3066,143 @@ class MySqlDropIndexConvertor extends Convertor { } } +class SingleStoreDropIndexConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return statement.type === 'drop_index' && dialect === 'singlestore'; + } + + convert(statement: JsonDropIndexStatement): string { + const { name } = SingleStoreSquasher.unsquashIdx(statement.data); + return `DROP INDEX \`${name}\` ON \`${statement.tableName}\`;`; + } +} + +class SQLiteRecreateTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'recreate_table' && dialect === 'sqlite' + ); + } + + convert(statement: JsonRecreateTableStatement): string | string[] { + const { tableName, columns, compositePKs, referenceData } = statement; + + const columnNames = columns.map((it) => `"${it.name}"`).join(', '); + const newTableName = `__new_${tableName}`; + + const sqlStatements: string[] = []; + + sqlStatements.push(`PRAGMA foreign_keys=OFF;`); + + // create new table + sqlStatements.push( + new SQLiteCreateTableConvertor().convert({ + type: 'sqlite_create_table', + tableName: newTableName, + columns, + referenceData, + compositePKs, + }), + ); + + // migrate data + sqlStatements.push( + `INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${columnNames} FROM \`${tableName}\`;`, + ); + + // drop table + sqlStatements.push( + new SQLiteDropTableConvertor().convert({ + type: 'drop_table', + tableName: tableName, + schema: '', + }), + ); + + // rename table + sqlStatements.push( + new SqliteRenameTableConvertor().convert({ + fromSchema: '', + tableNameFrom: newTableName, + tableNameTo: tableName, + toSchema: '', + type: 'rename_table', + }), + ); + + sqlStatements.push(`PRAGMA foreign_keys=ON;`); + + return sqlStatements; + } +} + +class LibSQLRecreateTableConvertor extends Convertor { + can(statement: JsonStatement, dialect: Dialect): boolean { + return ( + statement.type === 'recreate_table' + && dialect === 'turso' + ); + } + + convert(statement: JsonRecreateTableStatement): string[] { + const { tableName, columns, compositePKs, referenceData } = statement; + + const columnNames = columns.map((it) => `"${it.name}"`).join(', '); + const newTableName = `__new_${tableName}`; + + const sqlStatements: string[] = []; + + sqlStatements.push(`PRAGMA foreign_keys=OFF;`); + + // create new table + sqlStatements.push( + new SQLiteCreateTableConvertor().convert({ + type: 'sqlite_create_table', + tableName: newTableName, + columns, + referenceData, + compositePKs, + }), + ); + + // migrate data + sqlStatements.push( + `INSERT INTO \`${newTableName}\`(${columnNames}) SELECT ${columnNames} FROM \`${tableName}\`;`, + ); + + // drop table + sqlStatements.push( + new SQLiteDropTableConvertor().convert({ + type: 'drop_table', + tableName: tableName, + schema: '', + }), + ); + + // rename table + sqlStatements.push( + new SqliteRenameTableConvertor().convert({ + fromSchema: '', + tableNameFrom: newTableName, + tableNameTo: tableName, + toSchema: '', + type: 'rename_table', + }), + ); + + sqlStatements.push(`PRAGMA foreign_keys=ON;`); + + return sqlStatements; + } +} + const convertors: Convertor[] = []; convertors.push(new PgCreateTableConvertor()); convertors.push(new MySqlCreateTableConvertor()); +convertors.push(new SingleStoreCreateTableConvertor()); convertors.push(new SQLiteCreateTableConvertor()); +convertors.push(new SQLiteRecreateTableConvertor()); +convertors.push(new LibSQLRecreateTableConvertor()); convertors.push(new CreateTypeEnumConvertor()); @@ -2525,22 +3214,27 @@ convertors.push(new AlterPgSequenceConvertor()); convertors.push(new PgDropTableConvertor()); convertors.push(new MySQLDropTableConvertor()); +convertors.push(new SingleStoreDropTableConvertor()); convertors.push(new SQLiteDropTableConvertor()); convertors.push(new PgRenameTableConvertor()); convertors.push(new MySqlRenameTableConvertor()); +convertors.push(new SingleStoreRenameTableConvertor()); convertors.push(new SqliteRenameTableConvertor()); convertors.push(new PgAlterTableRenameColumnConvertor()); convertors.push(new MySqlAlterTableRenameColumnConvertor()); +convertors.push(new SingleStoreAlterTableRenameColumnConvertor()); convertors.push(new SQLiteAlterTableRenameColumnConvertor()); convertors.push(new PgAlterTableDropColumnConvertor()); convertors.push(new MySqlAlterTableDropColumnConvertor()); +convertors.push(new SingleStoreAlterTableDropColumnConvertor()); convertors.push(new SQLiteAlterTableDropColumnConvertor()); convertors.push(new PgAlterTableAddColumnConvertor()); convertors.push(new MySqlAlterTableAddColumnConvertor()); +convertors.push(new SingleStoreAlterTableAddColumnConvertor()); convertors.push(new SQLiteAlterTableAddColumnConvertor()); convertors.push(new PgAlterTableAlterColumnSetTypeConvertor()); @@ -2551,13 +3245,18 @@ convertors.push(new PgAlterTableDropUniqueConstraintConvertor()); convertors.push(new MySQLAlterTableAddUniqueConstraintConvertor()); convertors.push(new MySQLAlterTableDropUniqueConstraintConvertor()); +convertors.push(new SingleStoreAlterTableAddUniqueConstraintConvertor()); +convertors.push(new SingleStoreAlterTableDropUniqueConstraintConvertor()); + convertors.push(new CreatePgIndexConvertor()); convertors.push(new CreateMySqlIndexConvertor()); +convertors.push(new CreateSingleStoreIndexConvertor()); convertors.push(new CreateSqliteIndexConvertor()); convertors.push(new PgDropIndexConvertor()); convertors.push(new SqliteDropIndexConvertor()); convertors.push(new MySqlDropIndexConvertor()); +convertors.push(new SingleStoreDropIndexConvertor()); convertors.push(new AlterTypeAddValueConvertor()); @@ -2575,14 +3274,19 @@ convertors.push(new PgAlterTableAlterColumnAlterrGeneratedConvertor()); convertors.push(new MySqlAlterTableAlterColumnAlterrGeneratedConvertor()); +convertors.push(new SingleStoreAlterTableAlterColumnAlterrGeneratedConvertor()); + convertors.push(new SqliteAlterTableAlterColumnDropGeneratedConvertor()); convertors.push(new SqliteAlterTableAlterColumnAlterGeneratedConvertor()); convertors.push(new SqliteAlterTableAlterColumnSetExpressionConvertor()); convertors.push(new MySqlModifyColumn()); +convertors.push(new LibSQLModifyColumn()); // convertors.push(new MySqlAlterTableAlterColumnSetDefaultConvertor()); // convertors.push(new MySqlAlterTableAlterColumnDropDefaultConvertor()); +convertors.push(new SingleStoreModifyColumn()); + convertors.push(new PgCreateForeignKeyConvertor()); convertors.push(new MySqlCreateForeignKeyConvertor()); @@ -2598,31 +3302,12 @@ convertors.push(new PgAlterTableSetSchemaConvertor()); convertors.push(new PgAlterTableSetNewSchemaConvertor()); convertors.push(new PgAlterTableRemoveFromSchemaConvertor()); -// Unhandled sqlite queries, so they will appear last -convertors.push(new SQLiteAlterTableAlterColumnSetTypeConvertor()); -convertors.push(new SqliteAlterForeignKeyConvertor()); -convertors.push(new SqliteDeleteForeignKeyConvertor()); -convertors.push(new SqliteCreateForeignKeyConvertor()); - -convertors.push(new SQLiteAlterTableAddUniqueConstraintConvertor()); -convertors.push(new SQLiteAlterTableDropUniqueConstraintConvertor()); +convertors.push(new LibSQLCreateForeignKeyConvertor()); convertors.push(new PgAlterTableAlterColumnDropGenerated()); convertors.push(new PgAlterTableAlterColumnSetGenerated()); convertors.push(new PgAlterTableAlterColumnAlterGenerated()); -convertors.push(new SqliteAlterTableAlterColumnSetNotNullConvertor()); -convertors.push(new SqliteAlterTableAlterColumnDropNotNullConvertor()); -convertors.push(new SqliteAlterTableAlterColumnSetDefaultConvertor()); -convertors.push(new SqliteAlterTableAlterColumnDropDefaultConvertor()); - -convertors.push(new SqliteAlterTableAlterColumnSetAutoincrementConvertor()); -convertors.push(new SqliteAlterTableAlterColumnDropAutoincrementConvertor()); - -convertors.push(new SqliteAlterTableCreateCompositePrimaryKeyConvertor()); -convertors.push(new SqliteAlterTableDeleteCompositePrimaryKeyConvertor()); -convertors.push(new SqliteAlterTableAlterCompositePrimaryKeyConvertor()); - convertors.push(new PgAlterTableCreateCompositePrimaryKeyConvertor()); convertors.push(new PgAlterTableDeleteCompositePrimaryKeyConvertor()); convertors.push(new PgAlterTableAlterCompositePrimaryKeyConvertor()); @@ -2633,26 +3318,47 @@ convertors.push(new MySqlAlterTableCreateCompositePrimaryKeyConvertor()); convertors.push(new MySqlAlterTableAddPk()); convertors.push(new MySqlAlterTableAlterCompositePrimaryKeyConvertor()); -export const fromJson = (statements: JsonStatement[], dialect: Dialect) => { +convertors.push(new SingleStoreAlterTableDeleteCompositePrimaryKeyConvertor()); +convertors.push(new SingleStoreAlterTableDropPk()); +convertors.push(new SingleStoreAlterTableCreateCompositePrimaryKeyConvertor()); +convertors.push(new SingleStoreAlterTableAddPk()); +convertors.push(new SingleStoreAlterTableAlterCompositePrimaryKeyConvertor()); + +// overloads for turso driver +export function fromJson( + statements: JsonStatement[], + dialect: Exclude, +): string[]; +export function fromJson( + statements: JsonStatement[], + dialect: 'sqlite' | 'turso', + action?: 'push', + json2?: SQLiteSchemaSquashed, +): string[]; + +export function fromJson( + statements: JsonStatement[], + dialect: Dialect, + action?: 'push', + json2?: SQLiteSchemaSquashed, +) { const result = statements .flatMap((statement) => { const filtered = convertors.filter((it) => { - // console.log(statement, dialect) return it.can(statement, dialect); }); const convertor = filtered.length === 1 ? filtered[0] : undefined; if (!convertor) { - // console.log("no convertor:", statement.type, dialect); return ''; } - return convertor.convert(statement); + return convertor.convert(statement, json2, action); }) .filter((it) => it !== ''); return result; -}; +} // blog.yo1.dog/updating-enum-values-in-postgresql-the-safe-and-easy-way/ // test case for enum altering diff --git a/drizzle-kit/src/statementCombiner.ts b/drizzle-kit/src/statementCombiner.ts new file mode 100644 index 000000000..2f7b6ddbe --- /dev/null +++ b/drizzle-kit/src/statementCombiner.ts @@ -0,0 +1,450 @@ +import { + JsonCreateIndexStatement, + JsonRecreateTableStatement, + JsonStatement, + prepareCreateIndexesJson, +} from './jsonStatements'; +import { SQLiteSchemaSquashed, SQLiteSquasher } from './serializer/sqliteSchema'; + +export const prepareLibSQLRecreateTable = ( + table: SQLiteSchemaSquashed['tables'][keyof SQLiteSchemaSquashed['tables']], + action?: 'push', +): (JsonRecreateTableStatement | JsonCreateIndexStatement)[] => { + const { name, columns, uniqueConstraints, indexes } = table; + + const composites: string[][] = Object.values(table.compositePrimaryKeys).map( + (it) => SQLiteSquasher.unsquashPK(it), + ); + + const references: string[] = Object.values(table.foreignKeys); + const fks = references.map((it) => + action === 'push' ? SQLiteSquasher.unsquashPushFK(it) : SQLiteSquasher.unsquashFK(it) + ); + + const statements: (JsonRecreateTableStatement | JsonCreateIndexStatement)[] = [ + { + type: 'recreate_table', + tableName: name, + columns: Object.values(columns), + compositePKs: composites, + referenceData: fks, + uniqueConstraints: Object.values(uniqueConstraints), + }, + ]; + + if (Object.keys(indexes).length) { + statements.push(...prepareCreateIndexesJson(name, '', indexes)); + } + return statements; +}; + +export const prepareSQLiteRecreateTable = ( + table: SQLiteSchemaSquashed['tables'][keyof SQLiteSchemaSquashed['tables']], + action?: 'push', +): JsonStatement[] => { + const { name, columns, uniqueConstraints, indexes } = table; + + const composites: string[][] = Object.values(table.compositePrimaryKeys).map( + (it) => SQLiteSquasher.unsquashPK(it), + ); + + const references: string[] = Object.values(table.foreignKeys); + const fks = references.map((it) => + action === 'push' ? SQLiteSquasher.unsquashPushFK(it) : SQLiteSquasher.unsquashFK(it) + ); + + const statements: JsonStatement[] = [ + { + type: 'recreate_table', + tableName: name, + columns: Object.values(columns), + compositePKs: composites, + referenceData: fks, + uniqueConstraints: Object.values(uniqueConstraints), + }, + ]; + + if (Object.keys(indexes).length) { + statements.push(...prepareCreateIndexesJson(name, '', indexes)); + } + return statements; +}; + +export const libSQLCombineStatements = ( + statements: JsonStatement[], + json2: SQLiteSchemaSquashed, + action?: 'push', +) => { + // const tablesContext: Record = {}; + const newStatements: Record = {}; + for (const statement of statements) { + if ( + statement.type === 'alter_table_alter_column_drop_autoincrement' + || statement.type === 'alter_table_alter_column_set_autoincrement' + || statement.type === 'alter_table_alter_column_drop_pk' + || statement.type === 'alter_table_alter_column_set_pk' + || statement.type === 'create_composite_pk' + || statement.type === 'alter_composite_pk' + || statement.type === 'delete_composite_pk' + ) { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if ( + statement.type === 'alter_table_alter_column_set_type' + || statement.type === 'alter_table_alter_column_drop_notnull' + || statement.type === 'alter_table_alter_column_set_notnull' + || statement.type === 'alter_table_alter_column_set_default' + || statement.type === 'alter_table_alter_column_drop_default' + ) { + const { tableName, columnName, columnPk } = statement; + + // const columnIsPartOfUniqueIndex = Object.values( + // json2.tables[tableName].indexes, + // ).some((it) => { + // const unsquashIndex = SQLiteSquasher.unsquashIdx(it); + + // return ( + // unsquashIndex.columns.includes(columnName) && unsquashIndex.isUnique + // ); + // }); + + const columnIsPartOfForeignKey = Object.values( + json2.tables[tableName].foreignKeys, + ).some((it) => { + const unsquashFk = action === 'push' ? SQLiteSquasher.unsquashPushFK(it) : SQLiteSquasher.unsquashFK(it); + + return ( + unsquashFk.columnsFrom.includes(columnName) + ); + }); + + const statementsForTable = newStatements[tableName]; + + if ( + !statementsForTable && (columnIsPartOfForeignKey || columnPk) + ) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + continue; + } + + if ( + statementsForTable && (columnIsPartOfForeignKey || columnPk) + ) { + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + } + continue; + } + if ( + statementsForTable && !(columnIsPartOfForeignKey || columnPk) + ) { + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + newStatements[tableName].push(statement); + } + continue; + } + + newStatements[tableName] = [statement]; + + continue; + } + + if (statement.type === 'create_reference') { + const tableName = statement.tableName; + + const data = action === 'push' + ? SQLiteSquasher.unsquashPushFK(statement.data) + : SQLiteSquasher.unsquashFK(statement.data); + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = statement.isMulticolumn + ? prepareLibSQLRecreateTable(json2.tables[tableName], action) + : [statement]; + + continue; + } + + // if add column with reference -> skip create_reference statement + if ( + !statement.isMulticolumn + && statementsForTable.some((st) => + st.type === 'sqlite_alter_table_add_column' && st.column.name === data.columnsFrom[0] + ) + ) { + continue; + } + + if (statement.isMulticolumn) { + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + newStatements[tableName].push(statement); + } + + continue; + } + + if (statement.type === 'delete_reference') { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if (statement.type === 'sqlite_alter_table_add_column' && statement.column.primaryKey) { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + const tableName = statement.type === 'rename_table' + ? statement.tableNameTo + : (statement as { tableName: string }).tableName; + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = [statement]; + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + newStatements[tableName].push(statement); + } + } + + const combinedStatements = Object.values(newStatements).flat(); + const renamedTables = combinedStatements.filter((it) => it.type === 'rename_table'); + const renamedColumns = combinedStatements.filter((it) => it.type === 'alter_table_rename_column'); + + const rest = combinedStatements.filter((it) => it.type !== 'rename_table' && it.type !== 'alter_table_rename_column'); + + return [...renamedTables, ...renamedColumns, ...rest]; +}; + +export const sqliteCombineStatements = ( + statements: JsonStatement[], + json2: SQLiteSchemaSquashed, + action?: 'push', +) => { + // const tablesContext: Record = {}; + const newStatements: Record = {}; + for (const statement of statements) { + if ( + statement.type === 'alter_table_alter_column_set_type' + || statement.type === 'alter_table_alter_column_set_default' + || statement.type === 'alter_table_alter_column_drop_default' + || statement.type === 'alter_table_alter_column_set_notnull' + || statement.type === 'alter_table_alter_column_drop_notnull' + || statement.type === 'alter_table_alter_column_drop_autoincrement' + || statement.type === 'alter_table_alter_column_set_autoincrement' + || statement.type === 'alter_table_alter_column_drop_pk' + || statement.type === 'alter_table_alter_column_set_pk' + || statement.type === 'delete_reference' + || statement.type === 'alter_reference' + || statement.type === 'create_composite_pk' + || statement.type === 'alter_composite_pk' + || statement.type === 'delete_composite_pk' + || statement.type === 'create_unique_constraint' + || statement.type === 'delete_unique_constraint' + ) { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if (statement.type === 'sqlite_alter_table_add_column' && statement.column.primaryKey) { + const tableName = statement.tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareLibSQLRecreateTable(json2.tables[tableName], action); + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + if (statement.type === 'create_reference') { + const tableName = statement.tableName; + + const data = action === 'push' + ? SQLiteSquasher.unsquashPushFK(statement.data) + : SQLiteSquasher.unsquashFK(statement.data); + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = prepareSQLiteRecreateTable(json2.tables[tableName], action); + continue; + } + + // if add column with reference -> skip create_reference statement + if ( + data.columnsFrom.length === 1 + && statementsForTable.some((st) => + st.type === 'sqlite_alter_table_add_column' && st.column.name === data.columnsFrom[0] + ) + ) { + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + const wasRename = statementsForTable.some(({ type }) => type === 'rename_table'); + const preparedStatements = prepareLibSQLRecreateTable(json2.tables[tableName], action); + + if (wasRename) { + newStatements[tableName].push(...preparedStatements); + } else { + newStatements[tableName] = preparedStatements; + } + + continue; + } + + continue; + } + + const tableName = statement.type === 'rename_table' + ? statement.tableNameTo + : (statement as { tableName: string }).tableName; + + const statementsForTable = newStatements[tableName]; + + if (!statementsForTable) { + newStatements[tableName] = [statement]; + continue; + } + + if (!statementsForTable.some(({ type }) => type === 'recreate_table')) { + newStatements[tableName].push(statement); + } + } + + const combinedStatements = Object.values(newStatements).flat(); + + const renamedTables = combinedStatements.filter((it) => it.type === 'rename_table'); + const renamedColumns = combinedStatements.filter((it) => it.type === 'alter_table_rename_column'); + + const rest = combinedStatements.filter((it) => it.type !== 'rename_table' && it.type !== 'alter_table_rename_column'); + + return [...renamedTables, ...renamedColumns, ...rest]; +}; diff --git a/drizzle-kit/src/utils.ts b/drizzle-kit/src/utils.ts index 7b363a9d3..71454550e 100644 --- a/drizzle-kit/src/utils.ts +++ b/drizzle-kit/src/utils.ts @@ -9,6 +9,7 @@ import { assertUnreachable, snapshotVersion } from './global'; import type { Dialect } from './schemaValidator'; import { backwardCompatibleMysqlSchema } from './serializer/mysqlSchema'; import { backwardCompatiblePgSchema } from './serializer/pgSchema'; +import { backwardCompatibleSingleStoreSchema } from './serializer/singlestoreSchema'; import { backwardCompatibleSqliteSchema } from './serializer/sqliteSchema'; import type { ProxyParams } from './serializer/studio'; @@ -25,9 +26,12 @@ export type DB = { export type SQLiteDB = { query: (sql: string, params?: any[]) => Promise; run(query: string): Promise; - batch?( - queries: { query: string; values?: any[] | undefined }[], - ): Promise; +}; + +export type LibSQLDB = { + query: (sql: string, params?: any[]) => Promise; + run(query: string): Promise; + batchWithPragma?(queries: string[]): Promise; }; export const copy = (it: T): T => { @@ -115,8 +119,12 @@ const validatorForDialect = (dialect: Dialect) => { return { validator: backwardCompatiblePgSchema, version: 7 }; case 'sqlite': return { validator: backwardCompatibleSqliteSchema, version: 6 }; + case 'turso': + return { validator: backwardCompatibleSqliteSchema, version: 6 }; case 'mysql': return { validator: backwardCompatibleMysqlSchema, version: 5 }; + case 'singlestore': + return { validator: backwardCompatibleSingleStoreSchema, version: 1 }; } }; @@ -341,3 +349,13 @@ export const normalisePGliteUrl = ( export function isPgArrayType(sqlType: string) { return sqlType.match(/.*\[\d*\].*|.*\[\].*/g) !== null; } + +export function findAddedAndRemoved(columnNames1: string[], columnNames2: string[]) { + const set1 = new Set(columnNames1); + const set2 = new Set(columnNames2); + + const addedColumns = columnNames2.filter((it) => !set1.has(it)); + const removedColumns = columnNames1.filter((it) => !set2.has(it)); + + return { addedColumns, removedColumns }; +} diff --git a/drizzle-kit/tests/cli-generate.test.ts b/drizzle-kit/tests/cli-generate.test.ts index 3e5c0fc22..56a3a0d04 100644 --- a/drizzle-kit/tests/cli-generate.test.ts +++ b/drizzle-kit/tests/cli-generate.test.ts @@ -62,6 +62,7 @@ test('generate #2', async (t) => { test('generate #3', async (t) => { const res = await brotest(generate, ''); + if (res.type !== 'handler') assert.fail(res.type, 'handler'); expect(res.options).toStrictEqual({ dialect: 'postgresql', diff --git a/drizzle-kit/tests/cli-migrate.test.ts b/drizzle-kit/tests/cli-migrate.test.ts index a4ffec2f0..1425691f0 100644 --- a/drizzle-kit/tests/cli-migrate.test.ts +++ b/drizzle-kit/tests/cli-migrate.test.ts @@ -31,11 +31,10 @@ test('migrate #2', async (t) => { const res = await brotest(migrate, '--config=turso.config.ts'); if (res.type !== 'handler') assert.fail(res.type, 'handler'); expect(res.options).toStrictEqual({ - dialect: 'sqlite', + dialect: 'turso', out: 'drizzle', credentials: { authToken: 'token', - driver: 'turso', url: 'turso.dev', }, schema: undefined, // drizzle migrations table schema diff --git a/drizzle-kit/tests/cli-push.test.ts b/drizzle-kit/tests/cli-push.test.ts index 1a4bde66d..f5b84fdce 100644 --- a/drizzle-kit/tests/cli-push.test.ts +++ b/drizzle-kit/tests/cli-push.test.ts @@ -34,10 +34,9 @@ test('push #2', async (t) => { const res = await brotest(push, '--config=turso.config.ts'); if (res.type !== 'handler') assert.fail(res.type, 'handler'); expect(res.options).toStrictEqual({ - dialect: 'sqlite', + dialect: 'turso', credentials: { authToken: 'token', - driver: 'turso', url: 'turso.dev', }, force: false, diff --git a/drizzle-kit/tests/cli/turso.config.ts b/drizzle-kit/tests/cli/turso.config.ts index 089e4d216..85efe5934 100644 --- a/drizzle-kit/tests/cli/turso.config.ts +++ b/drizzle-kit/tests/cli/turso.config.ts @@ -2,8 +2,7 @@ import { defineConfig } from '../../src'; export default defineConfig({ schema: './schema.ts', - dialect: 'sqlite', - driver: 'turso', + dialect: 'turso', dbCredentials: { url: 'turso.dev', authToken: 'token', diff --git a/drizzle-kit/tests/libsql-statements.test.ts b/drizzle-kit/tests/libsql-statements.test.ts new file mode 100644 index 000000000..8221e52e0 --- /dev/null +++ b/drizzle-kit/tests/libsql-statements.test.ts @@ -0,0 +1,982 @@ +import { foreignKey, index, int, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'; +import { JsonRecreateTableStatement } from 'src/jsonStatements'; +import { expect, test } from 'vitest'; +import { diffTestSchemasLibSQL } from './schemaDiffer'; + +test('drop autoincrement', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + }), + }; + + const { statements } = await diffTestSchemasLibSQL(schema1, schema2, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [{ + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); +}); + +test('set autoincrement', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + }), + }; + + const { statements } = await diffTestSchemasLibSQL(schema1, schema2, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [{ + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); +}); + +test('set not null', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text NOT NULL;`, + ); +}); + +test('drop not null', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_drop_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`, + ); +}); + +test('set default. set not null. add column', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull().default('name'), + age: int('age').notNull(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(3); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_default', + tableName: 'users', + columnName: 'name', + newDefaultValue: "'name'", + schema: '', + newDataType: 'text', + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + expect(statements[1]).toStrictEqual({ + type: 'alter_table_alter_column_set_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: "'name'", + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + expect(statements[2]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'users', + referenceData: undefined, + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text NOT NULL DEFAULT 'name';`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` ADD \`age\` integer NOT NULL;`, + ); +}); + +test('drop default. drop not null', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull().default('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_drop_default', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + expect(statements[1]).toStrictEqual({ + type: 'alter_table_alter_column_drop_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`, + ); +}); + +test('set data type. set default', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: int('name').default(123), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_type', + tableName: 'users', + columnName: 'name', + newDataType: 'integer', + oldDataType: 'text', + schema: '', + columnDefault: 123, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + expect(statements[1]).toStrictEqual({ + type: 'alter_table_alter_column_set_default', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'integer', + newDefaultValue: 123, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" integer DEFAULT 123;`, + ); +}); + +test('add foriegn key', async (t) => { + const schema = { + table: sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id').references(() => schema.table.id), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_reference', + tableName: 'users', + data: 'users_table_id_table_id_fk;users;table_id;table;id;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "table_id" TO "table_id" integer REFERENCES table(id) ON DELETE no action ON UPDATE no action;`, + ); +}); + +test('drop foriegn key', async (t) => { + const schema = { + table: sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id').references(() => schema.table.id, { + onDelete: 'cascade', + }), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'table_id', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`table_id\` integer +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "table_id") SELECT "id", "table_id" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('alter foriegn key', async (t) => { + const tableRef = sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }); + const tableRef2 = sqliteTable('table2', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id').references(() => tableRef.id, { + onDelete: 'cascade', + }), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + tableId: int('table_id').references(() => tableRef2.id), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'table_id', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [ + { + columnsFrom: ['table_id'], + columnsTo: ['id'], + name: 'users_table_id_table2_id_fk', + onDelete: 'no action', + onUpdate: 'no action', + tableFrom: 'users', + tableTo: 'table2', + }, + ], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`table_id\` integer, +\tFOREIGN KEY (\`table_id\`) REFERENCES \`table2\`(\`id\`) ON UPDATE no action ON DELETE no action +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "table_id") SELECT "id", "table_id" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe( + 'DROP TABLE `users`;', + ); + expect(sqlStatements[4]).toBe( + 'ALTER TABLE `__new_users` RENAME TO `users`;', + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('add foriegn key for multiple columns', async (t) => { + const tableRef = sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + age: int('age'), + age1: int('age_1'), + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + column: int('column'), + column1: int('column_1'), + }), + tableRef, + }; + + const schema2 = { + tableRef, + users: sqliteTable( + 'users', + { + id: int('id').primaryKey({ autoIncrement: true }), + column: int('column'), + column1: int('column_1'), + }, + (table) => ({ + foreignKey: foreignKey({ + columns: [table.column, table.column1], + foreignColumns: [tableRef.age, tableRef.age1], + }), + }), + ), + }; + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'column', + notNull: false, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'column_1', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [ + { + columnsFrom: ['column', 'column_1'], + columnsTo: ['age', 'age_1'], + name: 'users_column_column_1_table_age_age_1_fk', + onDelete: 'no action', + onUpdate: 'no action', + tableFrom: 'users', + tableTo: 'table', + }, + ], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + } as JsonRecreateTableStatement); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe( + `CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`column\` integer, +\t\`column_1\` integer, +\tFOREIGN KEY (\`column\`,\`column_1\`) REFERENCES \`table\`(\`age\`,\`age_1\`) ON UPDATE no action ON DELETE no action +);\n`, + ); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "column", "column_1") SELECT "id", "column", "column_1" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('drop foriegn key for multiple columns', async (t) => { + const tableRef = sqliteTable('table', { + id: int('id').primaryKey({ autoIncrement: true }), + age: int('age'), + age1: int('age_1'), + }); + + const schema1 = { + users: sqliteTable( + 'users', + { + id: int('id').primaryKey({ autoIncrement: true }), + column: int('column'), + column1: int('column_1'), + }, + (table) => ({ + foreignKey: foreignKey({ + columns: [table.column, table.column1], + foreignColumns: [tableRef.age, tableRef.age1], + }), + }), + ), + tableRef, + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + column: int('column'), + column1: int('column_1'), + }), + tableRef, + }; + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'column', + notNull: false, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'column_1', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe( + `CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`column\` integer, +\t\`column_1\` integer +);\n`, + ); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "column", "column_1") SELECT "id", "column", "column_1" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('alter column drop generated', async (t) => { + const from = { + users: sqliteTable('table', { + id: int('id').primaryKey().notNull(), + name: text('name').generatedAlwaysAs('drizzle is the best').notNull(), + }), + }; + + const to = { + users: sqliteTable('table', { + id: int('id').primaryKey().notNull(), + name: text('name').notNull(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + from, + to, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'table', + type: 'alter_table_alter_column_drop_generated', + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TABLE \`table\` DROP COLUMN \`name\`;`); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`table\` ADD \`name\` text NOT NULL;`, + ); +}); + +test('recreate table with nested references', async (t) => { + let users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }); + let subscriptions = sqliteTable('subscriptions', { + id: int('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + customerId: text('customer_id'), + }); + const schema1 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }); + const schema2 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "name", "age") SELECT "id", "name", "age" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); + +test('set not null with index', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }, (table) => ({ + someIndex: index('users_name_index').on(table.name), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }, (table) => ({ + someIndex: index('users_name_index').on(table.name), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(3); + expect(sqlStatements[0]).toBe( + `DROP INDEX IF EXISTS "users_name_index";`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text NOT NULL;`, + ); + expect(sqlStatements[2]).toBe( + `CREATE INDEX \`users_name_index\` ON \`users\` (\`name\`);`, + ); +}); + +test('drop not null with two indexes', async (t) => { + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + age: int('age').notNull(), + }, (table) => ({ + someUniqeIndex: uniqueIndex('users_name_unique').on(table.name), + someIndex: index('users_age_index').on(table.age), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: int('age').notNull(), + }, (table) => ({ + someUniqeIndex: uniqueIndex('users_name_unique').on(table.name), + someIndex: index('users_age_index').on(table.age), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasLibSQL( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_drop_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(5); + expect(sqlStatements[0]).toBe( + `DROP INDEX IF EXISTS "users_name_unique";`, + ); + expect(sqlStatements[1]).toBe( + `DROP INDEX IF EXISTS "users_age_index";`, + ); + expect(sqlStatements[2]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`, + ); + expect(sqlStatements[3]).toBe( + `CREATE UNIQUE INDEX \`users_name_unique\` ON \`users\` (\`name\`);`, + ); + expect(sqlStatements[4]).toBe( + `CREATE INDEX \`users_age_index\` ON \`users\` (\`age\`);`, + ); +}); diff --git a/drizzle-kit/tests/migrate/libsq-schema.ts b/drizzle-kit/tests/migrate/libsq-schema.ts new file mode 100644 index 000000000..5cb344d51 --- /dev/null +++ b/drizzle-kit/tests/migrate/libsq-schema.ts @@ -0,0 +1,6 @@ +import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; + +export const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), +}); diff --git a/drizzle-kit/tests/migrate/libsql-migrate.test.ts b/drizzle-kit/tests/migrate/libsql-migrate.test.ts new file mode 100644 index 000000000..b937b644f --- /dev/null +++ b/drizzle-kit/tests/migrate/libsql-migrate.test.ts @@ -0,0 +1,58 @@ +import { createClient } from '@libsql/client'; +import { connectToLibSQL } from 'src/cli/connections'; +import { expect, test } from 'vitest'; + +test('validate migrate function', async () => { + const credentials = { + url: ':memory:', + }; + const { migrate, query } = await connectToLibSQL(credentials); + + await migrate({ migrationsFolder: 'tests/migrate/migrations' }); + + const res = await query(`PRAGMA table_info("users");`); + + expect(res).toStrictEqual([{ + cid: 0, + name: 'id', + type: 'INTEGER', + notnull: 0, + dflt_value: null, + pk: 0, + }, { + cid: 1, + name: 'name', + type: 'INTEGER', + notnull: 1, + dflt_value: null, + pk: 0, + }]); +}); + +// test('validate migrate function', async () => { +// const credentials = { +// url: '', +// authToken: '', +// }; +// const { migrate, query } = await connectToLibSQL(credentials); + +// await migrate({ migrationsFolder: 'tests/migrate/migrations' }); + +// const res = await query(`PRAGMA table_info("users");`); + +// expect(res).toStrictEqual([{ +// cid: 0, +// name: 'id', +// type: 'INTEGER', +// notnull: 0, +// dflt_value: null, +// pk: 0, +// }, { +// cid: 1, +// name: 'name', +// type: 'INTEGER', +// notnull: 1, +// dflt_value: null, +// pk: 0, +// }]); +// }); diff --git a/drizzle-kit/tests/migrate/migrations/0000_little_blizzard.sql b/drizzle-kit/tests/migrate/migrations/0000_little_blizzard.sql new file mode 100644 index 000000000..9de0a139d --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/0000_little_blizzard.sql @@ -0,0 +1,4 @@ +CREATE TABLE `users` ( + `id` integer PRIMARY KEY NOT NULL, + `name` text NOT NULL +); diff --git a/drizzle-kit/tests/migrate/migrations/0001_nebulous_storm.sql b/drizzle-kit/tests/migrate/migrations/0001_nebulous_storm.sql new file mode 100644 index 000000000..4309a05c2 --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/0001_nebulous_storm.sql @@ -0,0 +1,10 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_users` ( + `id` integer, + `name` integer NOT NULL +); +--> statement-breakpoint +INSERT INTO `__new_users`("id", "name") SELECT "id", "name" FROM `users`;--> statement-breakpoint +DROP TABLE `users`;--> statement-breakpoint +ALTER TABLE `__new_users` RENAME TO `users`;--> statement-breakpoint +PRAGMA foreign_keys=ON; \ No newline at end of file diff --git a/drizzle-kit/tests/migrate/migrations/meta/0000_snapshot.json b/drizzle-kit/tests/migrate/migrations/meta/0000_snapshot.json new file mode 100644 index 000000000..599d02b91 --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/meta/0000_snapshot.json @@ -0,0 +1,40 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2bd46776-9e41-4a6c-b617-5c600bb176f2", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle-kit/tests/migrate/migrations/meta/0001_snapshot.json b/drizzle-kit/tests/migrate/migrations/meta/0001_snapshot.json new file mode 100644 index 000000000..e3b26ba14 --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/meta/0001_snapshot.json @@ -0,0 +1,40 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6c0ec455-42fd-47fd-a22c-4bb4551e1358", + "prevId": "2bd46776-9e41-4a6c-b617-5c600bb176f2", + "tables": { + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle-kit/tests/migrate/migrations/meta/_journal.json b/drizzle-kit/tests/migrate/migrations/meta/_journal.json new file mode 100644 index 000000000..c836eb194 --- /dev/null +++ b/drizzle-kit/tests/migrate/migrations/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1725358702427, + "tag": "0000_little_blizzard", + "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1725358713033, + "tag": "0001_nebulous_storm", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/drizzle-kit/tests/push/libsql.test.ts b/drizzle-kit/tests/push/libsql.test.ts new file mode 100644 index 000000000..89ec008ca --- /dev/null +++ b/drizzle-kit/tests/push/libsql.test.ts @@ -0,0 +1,1049 @@ +import { createClient } from '@libsql/client'; +import chalk from 'chalk'; +import { sql } from 'drizzle-orm'; +import { + blob, + foreignKey, + getTableConfig, + index, + int, + integer, + numeric, + real, + sqliteTable, + text, + uniqueIndex, +} from 'drizzle-orm/sqlite-core'; +import { diffTestSchemasPushLibSQL } from 'tests/schemaDiffer'; +import { expect, test } from 'vitest'; + +test('nothing changed in schema', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), + email: text('email'), + textJson: text('text_json', { mode: 'json' }), + blobJon: blob('blob_json', { mode: 'json' }), + blobBigInt: blob('blob_bigint', { mode: 'bigint' }), + numeric: numeric('numeric'), + createdAt: integer('created_at', { mode: 'timestamp' }), + createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { length: 255 }), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + isConfirmed: integer('is_confirmed', { + mode: 'boolean', + }), + }); + + const schema1 = { + users, + + customers: sqliteTable('customers', { + id: integer('id').primaryKey(), + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + userId: integer('user_id') + .references(() => users.id) + .notNull(), + }), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL(turso, schema1, schema1, [], false); + expect(sqlStatements.length).toBe(0); + expect(statements.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); +}); + +test('added, dropped index', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), + email: text('email'), + textJson: text('text_json', { mode: 'json' }), + blobJon: blob('blob_json', { mode: 'json' }), + blobBigInt: blob('blob_bigint', { mode: 'bigint' }), + numeric: numeric('numeric'), + createdAt: integer('created_at', { mode: 'timestamp' }), + createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { length: 255 }), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + isConfirmed: integer('is_confirmed', { + mode: 'boolean', + }), + }); + + const schema1 = { + users, + customers: sqliteTable( + 'customers', + { + id: integer('id').primaryKey(), + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + userId: integer('user_id').notNull(), + }, + (table) => ({ + uniqueIndex: uniqueIndex('customers_address_unique').on(table.address), + }), + ), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const schema2 = { + users, + customers: sqliteTable( + 'customers', + { + id: integer('id').primaryKey(), + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + userId: integer('user_id').notNull(), + }, + (table) => ({ + uniqueIndex: uniqueIndex('customers_is_confirmed_unique').on( + table.isConfirmed, + ), + }), + ), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL(turso, schema1, schema2, [], false); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'drop_index', + tableName: 'customers', + data: 'customers_address_unique;address;true;', + schema: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'create_index', + tableName: 'customers', + data: 'customers_is_confirmed_unique;is_confirmed;true;', + schema: '', + internal: { indexes: {} }, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `DROP INDEX IF EXISTS \`customers_address_unique\`;`, + ); + expect(sqlStatements[1]).toBe( + `CREATE UNIQUE INDEX \`customers_is_confirmed_unique\` ON \`customers\` (\`is_confirmed\`);`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('added column not null and without default to table with data', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + age: integer('age').notNull(), + }), + }; + + const table = getTableConfig(schema1.companies); + + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.name.name}") VALUES ('drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.name.name}") VALUES ('turso');`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'companies', + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + referenceData: undefined, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`delete from companies;`); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`companies\` ADD \`age\` integer NOT NULL;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to add not-null ${ + chalk.underline( + 'age', + ) + } column without default value, which contains 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(1); + expect(tablesToTruncate![0]).toBe('companies'); +}); + +test('added column not null and without default to table without data', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + age: integer('age').notNull(), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL(turso, schema1, schema2, [], false); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'companies', + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + referenceData: undefined, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`companies\` ADD \`age\` integer NOT NULL;`, + ); + + expect(infoToPrint!.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop autoincrement. drop column with data', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + }), + }; + + const table = getTableConfig(schema1.companies); + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (1, 'drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (2, 'turso');`, + ]; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ + { + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL +);\n`, + ); + expect(sqlStatements[1]).toBe(`INSERT INTO \`__new_companies\`("id") SELECT "id" FROM \`companies\`;`); + expect(sqlStatements[2]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + + expect(columnsToRemove!.length).toBe(1); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to delete ${ + chalk.underline( + 'name', + ) + } column in companies table with 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('change autoincrement. table is part of foreign key', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const companies1 = sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + }); + const users1 = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').unique(), + companyId: integer('company_id').references(() => companies1.id), + }); + const schema1 = { + companies: companies1, + users: users1, + }; + + const companies2 = sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + }); + const users2 = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').unique(), + companyId: integer('company_id').references(() => companies2.id), + }); + const schema2 = { + companies: companies2, + users: users2, + }; + + const { name: usersTableName } = getTableConfig(users1); + const { name: companiesTableName } = getTableConfig(companies1); + const seedStatements = [ + `INSERT INTO \`${usersTableName}\` ("${schema1.users.name.name}") VALUES ('drizzle');`, + `INSERT INTO \`${usersTableName}\` ("${schema1.users.name.name}") VALUES ('turso');`, + `INSERT INTO \`${companiesTableName}\` ("${schema1.companies.id.name}") VALUES (1);`, + `INSERT INTO \`${companiesTableName}\` ("${schema1.companies.id.name}") VALUES (2);`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ + { + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL +);\n`, + ); + expect(sqlStatements[1]).toBe( + `INSERT INTO \`__new_companies\`("id") SELECT "id" FROM \`companies\`;`, + ); + expect(sqlStatements[2]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop not null, add not null', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + posts: sqliteTable( + 'posts', + { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + userId: int('user_id'), + }, + ), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + posts: sqliteTable( + 'posts', + { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + userId: int('user_id'), + }, + ), + }; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(2); + expect(statements![0]).toStrictEqual({ + columnAutoIncrement: false, + columnDefault: undefined, + columnName: 'name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_drop_notnull', + }); + expect(statements![1]).toStrictEqual({ + columnAutoIncrement: false, + columnDefault: undefined, + columnName: 'name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'posts', + type: 'alter_table_alter_column_set_notnull', + }); + expect(sqlStatements!.length).toBe(2); + expect(sqlStatements![0]).toBe(`ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`); + expect(sqlStatements![1]).toBe(`ALTER TABLE \`posts\` ALTER COLUMN "name" TO "name" text NOT NULL;`); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop table with data', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + posts: sqliteTable( + 'posts', + { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + userId: int('user_id'), + }, + ), + }; + + const schema2 = { + posts: sqliteTable( + 'posts', + { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + userId: int('user_id'), + }, + ), + }; + + const seedStatements = [ + `INSERT INTO \`users\` ("name") VALUES ('drizzle')`, + ]; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + schema: undefined, + tableName: 'users', + type: 'drop_table', + }); + + expect(sqlStatements!.length).toBe(1); + expect(sqlStatements![0]).toBe(`DROP TABLE \`users\`;`); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe(`· You're about to delete ${chalk.underline('users')} table with 1 items`); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(1); + expect(tablesToRemove![0]).toBe('users'); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('recreate table with nested references', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + let users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }); + let subscriptions = sqliteTable('subscriptions', { + id: int('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + customerId: text('customer_id'), + }); + const schema1 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }); + const schema2 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL(turso, schema1, schema2, []); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements![1]).toBe( + `INSERT INTO \`__new_users\`("id", "name", "age") SELECT "id", "name", "age" FROM \`users\`;`, + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('recreate table with added column not null and without default', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + newColumn: text('new_column').notNull(), + }), + }; + + const seedStatements = [ + `INSERT INTO \`users\` ("name", "age") VALUES ('drizzle', 12)`, + `INSERT INTO \`users\` ("name", "age") VALUES ('turso', 12)`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'new_column', + notNull: true, + generated: undefined, + primaryKey: false, + type: 'text', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements[0]).toBe('DELETE FROM \`users\`;'); + expect(sqlStatements![1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer, +\t\`new_column\` text NOT NULL +);\n`); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to add not-null ${ + chalk.underline('new_column') + } column without default value to table, which contains 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(1); + expect(tablesToTruncate![0]).toBe('users'); +}); + +test('set not null with index', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }, (table) => ({ + someIndex: index('users_name_index').on(table.name), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }, (table) => ({ + someIndex: index('users_name_index').on(table.name), + })), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columnAutoIncrement: false, + columnDefault: undefined, + columnName: 'name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_notnull', + }); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_set_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(3); + expect(sqlStatements[0]).toBe( + `DROP INDEX IF EXISTS "users_name_index";`, + ); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text NOT NULL;`, + ); + expect(sqlStatements[2]).toBe( + `CREATE INDEX \`users_name_index\` ON \`users\` (\`name\`);`, + ); + expect(columnsToRemove!.length).toBe(0), expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop not null with two indexes', async (t) => { + const turso = createClient({ + url: ':memory:', + }); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + age: int('age').notNull(), + }, (table) => ({ + someUniqeIndex: uniqueIndex('users_name_unique').on(table.name), + someIndex: index('users_age_index').on(table.age), + })), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: int('age').notNull(), + }, (table) => ({ + someUniqeIndex: uniqueIndex('users_name_unique').on(table.name), + someIndex: index('users_age_index').on(table.age), + })), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushLibSQL( + turso, + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'alter_table_alter_column_drop_notnull', + tableName: 'users', + columnName: 'name', + schema: '', + newDataType: 'text', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }); + + expect(sqlStatements.length).toBe(5); + expect(sqlStatements[0]).toBe( + `DROP INDEX IF EXISTS "users_name_unique";`, + ); + expect(sqlStatements[1]).toBe( + `DROP INDEX IF EXISTS "users_age_index";`, + ); + expect(sqlStatements[2]).toBe( + `ALTER TABLE \`users\` ALTER COLUMN "name" TO "name" text;`, + ); + expect(sqlStatements[3]).toBe( + `CREATE UNIQUE INDEX \`users_name_unique\` ON \`users\` (\`name\`);`, + ); + expect(sqlStatements[4]).toBe( + `CREATE INDEX \`users_age_index\` ON \`users\` (\`age\`);`, + ); + expect(columnsToRemove!.length).toBe(0), expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); diff --git a/drizzle-kit/tests/push/sqlite.test.ts b/drizzle-kit/tests/push/sqlite.test.ts index cf468d3ec..aea5cd379 100644 --- a/drizzle-kit/tests/push/sqlite.test.ts +++ b/drizzle-kit/tests/push/sqlite.test.ts @@ -1,384 +1,630 @@ import Database from 'better-sqlite3'; -import { SQL, sql } from 'drizzle-orm'; -import { blob, foreignKey, int, integer, numeric, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import chalk from 'chalk'; +import { + blob, + foreignKey, + getTableConfig, + int, + integer, + numeric, + real, + sqliteTable, + text, + uniqueIndex, +} from 'drizzle-orm/sqlite-core'; import { diffTestSchemasPushSqlite } from 'tests/schemaDiffer'; import { expect, test } from 'vitest'; -import { DialectSuite, run } from './common'; - -const sqliteSuite: DialectSuite = { - addBasicIndexes: function(context?: any): Promise { - return {} as any; - }, - changeIndexFields: function(context?: any): Promise { - return {} as any; - }, - dropIndex: function(context?: any): Promise { - return {} as any; - }, - - async allTypes() { - const sqlite = new Database(':memory:'); - - const Users = sqliteTable('users', { - id: integer('id').primaryKey().notNull(), - name: text('name').notNull(), - email: text('email'), - textJson: text('text_json', { mode: 'json' }), - blobJon: blob('blob_json', { mode: 'json' }), - blobBigInt: blob('blob_bigint', { mode: 'bigint' }), - numeric: numeric('numeric'), - createdAt: integer('created_at', { mode: 'timestamp' }), - createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), - real: real('real'), - text: text('text', { length: 255 }), - role: text('role', { enum: ['admin', 'user'] }).default('user'), - isConfirmed: integer('is_confirmed', { - mode: 'boolean', - }), - }); - const schema1 = { - Users, +test('nothing changed in schema', async (t) => { + const client = new Database(':memory:'); + + const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), + email: text('email'), + textJson: text('text_json', { mode: 'json' }), + blobJon: blob('blob_json', { mode: 'json' }), + blobBigInt: blob('blob_bigint', { mode: 'bigint' }), + numeric: numeric('numeric'), + createdAt: integer('created_at', { mode: 'timestamp' }), + createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { length: 255 }), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + isConfirmed: integer('is_confirmed', { + mode: 'boolean', + }), + }); - Customers: sqliteTable('customers', { + const schema1 = { + users, + + customers: sqliteTable('customers', { + id: integer('id').primaryKey(), + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + userId: integer('user_id') + .references(() => users.id) + .notNull(), + }), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema1, [], false); + expect(sqlStatements.length).toBe(0); + expect(statements.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); +}); + +test('dropped, added unique index', async (t) => { + const client = new Database(':memory:'); + + const users = sqliteTable('users', { + id: integer('id').primaryKey().notNull(), + name: text('name').notNull(), + email: text('email'), + textJson: text('text_json', { mode: 'json' }), + blobJon: blob('blob_json', { mode: 'json' }), + blobBigInt: blob('blob_bigint', { mode: 'bigint' }), + numeric: numeric('numeric'), + createdAt: integer('created_at', { mode: 'timestamp' }), + createdAtMs: integer('created_at_ms', { mode: 'timestamp_ms' }), + real: real('real'), + text: text('text', { length: 255 }), + role: text('role', { enum: ['admin', 'user'] }).default('user'), + isConfirmed: integer('is_confirmed', { + mode: 'boolean', + }), + }); + + const schema1 = { + users, + + customers: sqliteTable( + 'customers', + { id: integer('id').primaryKey(), - address: text('address').notNull(), + address: text('address').notNull().unique(), isConfirmed: integer('is_confirmed', { mode: 'boolean' }), registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) .notNull() .$defaultFn(() => new Date()), - userId: integer('user_id') - .references(() => Users.id) - .notNull(), + userId: integer('user_id').notNull(), + }, + (table) => ({ + uniqueIndex: uniqueIndex('customers_address_unique').on(table.address), }), + ), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const schema2 = { + users, - Posts: sqliteTable('posts', { + customers: sqliteTable( + 'customers', + { id: integer('id').primaryKey(), - content: text('content'), - authorId: integer('author_id'), - }), - }; - - const { statements } = await diffTestSchemasPushSqlite( - sqlite, - schema1, - schema1, - [], - false, - ); - expect(statements.length).toBe(0); - }, - indexesToBeNotTriggered: function(context?: any): Promise { - return {} as any; - }, - indexesTestCase1: function(context?: any): Promise { - return {} as any; - }, - async case1(): Promise { - const sqlite = new Database(':memory:'); - - const schema1 = { - users: sqliteTable('users', { - id: text('id').notNull().primaryKey(), - firstName: text('first_name').notNull(), - lastName: text('last_name').notNull(), - username: text('username').notNull().unique(), - email: text('email').notNull().unique(), - password: text('password').notNull(), - avatarUrl: text('avatar_url').notNull(), - postsCount: integer('posts_count').notNull().default(0), - followersCount: integer('followers_count').notNull().default(0), - followingsCount: integer('followings_count').notNull().default(0), - createdAt: integer('created_at').notNull(), - }), - }; - - const schema2 = { - users: sqliteTable('users', { - id: text('id').notNull().primaryKey(), - firstName: text('first_name').notNull(), - lastName: text('last_name').notNull(), - username: text('username').notNull().unique(), - email: text('email').notNull().unique(), - password: text('password').notNull(), - avatarUrl: text('avatar_url').notNull(), - followersCount: integer('followers_count').notNull().default(0), - followingsCount: integer('followings_count').notNull().default(0), - createdAt: integer('created_at').notNull(), - }), - }; - - const { statements } = await diffTestSchemasPushSqlite( - sqlite, - schema1, - schema2, - [], - false, - ); - expect(statements.length).toBe(1); - expect(statements[0]).toStrictEqual({ - type: 'alter_table_drop_column', - tableName: 'users', - columnName: 'posts_count', - schema: '', - }); - }, - addNotNull: function(context?: any): Promise { - return {} as any; - }, - addNotNullWithDataNoRollback: function(context?: any): Promise { - return {} as any; - }, - addBasicSequences: function(context?: any): Promise { - return {} as any; - }, - // --- - addGeneratedColumn: async function(context?: any): Promise { - const sqlite = new Database(':memory:'); - - const from = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - }), - }; - const to = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'stored' }, - ), - }), - }; - - const { statements, sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, - from, - to, - [], - ); - - expect(statements).toStrictEqual([]); - expect(sqlStatements).toStrictEqual([]); - }, - addGeneratedToColumn: async function(context?: any): Promise { - const sqlite = new Database(':memory:'); - - const from = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').notNull(), - generatedName1: text('gen_name1'), - }), - }; - const to = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name') + address: text('address').notNull(), + isConfirmed: integer('is_confirmed', { mode: 'boolean' }), + registrationDate: integer('registration_date', { mode: 'timestamp_ms' }) .notNull() - .generatedAlwaysAs((): SQL => sql`${to.users.name} || 'hello'`, { - mode: 'stored', - }), - generatedName1: text('gen_name1').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'virtual' }, + .$defaultFn(() => new Date()), + userId: integer('user_id').notNull(), + }, + (table) => ({ + uniqueIndex: uniqueIndex('customers_is_confirmed_unique').on( + table.isConfirmed, ), }), - }; + ), + + posts: sqliteTable('posts', { + id: integer('id').primaryKey(), + content: text('content'), + authorId: integer('author_id'), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, [], false); + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'drop_index', + tableName: 'customers', + data: 'customers_address_unique;address;true;', + schema: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'create_index', + tableName: 'customers', + data: 'customers_is_confirmed_unique;is_confirmed;true;', + schema: '', + internal: { + indexes: {}, + }, + }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe( + `DROP INDEX IF EXISTS \`customers_address_unique\`;`, + ); + expect(sqlStatements[1]).toBe( + `CREATE UNIQUE INDEX \`customers_is_confirmed_unique\` ON \`customers\` (\`is_confirmed\`);`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('added column not null and without default to table with data', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + age: integer('age').notNull(), + }), + }; + + const table = getTableConfig(schema1.companies); + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.name.name}") VALUES ('drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.name.name}") VALUES ('turso');`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'companies', + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + referenceData: undefined, + }); + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`delete from companies;`); + expect(sqlStatements[1]).toBe( + `ALTER TABLE \`companies\` ADD \`age\` integer NOT NULL;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to add not-null ${ + chalk.underline( + 'age', + ) + } column without default value, which contains 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(1); + expect(tablesToTruncate![0]).toBe('companies'); +}); + +test('added column not null and without default to table without data', async (t) => { + const turso = new Database(':memory:'); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey(), + name: text('name').notNull(), + age: integer('age').notNull(), + }), + }; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(turso, schema1, schema2, [], false); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'sqlite_alter_table_add_column', + tableName: 'companies', + column: { + name: 'age', + type: 'integer', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + referenceData: undefined, + }); + + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + `ALTER TABLE \`companies\` ADD \`age\` integer NOT NULL;`, + ); + + expect(infoToPrint!.length).toBe(0); + expect(columnsToRemove!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop autoincrement. drop column with data', async (t) => { + const turso = new Database(':memory:'); + + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + }), + }; - const { statements, sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, - from, - to, - [], - ); + const table = getTableConfig(schema1.companies); + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (1, 'drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (2, 'turso');`, + ]; - expect(statements).toStrictEqual([ + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + turso, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ { - columnAutoIncrement: false, - columnDefault: undefined, - columnGenerated: { - as: '("name" || \'hello\')', - type: 'virtual', - }, - columnName: 'gen_name1', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', - tableName: 'users', - type: 'alter_table_alter_column_set_generated', + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, }, - ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER TABLE `users` DROP COLUMN `gen_name1`;', - 'ALTER TABLE `users` ADD `gen_name1` text GENERATED ALWAYS AS ("name" || \'hello\') VIRTUAL;', - ]); - - for (const st of sqlStatements) { - sqlite.exec(st); - } - }, - dropGeneratedConstraint: async function(context?: any): Promise { - const sqlite = new Database(':memory:'); - - const from = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'stored' }, - ), - generatedName1: text('gen_name1').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'virtual' }, - ), - }), - }; - const to = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name'), - generatedName1: text('gen_name1'), - }), - }; + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL +);\n`, + ); + expect(sqlStatements[1]).toBe( + `INSERT INTO \`__new_companies\`("id") SELECT "id" FROM \`companies\`;`, + ); + expect(sqlStatements[2]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + + expect(columnsToRemove!.length).toBe(1); + expect(columnsToRemove![0]).toBe('name'); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to delete ${ + chalk.underline( + 'name', + ) + } column in companies table with 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('drop autoincrement. drop column with data with pragma off', async (t) => { + const client = new Database(':memory:'); - const { statements, sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, - from, - to, - [], - ); + client.exec('PRAGMA foreign_keys=OFF;'); - expect(statements).toStrictEqual([ + const users = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + }); + const schema1 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name'), + user_id: integer('user_id').references(() => users.id), + }), + }; + + const schema2 = { + companies: sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + user_id: integer('user_id').references(() => users.id), + }), + }; + + const table = getTableConfig(schema1.companies); + const seedStatements = [ + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (1, 'drizzle');`, + `INSERT INTO \`${table.name}\` ("${schema1.companies.id.name}", "${schema1.companies.name.name}") VALUES (2, 'turso');`, + ]; + + const { + sqlStatements, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ { - columnAutoIncrement: false, - columnDefault: undefined, - columnGenerated: undefined, - columnName: 'gen_name', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', - tableName: 'users', - type: 'alter_table_alter_column_drop_generated', + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, }, { - columnAutoIncrement: false, - columnDefault: undefined, - columnGenerated: undefined, - columnName: 'gen_name1', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', - tableName: 'users', - type: 'alter_table_alter_column_drop_generated', + name: 'user_id', + type: 'integer', + autoincrement: false, + notNull: false, + primaryKey: false, + generated: undefined, }, - ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER TABLE `users` DROP COLUMN `gen_name`;', - 'ALTER TABLE `users` ADD `gen_name` text;', - 'ALTER TABLE `users` DROP COLUMN `gen_name1`;', - 'ALTER TABLE `users` ADD `gen_name1` text;', - ]); - - for (const st of sqlStatements) { - sqlite.exec(st); - } - }, - alterGeneratedConstraint: async function(context?: any): Promise { - const sqlite = new Database(':memory:'); - - const from = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'stored' }, - ), - generatedName1: text('gen_name1').generatedAlwaysAs( - (): SQL => sql`${to.users.name} || 'hello'`, - { mode: 'virtual' }, - ), - }), - }; - const to = { - users: sqliteTable('users', { - id: int('id'), - id2: int('id2'), - name: text('name'), - generatedName: text('gen_name').generatedAlwaysAs( - (): SQL => sql`${to.users.name}`, - { mode: 'stored' }, - ), - generatedName1: text('gen_name1').generatedAlwaysAs( - (): SQL => sql`${to.users.name}`, - { mode: 'virtual' }, - ), - }), - }; + ], + compositePKs: [], + referenceData: [ + { + columnsFrom: [ + 'user_id', + ], + columnsTo: [ + 'id', + ], + name: '', + onDelete: 'no action', + onUpdate: 'no action', + tableFrom: 'companies', + tableTo: 'users', + }, + ], + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(4); + expect(sqlStatements[0]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`user_id\` integer, +\tFOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON UPDATE no action ON DELETE no action +);\n`, + ); + expect(sqlStatements[1]).toBe( + `INSERT INTO \`__new_companies\`("id", "user_id") SELECT "id", "user_id" FROM \`companies\`;`, + ); + expect(sqlStatements[2]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + + expect(columnsToRemove!.length).toBe(1); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to delete ${ + chalk.underline( + 'name', + ) + } column in companies table with 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('change autoincrement. other table references current', async (t) => { + const client = new Database(':memory:'); - const { statements, sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, - from, - to, - [], - ); + const companies1 = sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: true }), + }); + const users1 = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').unique(), + companyId: text('company_id').references(() => companies1.id), + }); + const schema1 = { + companies: companies1, + users: users1, + }; - expect(statements).toStrictEqual([ + const companies2 = sqliteTable('companies', { + id: integer('id').primaryKey({ autoIncrement: false }), + }); + const users2 = sqliteTable('users', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').unique(), + companyId: text('company_id').references(() => companies1.id), + }); + const schema2 = { + companies: companies2, + users: users2, + }; + + const { name: usersTableName } = getTableConfig(users1); + const { name: companiesTableName } = getTableConfig(companies1); + const seedStatements = [ + `INSERT INTO \`${usersTableName}\` ("${schema1.users.name.name}") VALUES ('drizzle');`, + `INSERT INTO \`${usersTableName}\` ("${schema1.users.name.name}") VALUES ('turso');`, + `INSERT INTO \`${companiesTableName}\` ("${schema1.companies.id.name}") VALUES ('1');`, + `INSERT INTO \`${companiesTableName}\` ("${schema1.companies.id.name}") VALUES ('2');`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'recreate_table', + tableName: 'companies', + columns: [ { - columnAutoIncrement: false, - columnDefault: undefined, - columnGenerated: { - as: '("name")', - type: 'virtual', - }, - columnName: 'gen_name1', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', - tableName: 'users', - type: 'alter_table_alter_column_alter_generated', + name: 'id', + type: 'integer', + autoincrement: false, + notNull: true, + primaryKey: true, + generated: undefined, }, - ]); - expect(sqlStatements).toStrictEqual([ - 'ALTER TABLE `users` DROP COLUMN `gen_name1`;', - 'ALTER TABLE `users` ADD `gen_name1` text GENERATED ALWAYS AS ("name") VIRTUAL;', - ]); - - for (const st of sqlStatements) { - sqlite.exec(st); - } - }, - createTableWithGeneratedConstraint: function(context?: any): Promise { - return {} as any; - }, -}; - -run(sqliteSuite); + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe( + `CREATE TABLE \`__new_companies\` ( +\t\`id\` integer PRIMARY KEY NOT NULL +);\n`, + ); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_companies\`("id") SELECT "id" FROM \`companies\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`companies\`;`); + expect(sqlStatements[4]).toBe( + `ALTER TABLE \`__new_companies\` RENAME TO \`companies\`;`, + ); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); test('create table with custom name references', async (t) => { - const sqlite = new Database(':memory:'); + const client = new Database(':memory:'); const users = sqliteTable('users', { id: int('id').primaryKey({ autoIncrement: true }), @@ -424,7 +670,7 @@ test('create table with custom name references', async (t) => { }; const { sqlStatements } = await diffTestSchemasPushSqlite( - sqlite, + client, schema1, schema2, [], @@ -432,3 +678,613 @@ test('create table with custom name references', async (t) => { expect(sqlStatements!.length).toBe(0); }); + +test('drop not null, add not null', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + }), + posts: sqliteTable('posts', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + userId: int('user_id'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + posts: sqliteTable('posts', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + userId: int('user_id'), + }), + }; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, []); + + expect(statements!.length).toBe(2); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + expect(statements![1]).toStrictEqual({ + columns: [ + { + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'name', + notNull: true, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + generated: undefined, + name: 'user_id', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'posts', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(8); + expect(sqlStatements[0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`name\` text +);\n`); + expect(sqlStatements[1]).toBe( + `INSERT INTO \`__new_users\`("id", "name") SELECT "id", "name" FROM \`users\`;`, + ); + expect(sqlStatements[2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(sqlStatements![4]).toBe(`CREATE TABLE \`__new_posts\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`name\` text NOT NULL, +\t\`user_id\` integer +);\n`); + expect(sqlStatements![5]).toBe( + `INSERT INTO \`__new_posts\`("id", "name", "user_id") SELECT "id", "name", "user_id" FROM \`posts\`;`, + ); + expect(sqlStatements![6]).toBe(`DROP TABLE \`posts\`;`); + expect(sqlStatements![7]).toBe( + `ALTER TABLE \`__new_posts\` RENAME TO \`posts\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('rename table and change data type', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('old_users', { + id: int('id').primaryKey({ autoIncrement: true }), + age: text('age'), + }), + }; + + const schema2 = { + users: sqliteTable('new_users', { + id: int('id').primaryKey({ autoIncrement: true }), + age: integer('age'), + }), + }; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, [ + 'public.old_users->public.new_users', + ]); + + expect(statements!.length).toBe(2); + expect(statements![0]).toStrictEqual({ + fromSchema: undefined, + tableNameFrom: 'old_users', + tableNameTo: 'new_users', + toSchema: undefined, + type: 'rename_table', + }); + expect(statements![1]).toStrictEqual({ + columns: [ + { + autoincrement: true, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'new_users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements!.length).toBe(5); + expect(sqlStatements![0]).toBe( + `ALTER TABLE \`old_users\` RENAME TO \`new_users\`;`, + ); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`age\` integer +);\n`); + expect(sqlStatements![2]).toBe( + `INSERT INTO \`__new_new_users\`("id", "age") SELECT "id", "age" FROM \`new_users\`;`, + ); + expect(sqlStatements![3]).toBe(`DROP TABLE \`new_users\`;`); + expect(sqlStatements![4]).toBe( + `ALTER TABLE \`__new_new_users\` RENAME TO \`new_users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('rename column and change data type', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + age: integer('age'), + }), + }; + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, [ + 'public.users.name->public.users.age', + ]); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: true, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY AUTOINCREMENT NOT NULL, +\t\`age\` integer +);\n`); + expect(sqlStatements![1]).toBe( + `INSERT INTO \`__new_users\`("id", "age") SELECT "id", "age" FROM \`users\`;`, + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('recreate table with nested references', async (t) => { + const client = new Database(':memory:'); + + let users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }); + let subscriptions = sqliteTable('subscriptions', { + id: int('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + customerId: text('customer_id'), + }); + const schema1 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }); + const schema2 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references( + () => subscriptions.id, + ), + }), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite(client, schema1, schema2, [ + 'public.users.name->public.users.age', + ]); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements!.length).toBe(6); + expect(sqlStatements[0]).toBe('PRAGMA foreign_keys=OFF;'); + expect(sqlStatements![1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements![2]).toBe( + `INSERT INTO \`__new_users\`("id", "name", "age") SELECT "id", "name", "age" FROM \`users\`;`, + ); + expect(sqlStatements![3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![4]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + expect(sqlStatements[5]).toBe('PRAGMA foreign_keys=ON;'); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); + +test('recreate table with added column not null and without default with data', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + newColumn: text('new_column').notNull(), + }), + }; + + const seedStatements = [ + `INSERT INTO \`users\` ("name", "age") VALUES ('drizzle', 12)`, + `INSERT INTO \`users\` ("name", "age") VALUES ('turso', 12)`, + ]; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + false, + seedStatements, + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'new_column', + notNull: true, + generated: undefined, + primaryKey: false, + type: 'text', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements[0]).toBe('DELETE FROM \`users\`;'); + expect(sqlStatements![1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer, +\t\`new_column\` text NOT NULL +);\n`); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(1); + expect(infoToPrint![0]).toBe( + `· You're about to add not-null ${ + chalk.underline('new_column') + } column without default value to table, which contains 2 items`, + ); + expect(shouldAskForApprove).toBe(true); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(1); + expect(tablesToTruncate![0]).toBe('users'); +}); + +test('recreate table with added column not null and without default with data', async (t) => { + const client = new Database(':memory:'); + + const schema1 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }), + }; + + const schema2 = { + users: sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + newColumn: text('new_column').notNull(), + }), + }; + + const { + statements, + sqlStatements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await diffTestSchemasPushSqlite( + client, + schema1, + schema2, + [], + ); + + expect(statements!.length).toBe(1); + expect(statements![0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + name: 'id', + notNull: true, + generated: undefined, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + name: 'name', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'age', + notNull: false, + generated: undefined, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'new_column', + notNull: true, + generated: undefined, + primaryKey: false, + type: 'text', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements!.length).toBe(4); + expect(sqlStatements![0]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer, +\t\`new_column\` text NOT NULL +);\n`); + expect(sqlStatements[1]).toBe( + 'INSERT INTO `__new_users`("id", "name", "age", "new_column") SELECT "id", "name", "age", "new_column" FROM `users`;', + ); + expect(sqlStatements![2]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements![3]).toBe( + `ALTER TABLE \`__new_users\` RENAME TO \`users\`;`, + ); + + expect(columnsToRemove!.length).toBe(0); + expect(infoToPrint!.length).toBe(0); + expect(shouldAskForApprove).toBe(false); + expect(tablesToRemove!.length).toBe(0); + expect(tablesToTruncate!.length).toBe(0); +}); diff --git a/drizzle-kit/tests/schemaDiffer.ts b/drizzle-kit/tests/schemaDiffer.ts index 4a14d920b..3223ca5e7 100644 --- a/drizzle-kit/tests/schemaDiffer.ts +++ b/drizzle-kit/tests/schemaDiffer.ts @@ -1,11 +1,14 @@ import { PGlite } from '@electric-sql/pglite'; +import { Client } from '@libsql/client/.'; import { Database } from 'better-sqlite3'; import { is } from 'drizzle-orm'; import { MySqlSchema, MySqlTable } from 'drizzle-orm/mysql-core'; import { isPgEnum, isPgSequence, PgEnum, PgSchema, PgSequence, PgTable } from 'drizzle-orm/pg-core'; +import { SingleStoreSchema, SingleStoreTable } from 'drizzle-orm/singlestore-core'; import { SQLiteTable } from 'drizzle-orm/sqlite-core'; import * as fs from 'fs'; import { Connection } from 'mysql2/promise'; +import { libSqlLogSuggestionsAndReturn } from 'src/cli/commands/libSqlPushUtils'; import { columnsResolver, enumsResolver, @@ -17,21 +20,28 @@ import { import { logSuggestionsAndReturn } from 'src/cli/commands/sqlitePushUtils'; import { schemaToTypeScript as schemaToTypeScriptMySQL } from 'src/introspect-mysql'; import { schemaToTypeScript } from 'src/introspect-pg'; +import { schemaToTypeScript as schemaToTypeScriptSingleStore } from 'src/introspect-singlestore'; import { schemaToTypeScript as schemaToTypeScriptSQLite } from 'src/introspect-sqlite'; import { prepareFromMySqlImports } from 'src/serializer/mysqlImports'; import { mysqlSchema, squashMysqlScheme } from 'src/serializer/mysqlSchema'; -import { generateMySqlSnapshot } from 'src/serializer/mysqlSerializer'; -import { fromDatabase as fromMySqlDatabase } from 'src/serializer/mysqlSerializer'; +import { fromDatabase as fromMySqlDatabase, generateMySqlSnapshot } from 'src/serializer/mysqlSerializer'; import { prepareFromPgImports } from 'src/serializer/pgImports'; import { pgSchema, squashPgScheme } from 'src/serializer/pgSchema'; import { fromDatabase, generatePgSnapshot } from 'src/serializer/pgSerializer'; +import { prepareFromSingleStoreImports } from 'src/serializer/singlestoreImports'; +import { singlestoreSchema, squashSingleStoreScheme } from 'src/serializer/singlestoreSchema'; +import { + fromDatabase as fromSingleStoreDatabase, + generateSingleStoreSnapshot, +} from 'src/serializer/singlestoreSerializer'; import { prepareFromSqliteImports } from 'src/serializer/sqliteImports'; import { sqliteSchema, squashSqliteScheme } from 'src/serializer/sqliteSchema'; -import { fromDatabase as fromSqliteDatabase } from 'src/serializer/sqliteSerializer'; -import { generateSqliteSnapshot } from 'src/serializer/sqliteSerializer'; +import { fromDatabase as fromSqliteDatabase, generateSqliteSnapshot } from 'src/serializer/sqliteSerializer'; import { + applyLibSQLSnapshotsDiff, applyMysqlSnapshotsDiff, applyPgSnapshotsDiff, + applySingleStoreSnapshotsDiff, applySqliteSnapshotsDiff, Column, ColumnsResolverInput, @@ -49,6 +59,7 @@ export type PostgresSchema = Record< PgTable | PgEnum | PgSchema | PgSequence >; export type MysqlSchema = Record | MySqlSchema>; +export type SinglestoreSchema = Record | SingleStoreSchema>; export type SqliteSchema = Record>; export const testSchemasResolver = @@ -835,17 +846,133 @@ export const diffTestSchemasMysql = async ( return { sqlStatements, statements }; }; +export const diffTestSchemasSingleStore = async ( + left: SinglestoreSchema, + right: SinglestoreSchema, + renamesArr: string[], + cli: boolean = false, +) => { + const leftTables = Object.values(left).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; + + const rightTables = Object.values(right).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; + + const serialized1 = generateSingleStoreSnapshot(leftTables); + const serialized2 = generateSingleStoreSnapshot(rightTables); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '1', + dialect: 'singlestore', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sch2 = { + version: '1', + dialect: 'singlestore', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn1 = squashSingleStoreScheme(sch1); + const sn2 = squashSingleStoreScheme(sch2); + + const validatedPrev = singlestoreSchema.parse(sch1); + const validatedCur = singlestoreSchema.parse(sch2); + + const renames = new Set(renamesArr); + + if (!cli) { + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( + sn1, + sn2, + testTablesResolver(renames), + testColumnsResolver(renames), + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; + } + + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( + sn1, + sn2, + tablesResolver, + columnsResolver, + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; +}; + +export const applySingleStoreDiffs = async (sn: SingleStoreSchema) => { + const dryRun = { + version: '1', + dialect: 'singlestore', + id: '0', + prevId: '0', + tables: {}, + enums: {}, + schemas: {}, + _meta: { + schemas: {}, + tables: {}, + columns: {}, + }, + } as const; + + const tables = Object.values(sn).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; + + const serialized1 = generateSingleStoreSnapshot(tables); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + + const sch1 = { + version: '1', + dialect: 'singlestore', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sn1 = squashSingleStoreScheme(sch1); + + const validatedPrev = singlestoreSchema.parse(dryRun); + const validatedCur = singlestoreSchema.parse(sch1); + + const { sqlStatements, statements } = await applySingleStoreSnapshotsDiff( + dryRun, + sn1, + testTablesResolver(new Set()), + testColumnsResolver(new Set()), + validatedPrev, + validatedCur, + ); + return { sqlStatements, statements }; +}; + export const diffTestSchemasPushSqlite = async ( client: Database, left: SqliteSchema, right: SqliteSchema, renamesArr: string[], cli: boolean = false, + seedStatements: string[] = [], ) => { const { sqlStatements } = await applySqliteDiffs(left, 'push'); + for (const st of sqlStatements) { client.exec(st); } + + for (const st of seedStatements) { + client.exec(st); + } + // do introspect into PgSchemaInternal const introspectedSchema = await fromSqliteDatabase( { @@ -859,9 +986,9 @@ export const diffTestSchemasPushSqlite = async ( undefined, ); - const leftTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + const rightTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; - const serialized2 = generateSqliteSnapshot(leftTables); + const serialized2 = generateSqliteSnapshot(rightTables); const { version: v1, dialect: d1, ...rest1 } = introspectedSchema; const { version: v2, dialect: d2, ...rest2 } = serialized2; @@ -898,7 +1025,15 @@ export const diffTestSchemasPushSqlite = async ( 'push', ); - const { statementsToExecute } = await logSuggestionsAndReturn( + const { + statementsToExecute, + columnsToRemove, + infoToPrint, + schemasToRemove, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await logSuggestionsAndReturn( { query: async (sql: string, params: any[] = []) => { return client.prepare(sql).bind(params).all() as T[]; @@ -913,7 +1048,16 @@ export const diffTestSchemasPushSqlite = async ( _meta!, ); - return { sqlStatements: statementsToExecute, statements }; + return { + sqlStatements: statementsToExecute, + statements, + columnsToRemove, + infoToPrint, + schemasToRemove, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + }; } else { const { sqlStatements, statements } = await applySqliteSnapshotsDiff( sn1, @@ -928,6 +1072,122 @@ export const diffTestSchemasPushSqlite = async ( } }; +export async function diffTestSchemasPushLibSQL( + client: Client, + left: SqliteSchema, + right: SqliteSchema, + renamesArr: string[], + cli: boolean = false, + seedStatements: string[] = [], +) { + const { sqlStatements } = await applyLibSQLDiffs(left, 'push'); + + for (const st of sqlStatements) { + await client.execute(st); + } + + for (const st of seedStatements) { + await client.execute(st); + } + + const introspectedSchema = await fromSqliteDatabase( + { + query: async (sql: string, params?: any[]) => { + const res = await client.execute({ sql, args: params || [] }); + return res.rows as T[]; + }, + run: async (query: string) => { + await client.execute(query); + }, + }, + undefined, + ); + + const leftTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + + const serialized2 = generateSqliteSnapshot(leftTables); + + const { version: v1, dialect: d1, ...rest1 } = introspectedSchema; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sch2 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn1 = squashSqliteScheme(sch1, 'push'); + const sn2 = squashSqliteScheme(sch2, 'push'); + + const renames = new Set(renamesArr); + + if (!cli) { + const { sqlStatements, statements, _meta } = await applyLibSQLSnapshotsDiff( + sn1, + sn2, + testTablesResolver(renames), + testColumnsResolver(renames), + sch1, + sch2, + 'push', + ); + + const { + statementsToExecute, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + } = await libSqlLogSuggestionsAndReturn( + { + query: async (sql: string, params?: any[]) => { + const res = await client.execute({ sql, args: params || [] }); + return res.rows as T[]; + }, + run: async (query: string) => { + await client.execute(query); + }, + }, + statements, + sn1, + sn2, + _meta!, + ); + + return { + sqlStatements: statementsToExecute, + statements, + columnsToRemove, + infoToPrint, + shouldAskForApprove, + tablesToRemove, + tablesToTruncate, + }; + } else { + const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff( + sn1, + sn2, + tablesResolver, + columnsResolver, + sch1, + sch2, + 'push', + ); + return { sqlStatements, statements }; + } +} + export const applySqliteDiffs = async ( sn: SqliteSchema, action?: 'push' | undefined, @@ -976,6 +1236,54 @@ export const applySqliteDiffs = async ( return { sqlStatements, statements }; }; +export const applyLibSQLDiffs = async ( + sn: SqliteSchema, + action?: 'push' | undefined, +) => { + const dryRun = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + tables: {}, + enums: {}, + schemas: {}, + _meta: { + schemas: {}, + tables: {}, + columns: {}, + }, + } as const; + + const tables = Object.values(sn).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + + const serialized1 = generateSqliteSnapshot(tables); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + + const sch1 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sn1 = squashSqliteScheme(sch1, action); + + const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff( + dryRun, + sn1, + testTablesResolver(new Set()), + testColumnsResolver(new Set()), + dryRun, + sch1, + action, + ); + + return { sqlStatements, statements }; +}; + export const diffTestSchemasSqlite = async ( left: SqliteSchema, right: SqliteSchema, @@ -1036,6 +1344,66 @@ export const diffTestSchemasSqlite = async ( return { sqlStatements, statements }; }; +export const diffTestSchemasLibSQL = async ( + left: SqliteSchema, + right: SqliteSchema, + renamesArr: string[], + cli: boolean = false, +) => { + const leftTables = Object.values(left).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + + const rightTables = Object.values(right).filter((it) => is(it, SQLiteTable)) as SQLiteTable[]; + + const serialized1 = generateSqliteSnapshot(leftTables); + const serialized2 = generateSqliteSnapshot(rightTables); + + const { version: v1, dialect: d1, ...rest1 } = serialized1; + const { version: v2, dialect: d2, ...rest2 } = serialized2; + + const sch1 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest1, + } as const; + + const sch2 = { + version: '6', + dialect: 'sqlite', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn1 = squashSqliteScheme(sch1); + const sn2 = squashSqliteScheme(sch2); + + const renames = new Set(renamesArr); + + if (!cli) { + const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff( + sn1, + sn2, + testTablesResolver(renames), + testColumnsResolver(renames), + sch1, + sch2, + ); + return { sqlStatements, statements }; + } + + const { sqlStatements, statements } = await applyLibSQLSnapshotsDiff( + sn1, + sn2, + tablesResolver, + columnsResolver, + sch1, + sch2, + ); + return { sqlStatements, statements }; +}; + // --- Introspect to file helpers --- export const introspectPgToFile = async ( @@ -1224,6 +1592,89 @@ export const introspectMySQLToFile = async ( }; }; +export const introspectSingleStoreToFile = async ( + client: Connection, + initSchema: SingleStoreSchema, + testName: string, + schema: string, +) => { + // put in db + const { sqlStatements } = await applySingleStoreDiffs(initSchema); + for (const st of sqlStatements) { + await client.query(st); + } + + // introspect to schema + const introspectedSchema = await fromSingleStoreDatabase( + { + query: async (sql: string, params?: any[] | undefined) => { + const res = await client.execute(sql, params); + return res[0] as any; + }, + }, + schema, + ); + + const file = schemaToTypeScriptSingleStore(introspectedSchema, 'camel'); + + fs.writeFileSync(`tests/introspect/singlestore/${testName}.ts`, file.file); + + const response = await prepareFromSingleStoreImports([ + `tests/introspect/singlestore/${testName}.ts`, + ]); + + const afterFileImports = generateSingleStoreSnapshot(response.tables); + + const { version: v2, dialect: d2, ...rest2 } = afterFileImports; + + const sch2 = { + version: '1', + dialect: 'singlestore', + id: '0', + prevId: '0', + ...rest2, + } as const; + + const sn2AfterIm = squashSingleStoreScheme(sch2); + const validatedCurAfterImport = singlestoreSchema.parse(sch2); + + const leftTables = Object.values(initSchema).filter((it) => is(it, SingleStoreTable)) as SingleStoreTable[]; + + const initSnapshot = generateSingleStoreSnapshot(leftTables); + + const { version: initV, dialect: initD, ...initRest } = initSnapshot; + + const initSch = { + version: '1', + dialect: 'singlestore', + id: '0', + prevId: '0', + ...initRest, + } as const; + + const initSn = squashSingleStoreScheme(initSch); + const validatedCur = singlestoreSchema.parse(initSch); + + const { + sqlStatements: afterFileSqlStatements, + statements: afterFileStatements, + } = await applySingleStoreSnapshotsDiff( + sn2AfterIm, + initSn, + testTablesResolver(new Set()), + testColumnsResolver(new Set()), + validatedCurAfterImport, + validatedCur, + ); + + fs.rmSync(`tests/introspect/singlestore/${testName}.ts`); + + return { + sqlStatements: afterFileSqlStatements, + statements: afterFileStatements, + }; +}; + export const introspectSQLiteToFile = async ( client: Database, initSchema: SqliteSchema, diff --git a/drizzle-kit/tests/singlestore-generated.test.ts b/drizzle-kit/tests/singlestore-generated.test.ts new file mode 100644 index 000000000..8944f3b21 --- /dev/null +++ b/drizzle-kit/tests/singlestore-generated.test.ts @@ -0,0 +1,1290 @@ +import { SQL, sql } from 'drizzle-orm'; +import { int, singlestoreTable, text } from 'drizzle-orm/singlestore-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasSingleStore } from './schemaDiffer'; + +test('generated as callback: add column with generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${to.users.name} || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + column: { + generated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + autoincrement: false, + name: 'gen_name', + notNull: false, + primaryKey: false, + type: 'text', + }, + schema: '', + tableName: 'users', + type: 'alter_table_add_column', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as callback: add generated constraint to an exisiting column as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs((): SQL => sql`${from.users.name} || 'to add'`, { + mode: 'stored', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'stored', + }, + columnAutoIncrement: false, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + ]); +}); + +test('generated as callback: add generated constraint to an exisiting column as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs((): SQL => sql`${from.users.name} || 'to add'`, { + mode: 'virtual', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + ]); +}); + +test('generated as callback: drop generated constraint as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name} || 'to delete'`, + { mode: 'stored' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'stored', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` MODIFY COLUMN `gen_name` text;', + ]); +}); + +test('generated as callback: drop generated constraint as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name} || 'to delete'`, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'virtual', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + tableName: 'users', + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + 'ALTER TABLE `users` ADD `gen_name` text;', + ]); +}); + +test('generated as callback: change generated constraint type from virtual to stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name}`, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${to.users.name} || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as callback: change generated constraint type from stored to virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name}`, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${to.users.name} || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +test('generated as callback: change generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${from.users.name}`, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + (): SQL => sql`${to.users.name} || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +// --- + +test('generated as sql: add column with generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + column: { + generated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + autoincrement: false, + name: 'gen_name', + notNull: false, + primaryKey: false, + type: 'text', + }, + schema: '', + tableName: 'users', + type: 'alter_table_add_column', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as sql: add generated constraint to an exisiting column as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs(sql`\`users\`.\`name\` || 'to add'`, { + mode: 'stored', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'stored', + }, + columnAutoIncrement: false, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + ]); +}); + +test('generated as sql: add generated constraint to an exisiting column as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs(sql`\`users\`.\`name\` || 'to add'`, { + mode: 'virtual', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + ]); +}); + +test('generated as sql: drop generated constraint as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'to delete'`, + { mode: 'stored' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'stored', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` MODIFY COLUMN `gen_name` text;', + ]); +}); + +test('generated as sql: drop generated constraint as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'to delete'`, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'virtual', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + tableName: 'users', + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + 'ALTER TABLE `users` ADD `gen_name` text;', + ]); +}); + +test('generated as sql: change generated constraint type from virtual to stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\``, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as sql: change generated constraint type from stored to virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\``, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +test('generated as sql: change generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\``, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + sql`\`users\`.\`name\` || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +// --- + +test('generated as string: add column with generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + column: { + generated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + autoincrement: false, + name: 'gen_name', + notNull: false, + primaryKey: false, + type: 'text', + }, + schema: '', + tableName: 'users', + type: 'alter_table_add_column', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as string: add generated constraint to an exisiting column as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs(`\`users\`.\`name\` || 'to add'`, { + mode: 'stored', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'stored', + }, + columnAutoIncrement: false, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + "ALTER TABLE `users` MODIFY COLUMN `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') STORED;", + ]); +}); + +test('generated as string: add generated constraint to an exisiting column as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').notNull(), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name') + .notNull() + .generatedAlwaysAs(`\`users\`.\`name\` || 'to add'`, { + mode: 'virtual', + }), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'to add'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: true, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_set_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text NOT NULL GENERATED ALWAYS AS (`users`.`name` || 'to add') VIRTUAL;", + ]); +}); + +test('generated as string: drop generated constraint as stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'to delete'`, + { mode: 'stored' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'stored', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` MODIFY COLUMN `gen_name` text;', + ]); +}); + +test('generated as string: drop generated constraint as virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'to delete'`, + { mode: 'virtual' }, + ), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName1: text('gen_name'), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: undefined, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + oldColumn: { + autoincrement: false, + generated: { + as: "`users`.`name` || 'to delete'", + type: 'virtual', + }, + name: 'gen_name', + notNull: false, + onUpdate: undefined, + primaryKey: false, + type: 'text', + }, + tableName: 'users', + type: 'alter_table_alter_column_drop_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` DROP COLUMN `gen_name`;', + 'ALTER TABLE `users` ADD `gen_name` text;', + ]); +}); + +test('generated as string: change generated constraint type from virtual to stored', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs(`\`users\`.\`name\``, { + mode: 'virtual', + }), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'hello'`, + { mode: 'stored' }, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'stored', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') STORED;", + ]); +}); + +test('generated as string: change generated constraint type from stored to virtual', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs(`\`users\`.\`name\``), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); + +test('generated as string: change generated constraint', async () => { + const from = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs(`\`users\`.\`name\``), + }), + }; + const to = { + users: singlestoreTable('users', { + id: int('id'), + id2: int('id2'), + name: text('name'), + generatedName: text('gen_name').generatedAlwaysAs( + `\`users\`.\`name\` || 'hello'`, + ), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSingleStore( + from, + to, + [], + ); + + expect(statements).toStrictEqual([ + { + columnAutoIncrement: false, + columnDefault: undefined, + columnGenerated: { + as: "`users`.`name` || 'hello'", + type: 'virtual', + }, + columnName: 'gen_name', + columnNotNull: false, + columnOnUpdate: undefined, + columnPk: false, + newDataType: 'text', + schema: '', + tableName: 'users', + type: 'alter_table_alter_column_alter_generated', + }, + ]); + expect(sqlStatements).toStrictEqual([ + 'ALTER TABLE `users` drop column `gen_name`;', + "ALTER TABLE `users` ADD `gen_name` text GENERATED ALWAYS AS (`users`.`name` || 'hello') VIRTUAL;", + ]); +}); diff --git a/drizzle-kit/tests/singlestore-schemas.test.ts b/drizzle-kit/tests/singlestore-schemas.test.ts new file mode 100644 index 000000000..db9fe0480 --- /dev/null +++ b/drizzle-kit/tests/singlestore-schemas.test.ts @@ -0,0 +1,155 @@ +import { singlestoreSchema, singlestoreTable } from 'drizzle-orm/singlestore-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasSingleStore } from './schemaDiffer'; + +// We don't manage databases(schemas) in MySQL with Drizzle Kit +test('add schema #1', async () => { + const to = { + devSchema: singlestoreSchema('dev'), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(0); +}); + +test('add schema #2', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + }; + const to = { + devSchema: singlestoreSchema('dev'), + devSchema2: singlestoreSchema('dev2'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, []); + + expect(statements.length).toBe(0); +}); + +test('delete schema #1', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, {}, []); + + expect(statements.length).toBe(0); +}); + +test('delete schema #2', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + devSchema2: singlestoreSchema('dev2'), + }; + const to = { + devSchema: singlestoreSchema('dev'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, []); + + expect(statements.length).toBe(0); +}); + +test('rename schema #1', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + }; + const to = { + devSchema2: singlestoreSchema('dev2'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev->dev2']); + + expect(statements.length).toBe(0); +}); + +test('rename schema #2', async () => { + const from = { + devSchema: singlestoreSchema('dev'), + devSchema1: singlestoreSchema('dev1'), + }; + const to = { + devSchema: singlestoreSchema('dev'), + devSchema2: singlestoreSchema('dev2'), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); + +test('add table to schema #1', async () => { + const dev = singlestoreSchema('dev'); + const from = {}; + const to = { + dev, + users: dev.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); + +test('add table to schema #2', async () => { + const dev = singlestoreSchema('dev'); + const from = { dev }; + const to = { + dev, + users: dev.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); + +test('add table to schema #3', async () => { + const dev = singlestoreSchema('dev'); + const from = { dev }; + const to = { + dev, + usersInDev: dev.table('users', {}), + users: singlestoreTable('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + uniqueConstraints: [], + internals: { + tables: {}, + indexes: {}, + }, + compositePkName: '', + compositePKs: [], + }); +}); + +test('remove table from schema #1', async () => { + const dev = singlestoreSchema('dev'); + const from = { dev, users: dev.table('users', {}) }; + const to = { + dev, + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); + +test('remove table from schema #2', async () => { + const dev = singlestoreSchema('dev'); + const from = { dev, users: dev.table('users', {}) }; + const to = {}; + + const { statements } = await diffTestSchemasSingleStore(from, to, ['dev1->dev2']); + + expect(statements.length).toBe(0); +}); diff --git a/drizzle-kit/tests/singlestore.test.ts b/drizzle-kit/tests/singlestore.test.ts new file mode 100644 index 000000000..63abf1755 --- /dev/null +++ b/drizzle-kit/tests/singlestore.test.ts @@ -0,0 +1,578 @@ +import { sql } from 'drizzle-orm'; +import { + index, + json, + primaryKey, + serial, + singlestoreSchema, + singlestoreTable, + text, + uniqueIndex, +} from 'drizzle-orm/singlestore-core'; +import { expect, test } from 'vitest'; +import { diffTestSchemasSingleStore } from './schemaDiffer'; + +test('add table #1', async () => { + const to = { + users: singlestoreTable('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + compositePKs: [], + internals: { + tables: {}, + indexes: {}, + }, + uniqueConstraints: [], + compositePkName: '', + }); +}); + +test('add table #2', async () => { + const to = { + users: singlestoreTable('users', { + id: serial('id').primaryKey(), + }), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [ + { + name: 'id', + notNull: true, + primaryKey: false, + type: 'serial', + autoincrement: true, + }, + ], + compositePKs: ['users_id;id'], + compositePkName: 'users_id', + uniqueConstraints: [], + internals: { + tables: {}, + indexes: {}, + }, + }); +}); + +test('add table #3', async () => { + const to = { + users: singlestoreTable( + 'users', + { + id: serial('id'), + }, + (t) => { + return { + pk: primaryKey({ + name: 'users_pk', + columns: [t.id], + }), + }; + }, + ), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [ + { + name: 'id', + notNull: true, + primaryKey: false, + type: 'serial', + autoincrement: true, + }, + ], + compositePKs: ['users_pk;id'], + uniqueConstraints: [], + compositePkName: 'users_pk', + internals: { + tables: {}, + indexes: {}, + }, + }); +}); + +test('add table #4', async () => { + const to = { + users: singlestoreTable('users', {}), + posts: singlestoreTable('posts', {}), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + internals: { + tables: {}, + indexes: {}, + }, + compositePKs: [], + uniqueConstraints: [], + compositePkName: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'create_table', + tableName: 'posts', + schema: undefined, + columns: [], + compositePKs: [], + internals: { + tables: {}, + indexes: {}, + }, + uniqueConstraints: [], + compositePkName: '', + }); +}); + +test('add table #5', async () => { + const schema = singlestoreSchema('folder'); + const from = { + schema, + }; + + const to = { + schema, + users: schema.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, []); + + expect(statements.length).toBe(0); +}); + +test('add table #6', async () => { + const from = { + users1: singlestoreTable('users1', {}), + }; + + const to = { + users2: singlestoreTable('users2', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, []); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users2', + schema: undefined, + columns: [], + internals: { + tables: {}, + indexes: {}, + }, + compositePKs: [], + uniqueConstraints: [], + compositePkName: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'drop_table', + tableName: 'users1', + schema: undefined, + }); +}); + +test('add table #7', async () => { + const from = { + users1: singlestoreTable('users1', {}), + }; + + const to = { + users: singlestoreTable('users', {}), + users2: singlestoreTable('users2', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'public.users1->public.users2', + ]); + + expect(statements.length).toBe(2); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + compositePKs: [], + uniqueConstraints: [], + internals: { + tables: {}, + indexes: {}, + }, + compositePkName: '', + }); + expect(statements[1]).toStrictEqual({ + type: 'rename_table', + tableNameFrom: 'users1', + tableNameTo: 'users2', + fromSchema: undefined, + toSchema: undefined, + }); +}); + +test('add schema + table #1', async () => { + const schema = singlestoreSchema('folder'); + + const to = { + schema, + users: schema.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore({}, to, []); + + expect(statements.length).toBe(0); +}); + +test('change schema with tables #1', async () => { + const schema = singlestoreSchema('folder'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema, + users: schema.table('users', {}), + }; + const to = { + schema2, + users: schema2.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder->folder2', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #1', async () => { + const schema = singlestoreSchema('folder'); + const from = { + schema, + users: singlestoreTable('users', {}), + }; + const to = { + schema, + users: schema.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'public.users->folder.users', + ]); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'drop_table', + tableName: 'users', + schema: undefined, + }); +}); + +test('change table schema #2', async () => { + const schema = singlestoreSchema('folder'); + const from = { + schema, + users: schema.table('users', {}), + }; + const to = { + schema, + users: singlestoreTable('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder.users->public.users', + ]); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + type: 'create_table', + tableName: 'users', + schema: undefined, + columns: [], + uniqueConstraints: [], + compositePkName: '', + compositePKs: [], + internals: { + tables: {}, + indexes: {}, + }, + }); +}); + +test('change table schema #3', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, + schema2, + users: schema1.table('users', {}), + }; + const to = { + schema1, + schema2, + users: schema2.table('users', {}), + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1.users->folder2.users', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #4', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, + users: schema1.table('users', {}), + }; + const to = { + schema1, + schema2, // add schema + users: schema2.table('users', {}), // move table + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1.users->folder2.users', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #5', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, // remove schema + users: schema1.table('users', {}), + }; + const to = { + schema2, // add schema + users: schema2.table('users', {}), // move table + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1.users->folder2.users', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #5', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, + schema2, + users: schema1.table('users', {}), + }; + const to = { + schema1, + schema2, + users: schema2.table('users2', {}), // rename and move table + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1.users->folder2.users2', + ]); + + expect(statements.length).toBe(0); +}); + +test('change table schema #6', async () => { + const schema1 = singlestoreSchema('folder1'); + const schema2 = singlestoreSchema('folder2'); + const from = { + schema1, + users: schema1.table('users', {}), + }; + const to = { + schema2, // rename schema + users: schema2.table('users2', {}), // rename table + }; + + const { statements } = await diffTestSchemasSingleStore(from, to, [ + 'folder1->folder2', + 'folder2.users->folder2.users2', + ]); + + expect(statements.length).toBe(0); +}); + +test('add table #10', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default({}), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + "CREATE TABLE `table` (\n\t`json` json DEFAULT ('{}')\n);\n", + ); +}); + +test('add table #11', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default([]), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + "CREATE TABLE `table` (\n\t`json` json DEFAULT ('[]')\n);\n", + ); +}); + +test('add table #12', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default([1, 2, 3]), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + "CREATE TABLE `table` (\n\t`json` json DEFAULT ('[1,2,3]')\n);\n", + ); +}); + +test('add table #13', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default({ key: 'value' }), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + 'CREATE TABLE `table` (\n\t`json` json DEFAULT (\'{"key":"value"}\')\n);\n', + ); +}); + +test('add table #14', async () => { + const to = { + users: singlestoreTable('table', { + json: json('json').default({ + key: 'value', + arr: [1, 2, 3], + }), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore({}, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe( + 'CREATE TABLE `table` (\n\t`json` json DEFAULT (\'{"key":"value","arr":[1,2,3]}\')\n);\n', + ); +}); + +// TODO: add bson type tests + +// TODO: add blob type tests + +// TODO: add uuid type tests + +// TODO: add guid type tests + +// TODO: add vector type tests + +// TODO: add geopoint type tests + +test('drop index', async () => { + const from = { + users: singlestoreTable( + 'table', + { + name: text('name'), + }, + (t) => { + return { + idx: index('name_idx').on(t.name), + }; + }, + ), + }; + + const to = { + users: singlestoreTable('table', { + name: text('name'), + }), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore(from, to, []); + expect(sqlStatements.length).toBe(1); + expect(sqlStatements[0]).toBe('DROP INDEX `name_idx` ON `table`;'); +}); + +test('add table with indexes', async () => { + const from = {}; + + const to = { + users: singlestoreTable( + 'users', + { + id: serial('id').primaryKey(), + name: text('name'), + email: text('email'), + }, + (t) => ({ + uniqueExpr: uniqueIndex('uniqueExpr').on(sql`(lower(${t.email}))`), + indexExpr: index('indexExpr').on(sql`(lower(${t.email}))`), + indexExprMultiple: index('indexExprMultiple').on( + sql`(lower(${t.email}))`, + sql`(lower(${t.email}))`, + ), + + uniqueCol: uniqueIndex('uniqueCol').on(t.email), + indexCol: index('indexCol').on(t.email), + indexColMultiple: index('indexColMultiple').on(t.email, t.email), + + indexColExpr: index('indexColExpr').on( + sql`(lower(${t.email}))`, + t.email, + ), + }), + ), + }; + + const { sqlStatements } = await diffTestSchemasSingleStore(from, to, []); + expect(sqlStatements.length).toBe(6); + expect(sqlStatements).toStrictEqual([ + `CREATE TABLE \`users\` (\n\t\`id\` serial AUTO_INCREMENT NOT NULL,\n\t\`name\` text,\n\t\`email\` text,\n\tCONSTRAINT \`users_id\` PRIMARY KEY(\`id\`),\n\tCONSTRAINT \`uniqueExpr\` UNIQUE((lower(\`email\`))),\n\tCONSTRAINT \`uniqueCol\` UNIQUE(\`email\`) +); +`, + 'CREATE INDEX `indexExpr` ON `users` ((lower(`email`)));', + 'CREATE INDEX `indexExprMultiple` ON `users` ((lower(`email`)),(lower(`email`)));', + 'CREATE INDEX `indexCol` ON `users` (`email`);', + 'CREATE INDEX `indexColMultiple` ON `users` (`email`,`email`);', + 'CREATE INDEX `indexColExpr` ON `users` ((lower(`email`)),`email`);', + ]); +}); diff --git a/drizzle-kit/tests/sqlite-columns.test.ts b/drizzle-kit/tests/sqlite-columns.test.ts index 8a258072a..04dbb940c 100644 --- a/drizzle-kit/tests/sqlite-columns.test.ts +++ b/drizzle-kit/tests/sqlite-columns.test.ts @@ -8,6 +8,7 @@ import { sqliteTable, text, } from 'drizzle-orm/sqlite-core'; +import { JsonCreateIndexStatement, JsonRecreateTableStatement } from 'src/jsonStatements'; import { expect, test } from 'vitest'; import { diffTestSchemasSqlite } from './schemaDiffer'; @@ -223,7 +224,7 @@ test('add columns #5', async (t) => { const { statements } = await diffTestSchemasSqlite(schema1, schema2, []); // TODO: Fix here - expect(statements.length).toBe(2); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ type: 'sqlite_alter_table_add_column', tableName: 'users', @@ -332,12 +333,38 @@ test('add foreign key #1', async (t) => { const { statements } = await diffTestSchemasSqlite(schema1, schema2, []); expect(statements.length).toBe(1); - expect(statements[0]).toStrictEqual({ - type: 'create_reference', - tableName: 'users', - schema: '', - data: 'users_report_to_users_id_fk;users;report_to;users;id;no action;no action', - }); + expect(statements[0]).toStrictEqual( + { + type: 'recreate_table', + columns: [{ + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, { + autoincrement: false, + generated: undefined, + name: 'report_to', + notNull: false, + primaryKey: false, + type: 'integer', + }], + compositePKs: [], + referenceData: [{ + columnsFrom: ['report_to'], + columnsTo: ['id'], + name: 'users_report_to_users_id_fk', + tableFrom: 'users', + tableTo: 'users', + onDelete: 'no action', + onUpdate: 'no action', + }], + tableName: 'users', + uniqueConstraints: [], + } as JsonRecreateTableStatement, + ); }); test('add foreign key #2', async (t) => { @@ -371,11 +398,35 @@ test('add foreign key #2', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'create_reference', + type: 'recreate_table', + columns: [{ + autoincrement: true, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, { + autoincrement: false, + generated: undefined, + name: 'report_to', + notNull: false, + primaryKey: false, + type: 'integer', + }], + compositePKs: [], + referenceData: [{ + columnsFrom: ['report_to'], + columnsTo: ['id'], + name: 'reportee_fk', + tableFrom: 'users', + tableTo: 'users', + onDelete: 'no action', + onUpdate: 'no action', + }], tableName: 'users', - schema: '', - data: 'reportee_fk;users;report_to;users;id;no action;no action', - }); + uniqueConstraints: [], + } as JsonRecreateTableStatement); }); test('alter column change name #1', async (t) => { @@ -513,9 +564,26 @@ test('alter table add composite pk', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'create_composite_pk', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'id1', + notNull: false, + primaryKey: false, + type: 'integer', + }, { + autoincrement: false, + generated: undefined, + name: 'id2', + notNull: false, + primaryKey: false, + type: 'integer', + }], + compositePKs: [['id1', 'id2']], + referenceData: [], tableName: 'table', - data: 'id1,id2', + uniqueConstraints: [], }); }); @@ -540,16 +608,19 @@ test('alter column drop not null', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'alter_table_alter_column_drop_notnull', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }], + compositePKs: [], + referenceData: [], tableName: 'table', - columnName: 'name', - schema: '', - newDataType: 'text', - columnDefault: undefined, - columnOnUpdate: undefined, - columnNotNull: false, - columnAutoIncrement: false, - columnPk: false, + uniqueConstraints: [], }); }); @@ -574,16 +645,19 @@ test('alter column add not null', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'alter_table_alter_column_set_notnull', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: true, + primaryKey: false, + type: 'text', + }], + compositePKs: [], + referenceData: [], tableName: 'table', - columnName: 'name', - schema: '', - newDataType: 'text', - columnDefault: undefined, - columnOnUpdate: undefined, - columnNotNull: true, - columnAutoIncrement: false, - columnPk: false, + uniqueConstraints: [], }); }); @@ -608,16 +682,20 @@ test('alter column add default', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'alter_table_alter_column_set_default', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + default: "'dan'", + }], + compositePKs: [], + referenceData: [], tableName: 'table', - columnName: 'name', - schema: '', - newDataType: 'text', - columnNotNull: false, - columnOnUpdate: undefined, - columnAutoIncrement: false, - newDefaultValue: "'dan'", - columnPk: false, + uniqueConstraints: [], }); }); @@ -642,16 +720,19 @@ test('alter column drop default', async (t) => { expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - type: 'alter_table_alter_column_drop_default', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }], + compositePKs: [], + referenceData: [], tableName: 'table', - columnName: 'name', - schema: '', - newDataType: 'text', - columnNotNull: false, - columnOnUpdate: undefined, - columnDefault: undefined, - columnAutoIncrement: false, - columnPk: false, + uniqueConstraints: [], }); }); @@ -674,32 +755,84 @@ test('alter column add default not null', async (t) => { [], ); - expect(statements.length).toBe(2); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - columnAutoIncrement: false, - columnName: 'name', - columnNotNull: true, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - newDefaultValue: "'dan'", - schema: '', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: true, + primaryKey: false, + type: 'text', + default: "'dan'", + }], + compositePKs: [], + referenceData: [], tableName: 'table', - type: 'alter_table_alter_column_set_default', + uniqueConstraints: [], }); +}); +test('alter column add default not null with indexes', async (t) => { + const from = { + users: sqliteTable('table', { + name: text('name'), + }, (table) => ({ + someIndex: index('index_name').on(table.name), + })), + }; + + const to = { + users: sqliteTable('table', { + name: text('name').notNull().default('dan'), + }, (table) => ({ + someIndex: index('index_name').on(table.name), + })), + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite( + from, + to, + [], + ); + + expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ - columnAutoIncrement: false, - columnName: 'name', - columnNotNull: true, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - newDefaultValue: "'dan'", + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: true, + primaryKey: false, + type: 'text', + default: "'dan'", + }], + compositePKs: [], + referenceData: [], + tableName: 'table', + uniqueConstraints: [], + }); + expect(statements[1]).toStrictEqual({ + data: 'index_name;name;false;', schema: '', tableName: 'table', - type: 'alter_table_alter_column_set_default', + type: 'create_index', + internal: undefined, }); + expect(sqlStatements.length).toBe(7); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_table\` ( +\t\`name\` text DEFAULT 'dan' NOT NULL +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_table\`("name") SELECT "name" FROM \`table\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`table\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_table\` RENAME TO \`table\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); + expect(sqlStatements[6]).toBe(`CREATE INDEX \`index_name\` ON \`table\` (\`name\`);`); }); test('alter column drop default not null', async (t) => { @@ -721,30 +854,162 @@ test('alter column drop default not null', async (t) => { [], ); - expect(statements.length).toBe(2); + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ - columnAutoIncrement: false, - columnDefault: undefined, - columnName: 'name', - columnNotNull: false, - columnOnUpdate: undefined, - columnPk: false, - newDataType: 'text', - schema: '', + type: 'recreate_table', + columns: [{ + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }], + compositePKs: [], + referenceData: [], tableName: 'table', - type: 'alter_table_alter_column_drop_default', + uniqueConstraints: [], }); + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_table\` ( +\t\`name\` text +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_table\`("name") SELECT "name" FROM \`table\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`table\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_table\` RENAME TO \`table\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); +}); +test('alter column drop generated', async (t) => { + const from = { + users: sqliteTable('table', { + id: int('id').primaryKey().notNull(), + name: text('name').generatedAlwaysAs('drizzle is the best').notNull(), + }), + }; + + const to = { + users: sqliteTable('table', { + id: int('id').primaryKey().notNull(), + name: text('name').notNull(), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite( + from, + to, + [], + ); + + expect(statements.length).toBe(1); expect(statements[0]).toStrictEqual({ columnAutoIncrement: false, columnDefault: undefined, + columnGenerated: undefined, columnName: 'name', - columnNotNull: false, + columnNotNull: true, columnOnUpdate: undefined, columnPk: false, newDataType: 'text', schema: '', tableName: 'table', - type: 'alter_table_alter_column_drop_default', + type: 'alter_table_alter_column_drop_generated', }); + + expect(sqlStatements.length).toBe(2); + expect(sqlStatements[0]).toBe(`ALTER TABLE \`table\` DROP COLUMN \`name\`;`); + expect(sqlStatements[1]).toBe(`ALTER TABLE \`table\` ADD \`name\` text NOT NULL;`); +}); + +test('recreate table with nested references', async (t) => { + let users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: true }), + name: text('name'), + age: integer('age'), + }); + let subscriptions = sqliteTable('subscriptions', { + id: int('id').primaryKey({ autoIncrement: true }), + userId: integer('user_id').references(() => users.id), + customerId: text('customer_id'), + }); + const schema1 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references(() => subscriptions.id), + }), + }; + + users = sqliteTable('users', { + id: int('id').primaryKey({ autoIncrement: false }), + name: text('name'), + age: integer('age'), + }); + const schema2 = { + users: users, + subscriptions: subscriptions, + subscriptionMetadata: sqliteTable('subscriptions_metadata', { + id: int('id').primaryKey({ autoIncrement: true }), + subscriptionId: text('subscription_id').references(() => subscriptions.id), + }), + }; + + const { statements, sqlStatements } = await diffTestSchemasSqlite( + schema1, + schema2, + [], + ); + + expect(statements.length).toBe(1); + expect(statements[0]).toStrictEqual({ + columns: [ + { + autoincrement: false, + generated: undefined, + name: 'id', + notNull: true, + primaryKey: true, + type: 'integer', + }, + { + autoincrement: false, + generated: undefined, + name: 'name', + notNull: false, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + generated: undefined, + name: 'age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [], + tableName: 'users', + type: 'recreate_table', + uniqueConstraints: [], + }); + + expect(sqlStatements.length).toBe(6); + expect(sqlStatements[0]).toBe(`PRAGMA foreign_keys=OFF;`); + expect(sqlStatements[1]).toBe(`CREATE TABLE \`__new_users\` ( +\t\`id\` integer PRIMARY KEY NOT NULL, +\t\`name\` text, +\t\`age\` integer +);\n`); + expect(sqlStatements[2]).toBe( + `INSERT INTO \`__new_users\`("id", "name", "age") SELECT "id", "name", "age" FROM \`users\`;`, + ); + expect(sqlStatements[3]).toBe(`DROP TABLE \`users\`;`); + expect(sqlStatements[4]).toBe(`ALTER TABLE \`__new_users\` RENAME TO \`users\`;`); + expect(sqlStatements[5]).toBe(`PRAGMA foreign_keys=ON;`); }); diff --git a/drizzle-kit/tests/sqlite-tables.test.ts b/drizzle-kit/tests/sqlite-tables.test.ts index d7781f150..aa44908ba 100644 --- a/drizzle-kit/tests/sqlite-tables.test.ts +++ b/drizzle-kit/tests/sqlite-tables.test.ts @@ -162,6 +162,13 @@ test('add table #7', async () => { expect(statements.length).toBe(2); expect(statements[0]).toStrictEqual({ + type: 'rename_table', + tableNameFrom: 'users1', + tableNameTo: 'users2', + fromSchema: undefined, + toSchema: undefined, + }); + expect(statements[1]).toStrictEqual({ type: 'sqlite_create_table', tableName: 'users', columns: [], @@ -169,13 +176,6 @@ test('add table #7', async () => { uniqueConstraints: [], referenceData: [], }); - expect(statements[1]).toStrictEqual({ - type: 'rename_table', - tableNameFrom: 'users1', - tableNameTo: 'users2', - fromSchema: undefined, - toSchema: undefined, - }); }); test('add table #8', async () => { diff --git a/drizzle-kit/tests/statements-combiner/libsql-statements-combiner.test.ts b/drizzle-kit/tests/statements-combiner/libsql-statements-combiner.test.ts new file mode 100644 index 000000000..47447decd --- /dev/null +++ b/drizzle-kit/tests/statements-combiner/libsql-statements-combiner.test.ts @@ -0,0 +1,1749 @@ +import { JsonAddColumnStatement, JsonSqliteAddColumnStatement, JsonStatement } from 'src/jsonStatements'; +import { SQLiteSchemaSquashed } from 'src/serializer/sqliteSchema'; +import { SQLiteAlterTableAddColumnConvertor } from 'src/sqlgenerator'; +import { libSQLCombineStatements } from 'src/statementCombiner'; +import { expect, test } from 'vitest'; + +/** + * ! before: + * + * user: { + * id INT; + * first_name INT; + * iq INT; + * PRIMARY KEY (id, iq) + * INDEXES: { + * UNIQUE id; + * } + * } + * + * ! after: + * + * new_user: { + * id INT; + * first_name INT; + * iq INT; + * PRIMARY KEY (id, iq) + * INDEXES: {} + * } + * + * rename table and drop unique index + * expect to get "rename_table" statement and then "recreate_table" + */ +test(`rename table and drop index`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'rename_table', + fromSchema: '', + toSchema: '', + tableNameFrom: 'user', + tableNameTo: 'new_user', + }, + { + type: 'drop_index', + tableName: 'new_user', + data: 'user_first_name_unique;first_name;true;', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + first_name: { + name: 'first_name', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: { + user_first_name_unique: 'user_first_name_unique;first_name;true;', + }, + foreignKeys: {}, + compositePrimaryKeys: { + user_id_iq_pk: 'id,iq', + }, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + new_user: { + name: 'new_user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + first_name: { + name: 'first_name', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: { + new_user_id_iq_pk: 'id,iq', + }, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'rename_table', + fromSchema: '', + toSchema: '', + tableNameFrom: 'user', + tableNameTo: 'new_user', + }, + { + type: 'drop_index', + tableName: 'new_user', + data: 'user_first_name_unique;first_name;true;', + schema: '', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +/** + * ! before: + * + * autoincrement1: { + * id INT PRIMARY KEY; + * } + * + * autoincrement2: { + * id INT PRIMARY KEY AUTOINCREMENT; + * } + * + * dropNotNull: { + * id INT NOT NULL; + * } + * + * ! after: + * + * autoincrement1: { + * id INT PRIMARY KEY AUTOINCREMENT; + * } + * + * autoincrement2: { + * id INT PRI { + const statements: JsonStatement[] = [ + { + type: 'alter_table_alter_column_set_autoincrement', + tableName: 'autoincrement1', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: true, + columnPk: true, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_drop_autoincrement', + tableName: 'autoincrement2', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: true, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_drop_notnull', + tableName: 'dropNotNull', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + autoincrement1: { + name: 'autoincrement1', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + autoincrement2: { + name: 'autoincrement2', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: false, + autoincrement: true, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + dropNotNull: { + name: 'dropNotNull', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + autoincrement1: { + name: 'autoincrement1', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: true, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + autoincrement2: { + name: 'autoincrement2', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + dropNotNull: { + name: 'dropNotNull', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'autoincrement1', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: true, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + { + type: 'recreate_table', + tableName: 'autoincrement2', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + { + type: 'alter_table_alter_column_drop_notnull', + tableName: 'dropNotNull', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +/** + * ! before: + * + * pk1: { + * id INT; + * } + * + * pk2: { + * id INT PRIMARY KEY; + * } + * + * ref_table: { + * id INT; + * } + * + * create_reference: { + * id INT; + * } + * + * ! after: + * + * pk1: { + * id INT PRIMARY KEY; + * } + * + * pk2: { + * id INT; + * } + * + * ref_table: { + * id INT; + * } + * + * create_reference: { + * id INT -> ref_table INT; + * } + * + * drop primary key for pk2 + * set primary key for pk1 + * "create_reference" reference on "ref_table" + * + * expect to: + * - "recreate_table" statement for pk1 + * - "recreate_table" statement for pk2 + * - "create_reference" statement for create_reference + */ +test(`drop and set primary key. create reference`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_alter_column_set_pk', + tableName: 'pk1', + schema: '', + columnName: 'id', + }, + { + type: 'alter_table_alter_column_set_notnull', + tableName: 'pk1', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: true, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_drop_pk', + tableName: 'pk2', + columnName: 'id', + schema: '', + }, + { + type: 'alter_table_alter_column_drop_notnull', + tableName: 'pk2', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'create_reference', + tableName: 'create_reference', + data: 'create_reference_id_ref_table_id_fk;create_reference;id;ref_table;id;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'int', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + create_reference: { + name: 'create_reference', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + pk1: { + name: 'pk1', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + pk2: { + name: 'pk2', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + ref_table: { + name: 'ref_table', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + create_reference: { + name: 'create_reference', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + create_reference_id_ref_table_id_fk: + 'create_reference_id_ref_table_id_fk;create_reference;id;ref_table;id;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + pk1: { + name: 'pk1', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + pk2: { + name: 'pk2', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + ref_table: { + name: 'ref_table', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'pk1', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + { + type: 'recreate_table', + tableName: 'pk2', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + { + type: 'create_reference', + tableName: 'create_reference', + data: 'create_reference_id_ref_table_id_fk;create_reference;id;ref_table;id;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'int', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +/** + * ! before: + * + * fk1: { + * fk_id INT; + * fk_id1 INT; + * } + * + * fk2: { + * fk2_id INT; -> composite reference on ref_table id INT + * fk2_id1 INT; -> composite reference on ref_table id1 INT + * } + * + * ref_table: { + * id INT; + * id1 INT; + * } + * + * ! after: + * + * fk1: { + * fk_id INT; -> composite reference on ref_table id INT + * fk_id1 INT; -> composite reference on ref_table id1 INT + * } + * + * fk2: { + * fk2_id INT; + * fk2_id1 INT; + * } + * + * ref_table: { + * id INT; + * id1 INT; + * } + * + * set multi column reference for fk1 + * drop multi column reference for fk2 + * + * expect to: + * - "recreate_table" statement for fk1 + * - "recreate_table" statement for fk2 + */ +test(`set and drop multiple columns reference`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'delete_reference', + tableName: 'fk1', + data: 'fk1_fk_id_fk_id1_ref_table_id_id1_fk;fk1;fk_id,fk_id1;ref_table;id,id1;no action;no action', + schema: '', + isMulticolumn: true, + }, + { + type: 'create_reference', + tableName: 'fk2', + data: 'fk2_fk2_id_fk2_id1_ref_table_id_id1_fk;fk2;fk2_id,fk2_id1;ref_table;id,id1;no action;no action', + schema: '', + isMulticolumn: true, + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + fk1: { + name: 'fk1', + columns: { + fk_id: { + name: 'fk_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + fk_id1: { + name: 'fk_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + fk1_fk_id_fk_id1_ref_table_id_id1_fk: + 'fk1_fk_id_fk_id1_ref_table_id_id1_fk;fk1;fk_id,fk_id1;ref_table;id,id1;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + fk2: { + name: 'fk2', + columns: { + fk2_id: { + name: 'fk2_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + fk2_id1: { + name: 'fk2_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + ref_table: { + name: 'ref_table', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + id1: { + name: 'id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + fk1: { + name: 'fk1', + columns: { + fk_id: { + name: 'fk_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + fk_id1: { + name: 'fk_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + fk2: { + name: 'fk2', + columns: { + fk2_id: { + name: 'fk2_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + fk2_id1: { + name: 'fk2_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + fk2_fk2_id_fk2_id1_ref_table_id_id1_fk: + 'fk2_fk2_id_fk2_id1_ref_table_id_id1_fk;fk2;fk2_id,fk2_id1;ref_table;id,id1;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + ref_table: { + name: 'ref_table', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + id1: { + name: 'id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'fk1', + columns: [ + { + name: 'fk_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'fk_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + { + type: 'recreate_table', + tableName: 'fk2', + columns: [ + { + name: 'fk2_id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'fk2_id1', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [ + { + name: 'fk2_fk2_id_fk2_id1_ref_table_id_id1_fk', + tableFrom: 'fk2', + tableTo: 'ref_table', + columnsFrom: ['fk2_id', 'fk2_id1'], + columnsTo: ['id', 'id1'], + onDelete: 'no action', + onUpdate: 'no action', + }, + ], + uniqueConstraints: [], + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +/** + * ! before: + * + * pk: { + * pk TEXT PRIMARY KEY; + * } + * + * simple: { + * simple TEXT; + * } + * + * unique: { + * unique INT UNIQUE; + * } + * + * ! after: + * + * pk: { + * pk INT PRIMARY KEY; + * } + * + * simple: { + * simple INT; + * } + * + * unique: { + * unique TEXT UNIQUE; + * } + * + * set new type for primary key column + * set new type for unique column + * set new type for column without pk or unique + * + * expect to: + * - "recreate_table" statement for pk + * - "recreate_table" statement for unique + * - "alter_table_alter_column_set_type" statement for simple + * - "create_index" statement for unique + */ +test(`set new type for primary key, unique and normal column`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_alter_column_set_type', + tableName: 'pk', + columnName: 'pk', + newDataType: 'int', + oldDataType: 'text', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: true, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_set_type', + tableName: 'simple', + columnName: 'simple', + newDataType: 'int', + oldDataType: 'text', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_set_type', + tableName: 'unique', + columnName: 'unique', + newDataType: 'text', + oldDataType: 'int', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + pk: { + name: 'pk', + columns: { + pk: { + name: 'pk', + type: 'text', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + simple: { + name: 'simple', + columns: { + simple: { + name: 'simple', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + unique: { + name: 'unique', + columns: { + unique: { + name: 'unique', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: { + unique_unique_unique: 'unique_unique_unique;unique;true;', + }, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + pk: { + name: 'pk', + columns: { + pk: { + name: 'pk', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + simple: { + name: 'simple', + columns: { + simple: { + name: 'simple', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + unique: { + name: 'unique', + columns: { + unique: { + name: 'unique', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: { + unique_unique_unique: 'unique_unique_unique;unique;true;', + }, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'pk', + columns: [ + { + name: 'pk', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + { + type: 'alter_table_alter_column_set_type', + tableName: 'simple', + columnName: 'simple', + newDataType: 'int', + oldDataType: 'text', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }, + { + type: 'alter_table_alter_column_set_type', + tableName: 'unique', + columnName: 'unique', + newDataType: 'text', + oldDataType: 'int', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add columns. set fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add column and fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_test1_user_new_age_fk: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add column and fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_test1_user_new_age_fk: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + ]; + expect(libSQLCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); diff --git a/drizzle-kit/tests/statements-combiner/sqlite-statements-combiner.test.ts b/drizzle-kit/tests/statements-combiner/sqlite-statements-combiner.test.ts new file mode 100644 index 000000000..2fcaf6436 --- /dev/null +++ b/drizzle-kit/tests/statements-combiner/sqlite-statements-combiner.test.ts @@ -0,0 +1,1170 @@ +import { JsonStatement } from 'src/jsonStatements'; +import { SQLiteSchemaSquashed } from 'src/serializer/sqliteSchema'; +import { sqliteCombineStatements } from 'src/statementCombiner'; +import { expect, test } from 'vitest'; + +test(`renamed column and altered this column type`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'user', + oldColumnName: 'lastName', + newColumnName: 'lastName123', + schema: '', + }, + { + type: 'alter_table_alter_column_set_type', + tableName: 'user', + columnName: 'lastName123', + newDataType: 'int', + oldDataType: 'text', + schema: '', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + columnIsUnique: false, + } as unknown as JsonStatement, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + firstName: { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + lastName: { + name: 'lastName', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + firstName: { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + lastName: { + name: 'lastName123', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'recreate_table', + tableName: 'user', + columns: [ + { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + { + name: 'lastName123', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'test', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`renamed column and droped column "test"`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'user', + oldColumnName: 'lastName', + newColumnName: 'lastName123', + schema: '', + }, + { + type: 'alter_table_drop_column', + tableName: 'user', + columnName: 'test', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + firstName: { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + lastName: { + name: 'lastName', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + firstName: { + name: 'firstName', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + lastName: { + name: 'lastName123', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'user', + oldColumnName: 'lastName', + newColumnName: 'lastName123', + schema: '', + }, + { + type: 'alter_table_drop_column', + tableName: 'user', + columnName: 'test', + schema: '', + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`droped column that is part of composite pk`, async (t) => { + const statements: JsonStatement[] = [ + { type: 'delete_composite_pk', tableName: 'user', data: 'id,iq' }, + { + type: 'alter_table_alter_column_set_pk', + tableName: 'user', + schema: '', + columnName: 'id', + }, + { + type: 'alter_table_drop_column', + tableName: 'user', + columnName: 'iq', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + first_nam: { + name: 'first_nam', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: { + user_id_iq_pk: 'id,iq', + }, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: false, + autoincrement: false, + }, + first_nam: { + name: 'first_nam', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements: JsonStatement[] = [ + { + type: 'recreate_table', + tableName: 'user', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: true, + notNull: false, + autoincrement: false, + }, + { + name: 'first_nam', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`drop column "ref"."name", rename column "ref"."age". dropped primary key "user"."id". Set not null to "user"."iq"`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'ref', + oldColumnName: 'age', + newColumnName: 'age1', + schema: '', + }, + { + type: 'alter_table_alter_column_drop_pk', + tableName: 'user', + columnName: 'id', + schema: '', + }, + { + type: 'alter_table_alter_column_drop_autoincrement', + tableName: 'user', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_drop_notnull', + tableName: 'user', + columnName: 'id', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: false, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'alter_table_alter_column_set_notnull', + tableName: 'user', + columnName: 'iq', + schema: '', + newDataType: 'int', + columnDefault: undefined, + columnOnUpdate: undefined, + columnNotNull: true, + columnAutoIncrement: false, + columnPk: false, + } as unknown as JsonStatement, + { + type: 'alter_table_drop_column', + tableName: 'ref', + columnName: 'text', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: true, + }, + user_iq: { + name: 'user_iq', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + name: { + name: 'name', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + age: { + name: 'age', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_user_iq_user_iq_fk: 'ref_user_iq_user_iq_fk;ref;user_iq;user;iq;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: true, + }, + first_name: { + name: 'first_name', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + user_iq: { + name: 'user_iq', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + age1: { + name: 'age1', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_user_iq_user_iq_fk: 'ref_user_iq_user_iq_fk;ref;user_iq;user;iq;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id: { + name: 'id', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + first_name: { + name: 'first_name', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + iq: { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements: JsonStatement[] = [ + { + type: 'alter_table_rename_column', + tableName: 'ref', + oldColumnName: 'age', + newColumnName: 'age1', + schema: '', + }, + { + type: 'alter_table_drop_column', + tableName: 'ref', + columnName: 'text', + schema: '', + }, + { + type: 'recreate_table', + tableName: 'user', + columns: [ + { + name: 'id', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + { + name: 'first_name', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'iq', + type: 'int', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [], + uniqueConstraints: [], + }, + ]; + + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`create reference on exising column (table includes unique index). expect to recreate column and recreate index`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'create_reference', + tableName: 'unique', + data: 'unique_ref_pk_pk_pk_fk;unique;ref_pk;pk;pk;no action;no action', + schema: '', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + pk: { + name: 'pk', + columns: { + pk: { + name: 'pk', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + unique: { + name: 'unique', + columns: { + unique: { + name: 'unique', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ref_pk: { + name: 'ref_pk', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: { + unique_unique_unique: 'unique_unique_unique;unique;true;', + }, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + pk: { + name: 'pk', + columns: { + pk: { + name: 'pk', + type: 'int', + primaryKey: true, + notNull: true, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + unique: { + name: 'unique', + columns: { + unique: { + name: 'unique', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ref_pk: { + name: 'ref_pk', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: { + unique_unique_unique: 'unique_unique_unique;unique;true;', + }, + foreignKeys: { + unique_ref_pk_pk_pk_fk: 'unique_ref_pk_pk_pk_fk;unique;ref_pk;pk;pk;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements: JsonStatement[] = [ + { + type: 'recreate_table', + tableName: 'unique', + columns: [ + { + name: 'unique', + type: 'text', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + { + name: 'ref_pk', + type: 'int', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + ], + compositePKs: [], + referenceData: [ + { + name: 'unique_ref_pk_pk_pk_fk', + tableFrom: 'unique', + tableTo: 'pk', + columnsFrom: ['ref_pk'], + columnsTo: ['pk'], + onDelete: 'no action', + onUpdate: 'no action', + }, + ], + uniqueConstraints: [], + }, + { + data: 'unique_unique_unique;unique;true;', + internal: undefined, + schema: '', + tableName: 'unique', + type: 'create_index', + }, + ]; + + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add columns. set fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: undefined, + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + columns: [ + { + autoincrement: false, + name: 'id1', + notNull: true, + primaryKey: false, + type: 'text', + }, + { + autoincrement: false, + name: 'new_age', + notNull: false, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'test', + notNull: false, + primaryKey: false, + type: 'integer', + }, + { + autoincrement: false, + name: 'test1', + notNull: false, + primaryKey: false, + type: 'integer', + }, + ], + compositePKs: [], + referenceData: [ + { + columnsFrom: [ + 'new_age', + ], + columnsTo: [ + 'new_age', + ], + name: 'ref_new_age_user_new_age_fk', + onDelete: 'no action', + onUpdate: 'no action', + tableFrom: 'ref', + tableTo: 'user', + }, + ], + tableName: 'ref', + type: 'recreate_table', + uniqueConstraints: [], + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); + +test(`add column and fk`, async (t) => { + const statements: JsonStatement[] = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + { + type: 'create_reference', + tableName: 'ref', + data: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + schema: '', + columnNotNull: false, + columnDefault: undefined, + columnType: 'integer', + }, + ]; + const json1: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_test1_user_new_age_fk: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + const json2: SQLiteSchemaSquashed = { + version: '6', + dialect: 'sqlite', + tables: { + ref: { + name: 'ref', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test: { + name: 'test', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + test1: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: { + ref_new_age_user_new_age_fk: 'ref_new_age_user_new_age_fk;ref;new_age;user;new_age;no action;no action', + }, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + user: { + name: 'user', + columns: { + id1: { + name: 'id1', + type: 'text', + primaryKey: false, + notNull: true, + autoincrement: false, + }, + new_age: { + name: 'new_age', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + }, + indexes: {}, + foreignKeys: {}, + compositePrimaryKeys: {}, + uniqueConstraints: {}, + }, + }, + enums: {}, + }; + + const newJsonStatements = [ + { + type: 'sqlite_alter_table_add_column', + tableName: 'ref', + column: { + name: 'test1', + type: 'integer', + primaryKey: false, + notNull: false, + autoincrement: false, + }, + referenceData: 'ref_test1_user_new_age_fk;ref;test1;user;new_age;no action;no action', + }, + ]; + expect(sqliteCombineStatements(statements, json2)).toStrictEqual( + newJsonStatements, + ); +}); diff --git a/drizzle-kit/tests/testsinglestore.ts b/drizzle-kit/tests/testsinglestore.ts new file mode 100644 index 000000000..1dc97d9c3 --- /dev/null +++ b/drizzle-kit/tests/testsinglestore.ts @@ -0,0 +1,29 @@ +import { index, singlestoreTable, text } from 'drizzle-orm/singlestore-core'; +import { diffTestSchemasSingleStore } from './schemaDiffer'; + +const from = { + users: singlestoreTable( + 'table', + { + name: text('name'), + }, + (t) => { + return { + idx: index('name_idx').on(t.name), + }; + }, + ), +}; + +const to = { + users: singlestoreTable('table', { + name: text('name'), + }), +}; + +diffTestSchemasSingleStore(from, to, []).then((res) => { + const { statements, sqlStatements } = res; + + console.log(statements); + console.log(sqlStatements); +}); diff --git a/drizzle-kit/tests/validations.test.ts b/drizzle-kit/tests/validations.test.ts index 82731ee25..8a64603bb 100644 --- a/drizzle-kit/tests/validations.test.ts +++ b/drizzle-kit/tests/validations.test.ts @@ -1,5 +1,6 @@ import { mysqlCredentials } from 'src/cli/validations/mysql'; import { postgresCredentials } from 'src/cli/validations/postgres'; +import { singlestoreCredentials } from 'src/cli/validations/singlestore'; import { sqliteCredentials } from 'src/cli/validations/sqlite'; import { expect, test } from 'vitest'; @@ -698,3 +699,171 @@ test('mysql #17', () => { }); }).toThrowError(); }); + +test('singlestore #1', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: 'database', + host: 'host', + }), + ).toStrictEqual({ + database: 'database', + host: 'host', + }); +}); + +test('singlestore #2', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: 'database', + host: 'host', + }), + ).toStrictEqual({ + database: 'database', + host: 'host', + }); +}); + +test('singlestore #3', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: 'host', + port: 1234, + user: 'user', + password: 'password', + database: 'database', + ssl: 'require', + }), + ).toStrictEqual({ + host: 'host', + port: 1234, + user: 'user', + password: 'password', + database: 'database', + ssl: 'require', + }); +}); + +test('singlestore #4', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: 'host', + database: 'database', + ssl: 'allow', + }), + ).toStrictEqual({ + host: 'host', + database: 'database', + ssl: 'allow', + }); +}); + +test('singlestore #5', () => { + expect( + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: 'host', + database: 'database', + ssl: { + ca: 'ca', + cert: 'cert', + }, + }), + ).toStrictEqual({ + host: 'host', + database: 'database', + ssl: { + ca: 'ca', + cert: 'cert', + }, + }); +}); + +test('singlestore #6', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + }); + }).toThrowError(); +}); + +test('singlestore #7', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + url: undefined, + }); + }).toThrowError(); +}); + +test('singlestore #8', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + url: '', + }); + }).toThrowError(); +}); + +test('singlestore #9', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: '', + database: '', + }); + }).toThrowError(); +}); + +test('singlestore #10', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: '', + }); + }).toThrowError(); +}); + +test('singlestore #11', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + host: '', + }); + }).toThrowError(); +}); + +test('singlestore #12', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: ' ', + host: '', + }); + }).toThrowError(); +}); + +test('singlestore #13', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: '', + host: ' ', + }); + }).toThrowError(); +}); + +test('singlestore #14', () => { + expect(() => { + singlestoreCredentials.parse({ + dialect: 'singlestore', + database: ' ', + host: ' ', + port: '', + }); + }).toThrowError(); +}); diff --git a/drizzle-kit/tests/wrap-param.test.ts b/drizzle-kit/tests/wrap-param.test.ts index 542998bda..a27d27d45 100644 --- a/drizzle-kit/tests/wrap-param.test.ts +++ b/drizzle-kit/tests/wrap-param.test.ts @@ -7,6 +7,9 @@ test('wrapParam', () => { expect(wrapParam('url', 'mysql://user:password@localhost:3306/database', false, 'url')).toBe( ` [${chalk.green('✓')}] url: 'mysql://user:****@localhost:3306/database'`, ); + expect(wrapParam('url', 'singlestore://user:password@localhost:3306/database', false, 'url')).toBe( + ` [${chalk.green('✓')}] url: 'singlestore://user:****@localhost:3306/database'`, + ); expect(wrapParam('url', 'postgresql://user:password@localhost:5432/database', false, 'url')).toBe( ` [${chalk.green('✓')}] url: 'postgresql://user:****@localhost:5432/database'`, ); diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index 888f7efcb..333521a48 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-orm", - "version": "0.33.0", + "version": "0.34.0", "description": "Drizzle ORM package for SQL databases", "type": "module", "scripts": { @@ -46,7 +46,7 @@ "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.1.1", - "@libsql/client": "*", + "@libsql/client": ">=0.10.0", "@neondatabase/serverless": ">=0.1", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", @@ -161,7 +161,7 @@ "@aws-sdk/client-rds-data": "^3.549.0", "@cloudflare/workers-types": "^4.20230904.0", "@electric-sql/pglite": "^0.1.1", - "@libsql/client": "^0.5.6", + "@libsql/client": "^0.10.0", "@neondatabase/serverless": "^0.9.0", "@op-engineering/op-sqlite": "^2.0.16", "@opentelemetry/api": "^1.4.1", diff --git a/drizzle-orm/src/better-sqlite3/driver.ts b/drizzle-orm/src/better-sqlite3/driver.ts index 728586e57..8fe7c00fb 100644 --- a/drizzle-orm/src/better-sqlite3/driver.ts +++ b/drizzle-orm/src/better-sqlite3/driver.ts @@ -1,4 +1,5 @@ import type { Database, RunResult } from 'better-sqlite3'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { createTableRelationsHelpers, @@ -11,9 +12,11 @@ import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; import type { DrizzleConfig } from '~/utils.ts'; import { BetterSQLiteSession } from './session.ts'; -export type BetterSQLite3Database< - TSchema extends Record = Record, -> = BaseSQLiteDatabase<'sync', RunResult, TSchema>; +export class BetterSQLite3Database = Record> + extends BaseSQLiteDatabase<'sync', RunResult, TSchema> +{ + static readonly [entityKind]: string = 'BetterSQLite3Database'; +} export function drizzle = Record>( client: Database, @@ -41,5 +44,5 @@ export function drizzle = Record; + return new BetterSQLite3Database('sync', dialect, session, schema) as BetterSQLite3Database; } diff --git a/drizzle-orm/src/bun-sqlite/driver.ts b/drizzle-orm/src/bun-sqlite/driver.ts index 0d196ff03..5771bd371 100644 --- a/drizzle-orm/src/bun-sqlite/driver.ts +++ b/drizzle-orm/src/bun-sqlite/driver.ts @@ -1,6 +1,7 @@ /// import type { Database } from 'bun:sqlite'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { createTableRelationsHelpers, @@ -13,9 +14,11 @@ import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; import type { DrizzleConfig } from '~/utils.ts'; import { SQLiteBunSession } from './session.ts'; -export type BunSQLiteDatabase< +export class BunSQLiteDatabase< TSchema extends Record = Record, -> = BaseSQLiteDatabase<'sync', void, TSchema>; +> extends BaseSQLiteDatabase<'sync', void, TSchema> { + static readonly [entityKind]: string = 'BunSQLiteDatabase'; +} export function drizzle = Record>( client: Database, @@ -43,5 +46,5 @@ export function drizzle = Record; + return new BunSQLiteDatabase('sync', dialect, session, schema) as BunSQLiteDatabase; } diff --git a/drizzle-orm/src/column-builder.ts b/drizzle-orm/src/column-builder.ts index 4a19a79a9..ad278e29d 100644 --- a/drizzle-orm/src/column-builder.ts +++ b/drizzle-orm/src/column-builder.ts @@ -2,6 +2,7 @@ import { entityKind } from '~/entity.ts'; import type { Column } from './column.ts'; import type { MySqlColumn } from './mysql-core/index.ts'; import type { ExtraConfigColumn, PgColumn, PgSequenceOptions } from './pg-core/index.ts'; +import type { SingleStoreColumn } from './singlestore-core/index.ts'; import type { SQL } from './sql/sql.ts'; import type { SQLiteColumn } from './sqlite-core/index.ts'; import type { Simplify } from './utils.ts'; @@ -17,7 +18,7 @@ export type ColumnDataType = | 'custom' | 'buffer'; -export type Dialect = 'pg' | 'mysql' | 'sqlite' | 'common'; +export type Dialect = 'pg' | 'mysql' | 'sqlite' | 'singlestore' | 'common'; export type GeneratedStorageMode = 'virtual' | 'stored'; @@ -299,7 +300,8 @@ export type BuildColumn< TTableName extends string, TBuilder extends ColumnBuilderBase, TDialect extends Dialect, -> = TDialect extends 'pg' ? PgColumn> +> = TDialect extends 'singlestore' ? SingleStoreColumn> + : TDialect extends 'pg' ? PgColumn> : TDialect extends 'mysql' ? MySqlColumn> : TDialect extends 'sqlite' ? SQLiteColumn> : TDialect extends 'common' ? Column> @@ -337,7 +339,8 @@ export type BuildExtraConfigColumns< & {}; export type ChangeColumnTableName = - TDialect extends 'pg' ? PgColumn> + TDialect extends 'singlestore' ? SingleStoreColumn> + : TDialect extends 'pg' ? PgColumn> : TDialect extends 'mysql' ? MySqlColumn> : TDialect extends 'sqlite' ? SQLiteColumn> : never; diff --git a/drizzle-orm/src/expo-sqlite/driver.ts b/drizzle-orm/src/expo-sqlite/driver.ts index ae8ce6577..fb858e482 100644 --- a/drizzle-orm/src/expo-sqlite/driver.ts +++ b/drizzle-orm/src/expo-sqlite/driver.ts @@ -1,4 +1,5 @@ import type { SQLiteDatabase, SQLiteRunResult } from 'expo-sqlite/next'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { createTableRelationsHelpers, @@ -11,9 +12,11 @@ import { SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; import type { DrizzleConfig } from '~/utils.ts'; import { ExpoSQLiteSession } from './session.ts'; -export type ExpoSQLiteDatabase< - TSchema extends Record = Record, -> = BaseSQLiteDatabase<'sync', SQLiteRunResult, TSchema>; +export class ExpoSQLiteDatabase = Record> + extends BaseSQLiteDatabase<'sync', SQLiteRunResult, TSchema> +{ + static readonly [entityKind]: string = 'ExpoSQLiteDatabase'; +} export function drizzle = Record>( client: SQLiteDatabase, @@ -41,5 +44,5 @@ export function drizzle = Record; + return new ExpoSQLiteDatabase('sync', dialect, session, schema) as ExpoSQLiteDatabase; } diff --git a/drizzle-orm/src/index.ts b/drizzle-orm/src/index.ts index bc72260b9..469f5713e 100644 --- a/drizzle-orm/src/index.ts +++ b/drizzle-orm/src/index.ts @@ -5,6 +5,8 @@ export * from './entity.ts'; export * from './errors.ts'; export * from './expressions.ts'; export * from './logger.ts'; +export * from './monodriver.ts'; +export * from './monomigrator.ts'; export * from './operations.ts'; export * from './query-promise.ts'; export * from './relations.ts'; diff --git a/drizzle-orm/src/libsql/migrator.ts b/drizzle-orm/src/libsql/migrator.ts index 58bcc9e05..d362a2e4d 100644 --- a/drizzle-orm/src/libsql/migrator.ts +++ b/drizzle-orm/src/libsql/migrator.ts @@ -5,7 +5,7 @@ import type { LibSQLDatabase } from './driver.ts'; export async function migrate>( db: LibSQLDatabase, - config: MigrationConfig, + config: MigrationConfig | string, ) { const migrations = readMigrationFiles(config); const migrationsTable = config === undefined @@ -47,5 +47,5 @@ export async function migrate>( } } - await db.session.batch(statementToBatch); + await db.session.migrate(statementToBatch); } diff --git a/drizzle-orm/src/libsql/session.ts b/drizzle-orm/src/libsql/session.ts index 29e4e268f..640977734 100644 --- a/drizzle-orm/src/libsql/session.ts +++ b/drizzle-orm/src/libsql/session.ts @@ -76,6 +76,21 @@ export class LibSQLSession< return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true)); } + async migrate[] | readonly BatchItem<'sqlite'>[]>(queries: T) { + const preparedQueries: PreparedQuery[] = []; + const builtQueries: InStatement[] = []; + + for (const query of queries) { + const preparedQuery = query._prepare(); + const builtQuery = preparedQuery.getQuery(); + preparedQueries.push(preparedQuery); + builtQueries.push({ sql: builtQuery.sql, args: builtQuery.params as InArgs }); + } + + const batchResults = await this.client.migrate(builtQueries); + return batchResults.map((result, i) => preparedQueries[i]!.mapResult(result, true)); + } + override async transaction( transaction: (db: LibSQLTransaction) => T | Promise, _config?: SQLiteTransactionConfig, diff --git a/drizzle-orm/src/monodriver.ts b/drizzle-orm/src/monodriver.ts new file mode 100644 index 000000000..03fcdc58f --- /dev/null +++ b/drizzle-orm/src/monodriver.ts @@ -0,0 +1,405 @@ +/* eslint-disable import/extensions */ +import type { RDSDataClient, RDSDataClientConfig, RDSDataClientConfig as RDSConfig } from '@aws-sdk/client-rds-data'; +import type { Client as LibsqlClient, Config as LibsqlConfig } from '@libsql/client'; +import type { + HTTPTransactionOptions as NeonHttpConfig, + NeonQueryFunction, + Pool as NeonServerlessPool, + PoolConfig as NeonServerlessConfig, +} from '@neondatabase/serverless'; +import type { Client as PlanetscaleClient, Config as PlanetscaleConfig } from '@planetscale/database'; +import type { Config as TiDBServerlessConfig, Connection as TiDBConnection } from '@tidbcloud/serverless'; +import type { QueryResult, QueryResultRow, VercelPool } from '@vercel/postgres'; +import type { Database as BetterSQLite3Database, Options as BetterSQLite3Options } from 'better-sqlite3'; +import type { Database as BunDatabase } from 'bun:sqlite'; +import type { Pool as Mysql2Pool, PoolOptions as Mysql2Config } from 'mysql2'; +import type { Pool as NodePgPool, PoolConfig as NodePGPoolConfig } from 'pg'; +import type { + Options as PostgresJSOptions, + PostgresType as PostgresJSPostgresType, + Sql as PostgresJsClient, +} from 'postgres'; +import type { AwsDataApiPgDatabase, DrizzleAwsDataApiPgConfig } from './aws-data-api/pg/index.ts'; +import type { BetterSQLite3Database as DrizzleBetterSQLite3Database } from './better-sqlite3/index.ts'; +import type { BunSQLiteDatabase } from './bun-sqlite/index.ts'; +import type { DrizzleD1Database } from './d1/index.ts'; +import type { LibSQLDatabase } from './libsql/index.ts'; +import type { MySql2Database, MySql2DrizzleConfig } from './mysql2/index.ts'; +import type { NeonHttpDatabase } from './neon-http/index.ts'; +import type { NeonDatabase } from './neon-serverless/index.ts'; +import type { NodePgDatabase } from './node-postgres/index.ts'; +import type { PlanetScaleDatabase } from './planetscale-serverless/index.ts'; +import type { PostgresJsDatabase } from './postgres-js/index.ts'; +import type { SingleStore2Database, SingleStore2DrizzleConfig } from './singlestore/driver.ts'; +import type { TiDBServerlessDatabase } from './tidb-serverless/index.ts'; +import type { DrizzleConfig } from './utils.ts'; +import type { VercelPgDatabase } from './vercel-postgres/index.ts'; + +type BunSqliteDatabaseOptions = + | number + | { + /** + * Open the database as read-only (no write operations, no create). + * + * Equivalent to {@link constants.SQLITE_OPEN_READONLY} + */ + readonly?: boolean; + /** + * Allow creating a new database + * + * Equivalent to {@link constants.SQLITE_OPEN_CREATE} + */ + create?: boolean; + /** + * Open the database as read-write + * + * Equivalent to {@link constants.SQLITE_OPEN_READWRITE} + */ + readwrite?: boolean; + }; + +type BunSqliteDatabaseConfig = + | { + filename?: ':memory:' | (string & {}); + options?: BunSqliteDatabaseOptions; + } + | ':memory:' + | (string & {}) + | undefined; + +type BetterSQLite3DatabaseConfig = + | { + filename?: + | ':memory:' + | (string & {}) + | Buffer; + options?: BetterSQLite3Options; + } + | ':memory:' + | (string & {}) + | undefined; + +type MonodriverNeonHttpConfig = { + connectionString: string; + options?: NeonHttpConfig; +}; + +type VercelPrimitive = string | number | boolean | undefined | null; + +type DatabaseClient = + | 'node-postgres' + | 'postgres-js' + | 'neon-serverless' + | 'neon-http' + | 'vercel-postgres' + | 'aws-data-api-pg' + | 'planetscale' + | 'mysql2' + | 'tidb-serverless' + | 'libsql' + | 'd1' + | 'bun:sqlite' + | 'better-sqlite3' + | 'singlestore'; + +type ClientDrizzleInstanceMap> = { + 'node-postgres': NodePgDatabase; + 'postgres-js': PostgresJsDatabase; + 'neon-serverless': NeonDatabase; + 'neon-http': NeonHttpDatabase; + 'vercel-postgres': VercelPgDatabase; + 'aws-data-api-pg': AwsDataApiPgDatabase; + planetscale: PlanetScaleDatabase; + mysql2: MySql2Database; + 'tidb-serverless': TiDBServerlessDatabase; + libsql: LibSQLDatabase; + d1: DrizzleD1Database; + 'bun:sqlite': BunSQLiteDatabase; + 'better-sqlite3': DrizzleBetterSQLite3Database; + singlestore: SingleStore2Database; +}; + +type ClientInstanceMap = { + 'node-postgres': NodePgPool; + 'postgres-js': PostgresJsClient; + 'neon-serverless': NeonServerlessPool; + 'neon-http': NeonQueryFunction; + 'vercel-postgres': + & VercelPool + & (( + strings: TemplateStringsArray, + ...values: VercelPrimitive[] + ) => Promise>); + 'aws-data-api-pg': RDSDataClient; + planetscale: PlanetscaleClient; + mysql2: Mysql2Pool; + 'tidb-serverless': TiDBConnection; + libsql: LibsqlClient; + d1: D1Database; + 'bun:sqlite': BunDatabase; + 'better-sqlite3': BetterSQLite3Database; + singlestore: SingleStore2Database; +}; + +type InitializerParams = { + 'node-postgres': { + connection: NodePGPoolConfig; + }; + 'postgres-js': { + connection: string | PostgresJSOptions>; + }; + 'neon-serverless': { + connection: NeonServerlessConfig; + }; + 'neon-http': { + connection: MonodriverNeonHttpConfig; + }; + 'vercel-postgres': { + connection: VercelPool; + }; + 'aws-data-api-pg': { + connection?: RDSConfig; + }; + planetscale: { + connection: PlanetscaleConfig; + }; + mysql2: { + connection: Mysql2Config | string; + }; + 'tidb-serverless': { + connection: TiDBServerlessConfig; + }; + libsql: { + connection: LibsqlConfig; + }; + d1: { + connection: D1Database; + }; + 'bun:sqlite': { + connection?: BunSqliteDatabaseConfig; + }; + 'better-sqlite3': { + connection?: BetterSQLite3DatabaseConfig; + }; + singlestore: { + // This Mysql2Config is from the node package 'mysql2' and not the one from Drizzle + connection: Mysql2Config; + }; +}; + +type DetermineClient< + TClient extends DatabaseClient, + TSchema extends Record, +> = + & ClientDrizzleInstanceMap< + TSchema + >[TClient] + & { + $client: ClientInstanceMap[TClient]; + }; + +const importError = (libName: string) => { + throw new Error( + `Please install '${libName}' for Drizzle ORM to connect to database`, + ); +}; + +function assertUnreachable(_: never | undefined): never { + throw new Error("Didn't expect to get here"); +} + +export async function drizzle< + TClient extends DatabaseClient, + TSchema extends Record = Record, +>( + client: TClient, + params: + & InitializerParams[TClient] + & (TClient extends 'mysql2' ? MySql2DrizzleConfig + : TClient extends 'aws-data-api-pg' ? DrizzleAwsDataApiPgConfig + : TClient extends 'neon-serverless' ? DrizzleConfig & { + ws?: any; + } + : DrizzleConfig), +): Promise> { + const { connection, ws, ...drizzleConfig } = params as typeof params & { + ws?: any; + }; + + switch (client) { + case 'node-postgres': { + const { Pool } = await import('pg').catch(() => importError('pg')); + const { drizzle } = await import('./node-postgres'); + const instance = new Pool(connection as NodePGPoolConfig); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'aws-data-api-pg': { + const { RDSDataClient } = await import('@aws-sdk/client-rds-data').catch(() => + importError('@aws-sdk/client-rds-data') + ); + const { drizzle } = await import('./aws-data-api/pg'); + const instance = new RDSDataClient(connection as RDSDataClientConfig); + + const db = drizzle(instance, drizzleConfig as any as DrizzleAwsDataApiPgConfig) as any; + db.$client = instance; + + return db; + } + case 'better-sqlite3': { + const { default: Client } = await import('better-sqlite3').catch(() => importError('better-sqlite3')); + const { drizzle } = await import('./better-sqlite3'); + + if (typeof connection === 'object') { + const { filename, options } = connection as Exclude; + + const instance = new Client(filename, options); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + + const instance = new Client(connection); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'bun:sqlite': { + const { Database: Client } = await import('bun:sqlite').catch(() => importError('bun:sqlite')); + const { drizzle } = await import('./bun-sqlite'); + + if (typeof connection === 'object') { + const { filename, options } = connection as Exclude; + + const instance = new Client(filename, options); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + + const instance = new Client(connection); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'd1': { + const { drizzle } = await import('./d1'); + + const db = drizzle(connection as D1Database, drizzleConfig) as any; + db.$client = connection; + + return db; + } + case 'libsql': { + const { createClient } = await import('@libsql/client').catch(() => importError('@libsql/client')); + const { drizzle } = await import('./libsql'); + const instance = createClient(connection as LibsqlConfig); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'mysql2': { + const { createPool } = await import('mysql2/promise').catch(() => importError('mysql2/promise')); + const instance = createPool(connection as Mysql2Config); + const { drizzle } = await import('./mysql2'); + + const db = drizzle(instance, drizzleConfig as MySql2DrizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'neon-http': { + const { neon } = await import('@neondatabase/serverless').catch(() => importError('@neondatabase/serverless')); + const { connectionString, options } = connection as MonodriverNeonHttpConfig; + const { drizzle } = await import('./neon-http'); + const instance = neon(connectionString, options); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'neon-serverless': { + const { Pool, neonConfig } = await import('@neondatabase/serverless').catch(() => + importError('@neondatabase/serverless') + ); + const { drizzle } = await import('./neon-serverless'); + const instance = new Pool(connection as NeonServerlessConfig); + + if (ws) { + neonConfig.webSocketConstructor = ws; + } + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'planetscale': { + const { Client } = await import('@planetscale/database').catch(() => importError('@planetscale/database')); + const { drizzle } = await import('./planetscale-serverless'); + const instance = new Client( + connection as PlanetscaleConfig, + ); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'postgres-js': { + const { default: client } = await import('postgres').catch(() => importError('postgres')); + const { drizzle } = await import('./postgres-js'); + const instance = client(connection as PostgresJSOptions>); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'tidb-serverless': { + const { connect } = await import('@tidbcloud/serverless').catch(() => importError('@tidbcloud/serverless')); + const { drizzle } = await import('./tidb-serverless'); + const instance = connect(connection as TiDBServerlessConfig); + + const db = drizzle(instance, drizzleConfig) as any; + db.$client = instance; + + return db; + } + case 'vercel-postgres': { + const { sql } = await import('@vercel/postgres').catch(() => importError('@vercel/postgres')); + const { drizzle } = await import('./vercel-postgres'); + + const db = drizzle(sql, drizzleConfig) as any; + db.$client = sql; + + return db; + } + case 'singlestore': { + const { createPool } = await import('mysql2/promise').catch(() => importError('mysql2/promise')); + const instance = createPool(connection as Mysql2Config); + const { drizzle } = await import('./mysql2'); + + const db = drizzle(instance, drizzleConfig as SingleStore2DrizzleConfig) as any; + db.$client = instance; + + return db; + } + } + + assertUnreachable(client); +} diff --git a/drizzle-orm/src/monomigrator.ts b/drizzle-orm/src/monomigrator.ts new file mode 100644 index 000000000..d8681ebf9 --- /dev/null +++ b/drizzle-orm/src/monomigrator.ts @@ -0,0 +1,111 @@ +/* eslint-disable import/extensions */ +import type { AwsDataApiPgDatabase } from './aws-data-api/pg/index.ts'; +import type { BetterSQLite3Database } from './better-sqlite3/index.ts'; +import type { BunSQLiteDatabase } from './bun-sqlite/index.ts'; +import type { DrizzleD1Database } from './d1/index.ts'; +import { entityKind } from './entity.ts'; +import type { LibSQLDatabase } from './libsql/index.ts'; +import type { MigrationConfig } from './migrator.ts'; +import type { MySql2Database } from './mysql2/index.ts'; +import type { NeonHttpDatabase } from './neon-http/index.ts'; +import type { NeonDatabase } from './neon-serverless/index.ts'; +import type { NodePgDatabase } from './node-postgres/index.ts'; +import type { PlanetScaleDatabase } from './planetscale-serverless/index.ts'; +import type { PostgresJsDatabase } from './postgres-js/index.ts'; +import type { SingleStore2Database } from './singlestore/driver.ts'; +import type { TiDBServerlessDatabase } from './tidb-serverless/index.ts'; +import type { VercelPgDatabase } from './vercel-postgres/index.ts'; + +export async function migrate( + db: + | AwsDataApiPgDatabase + | BetterSQLite3Database + | BunSQLiteDatabase + | DrizzleD1Database + | LibSQLDatabase + | MySql2Database + | NeonHttpDatabase + | NeonDatabase + | NodePgDatabase + | PlanetScaleDatabase + | PostgresJsDatabase + | VercelPgDatabase + | TiDBServerlessDatabase + | SingleStore2Database, + config: + | string + | MigrationConfig, +) { + switch (( db).constructor[entityKind]) { + case 'AwsDataApiPgDatabase': { + const { migrate } = await import('./aws-data-api/pg/migrator'); + + return migrate(db as AwsDataApiPgDatabase, config as string | MigrationConfig); + } + case 'BetterSQLite3Database': { + const { migrate } = await import('./better-sqlite3/migrator'); + + return migrate(db as BetterSQLite3Database, config as string | MigrationConfig); + } + case 'BunSQLiteDatabase': { + const { migrate } = await import('./bun-sqlite/migrator'); + + return migrate(db as BunSQLiteDatabase, config as string | MigrationConfig); + } + case 'D1Database': { + const { migrate } = await import('./d1/migrator'); + + return migrate(db as DrizzleD1Database, config as string | MigrationConfig); + } + case 'LibSQLDatabase': { + const { migrate } = await import('./libsql/migrator'); + + return migrate(db as LibSQLDatabase, config as string | MigrationConfig); + } + case 'MySql2Database': { + const { migrate } = await import('./mysql2/migrator'); + + return migrate(db as MySql2Database, config as string | MigrationConfig); + } + case 'NeonHttpDatabase': { + const { migrate } = await import('./neon-http/migrator'); + + return migrate(db as NeonHttpDatabase, config as string | MigrationConfig); + } + case 'NeonServerlessDatabase': { + const { migrate } = await import('./neon-serverless/migrator'); + + return migrate(db as NeonDatabase, config as string | MigrationConfig); + } + case 'NodePgDatabase': { + const { migrate } = await import('./node-postgres/migrator'); + + return migrate(db as NodePgDatabase, config as string | MigrationConfig); + } + case 'PlanetScaleDatabase': { + const { migrate } = await import('./planetscale-serverless/migrator'); + + return migrate(db as PlanetScaleDatabase, config as string | MigrationConfig); + } + case 'PostgresJsDatabase': { + const { migrate } = await import('./postgres-js/migrator'); + + return migrate(db as PostgresJsDatabase, config as string | MigrationConfig); + } + case 'TiDBServerlessDatabase': { + const { migrate } = await import('./tidb-serverless/migrator'); + + return migrate(db as TiDBServerlessDatabase, config as MigrationConfig); + } + case 'VercelPgDatabase': { + const { migrate } = await import('./vercel-postgres/migrator'); + + return migrate(db as VercelPgDatabase, config as string | MigrationConfig); + } + case 'SingleStore2Database': { + const { migrate } = await import('./singlestore/migrator'); + + return migrate(db as SingleStore2Database, config as MigrationConfig); + } + } +} diff --git a/drizzle-orm/src/mysql-core/db.ts b/drizzle-orm/src/mysql-core/db.ts index 8df6ff343..8934c0edf 100644 --- a/drizzle-orm/src/mysql-core/db.ts +++ b/drizzle-orm/src/mysql-core/db.ts @@ -3,10 +3,11 @@ import { entityKind } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; -import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; +import type { ColumnsSelection, SQL, SQLWrapper } from '~/sql/sql.ts'; import { WithSubquery } from '~/subquery.ts'; import type { DrizzleTypeError } from '~/utils.ts'; import type { MySqlDialect } from './dialect.ts'; +import { MySqlCountBuilder } from './query-builders/count.ts'; import { MySqlDeleteBase, MySqlInsertBuilder, @@ -27,6 +28,7 @@ import type { } from './session.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; import type { MySqlTable } from './table.ts'; +import type { MySqlViewBase } from './view-base.ts'; export class MySqlDatabase< TQueryResult extends MySqlQueryResultHKT, @@ -134,6 +136,13 @@ export class MySqlDatabase< }; } + $count( + source: MySqlTable | MySqlViewBase | SQL | SQLWrapper, + filters?: SQL, + ) { + return new MySqlCountBuilder({ source, filters, session: this.session }); + } + /** * Incorporates a previously defined CTE (using `$with`) into the main query. * diff --git a/drizzle-orm/src/mysql-core/query-builders/count.ts b/drizzle-orm/src/mysql-core/query-builders/count.ts new file mode 100644 index 000000000..645bb4753 --- /dev/null +++ b/drizzle-orm/src/mysql-core/query-builders/count.ts @@ -0,0 +1,80 @@ +import { entityKind, sql } from '~/index.ts'; +import type { SQLWrapper } from '~/sql/sql.ts'; +import { SQL } from '~/sql/sql.ts'; +import type { MySqlSession } from '../session.ts'; +import type { MySqlTable } from '../table.ts'; +import type { MySqlViewBase } from '../view-base.ts'; + +export class MySqlCountBuilder< + TSession extends MySqlSession, +> extends SQL implements Promise, SQLWrapper { + private sql: SQL; + + static readonly [entityKind] = 'MySqlCountBuilder'; + [Symbol.toStringTag] = 'MySqlCountBuilder'; + + private session: TSession; + + private static buildEmbeddedCount( + source: MySqlTable | MySqlViewBase | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`; + } + + private static buildCount( + source: MySqlTable | MySqlViewBase | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`select count(*) as count from ${source}${sql.raw(' where ').if(filters)}${filters}`; + } + + constructor( + readonly params: { + source: MySqlTable | MySqlViewBase | SQL | SQLWrapper; + filters?: SQL; + session: TSession; + }, + ) { + super(MySqlCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks); + + this.mapWith(Number); + + this.session = params.session; + + this.sql = MySqlCountBuilder.buildCount( + params.source, + params.filters, + ); + } + + then( + onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined, + ): Promise { + return Promise.resolve(this.session.count(this.sql)) + .then( + onfulfilled, + onrejected, + ); + } + + catch( + onRejected?: ((reason: any) => never | PromiseLike) | null | undefined, + ): Promise { + return this.then(undefined, onRejected); + } + + finally(onFinally?: (() => void) | null | undefined): Promise { + return this.then( + (value) => { + onFinally?.(); + return value; + }, + (reason) => { + onFinally?.(); + throw reason; + }, + ); + } +} diff --git a/drizzle-orm/src/mysql-core/session.ts b/drizzle-orm/src/mysql-core/session.ts index 6b6269639..021d4276d 100644 --- a/drizzle-orm/src/mysql-core/session.ts +++ b/drizzle-orm/src/mysql-core/session.ts @@ -86,6 +86,14 @@ export abstract class MySqlSession< abstract all(query: SQL): Promise; + async count(sql: SQL): Promise { + const res = await this.execute<[[{ count: string }]]>(sql); + + return Number( + res[0][0]['count'], + ); + } + abstract transaction( transaction: (tx: MySqlTransaction) => Promise, config?: MySqlTransactionConfig, diff --git a/drizzle-orm/src/mysql-proxy/driver.ts b/drizzle-orm/src/mysql-proxy/driver.ts index 574db42c1..dfbf69cc9 100644 --- a/drizzle-orm/src/mysql-proxy/driver.ts +++ b/drizzle-orm/src/mysql-proxy/driver.ts @@ -1,3 +1,4 @@ +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { MySqlDatabase } from '~/mysql-core/db.ts'; import { MySqlDialect } from '~/mysql-core/dialect.ts'; @@ -10,9 +11,11 @@ import { import type { DrizzleConfig } from '~/utils.ts'; import { type MySqlRemotePreparedQueryHKT, type MySqlRemoteQueryResultHKT, MySqlRemoteSession } from './session.ts'; -export type MySqlRemoteDatabase< +export class MySqlRemoteDatabase< TSchema extends Record = Record, -> = MySqlDatabase; +> extends MySqlDatabase { + static readonly [entityKind]: string = 'MySqlRemoteDatabase'; +} export type RemoteCallback = ( sql: string, @@ -46,5 +49,5 @@ export function drizzle = Record; + return new MySqlRemoteDatabase(dialect, session, schema as any, 'default') as MySqlRemoteDatabase; } diff --git a/drizzle-orm/src/mysql2/driver.ts b/drizzle-orm/src/mysql2/driver.ts index 3b21bf11d..a8fb65c3b 100644 --- a/drizzle-orm/src/mysql2/driver.ts +++ b/drizzle-orm/src/mysql2/driver.ts @@ -40,9 +40,11 @@ export class MySql2Driver { export { MySqlDatabase } from '~/mysql-core/db.ts'; -export type MySql2Database< +export class MySql2Database< TSchema extends Record = Record, -> = MySqlDatabase; +> extends MySqlDatabase { + static readonly [entityKind]: string = 'MySql2Database'; +} export type MySql2DrizzleConfig = Record> = & Omit, 'schema'> @@ -87,7 +89,7 @@ export function drizzle = Record; + return new MySql2Database(dialect, session, schema as any, mode) as MySql2Database; } interface CallbackClient { diff --git a/drizzle-orm/src/mysql2/migrator.ts b/drizzle-orm/src/mysql2/migrator.ts index 2f3c9c3dc..ae56e01f1 100644 --- a/drizzle-orm/src/mysql2/migrator.ts +++ b/drizzle-orm/src/mysql2/migrator.ts @@ -4,8 +4,15 @@ import type { MySql2Database } from './driver.ts'; export async function migrate>( db: MySql2Database, - config: MigrationConfig, + config: MigrationConfig | string, ) { const migrations = readMigrationFiles(config); - await db.dialect.migrate(migrations, db.session, config); + + const preparedConfig = typeof config === 'string' + ? { + migrationsFolder: config, + } + : config; + + await db.dialect.migrate(migrations, db.session, preparedConfig); } diff --git a/drizzle-orm/src/neon-http/session.ts b/drizzle-orm/src/neon-http/session.ts index 6d7685116..4dd768d3e 100644 --- a/drizzle-orm/src/neon-http/session.ts +++ b/drizzle-orm/src/neon-http/session.ts @@ -10,7 +10,7 @@ import type { PgQueryResultHKT, PgTransactionConfig, PreparedQueryConfig } from import { PgPreparedQuery as PgPreparedQuery, PgSession } from '~/pg-core/session.ts'; import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; import type { PreparedQuery } from '~/session.ts'; -import { fillPlaceholders, type Query } from '~/sql/sql.ts'; +import { fillPlaceholders, type Query, type SQL } from '~/sql/sql.ts'; import { mapResultRow } from '~/utils.ts'; export type NeonHttpClient = NeonQueryFunction; @@ -161,6 +161,14 @@ export class NeonHttpSession< return this.client(query, params, { arrayMode: false, fullResults: true }); } + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + + return Number( + res['rows'][0]['count'], + ); + } + override async transaction( _transaction: (tx: NeonTransaction) => Promise, // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/drizzle-orm/src/neon-serverless/driver.ts b/drizzle-orm/src/neon-serverless/driver.ts index 8a15dd678..7f42cfeb3 100644 --- a/drizzle-orm/src/neon-serverless/driver.ts +++ b/drizzle-orm/src/neon-serverless/driver.ts @@ -43,9 +43,11 @@ export class NeonDriver { } } -export type NeonDatabase< +export class NeonDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static readonly [entityKind]: string = 'NeonServerlessDatabase'; +} export function drizzle = Record>( client: NeonClient, @@ -74,5 +76,5 @@ export function drizzle = Record; + return new NeonDatabase(dialect, session, schema as any) as NeonDatabase; } diff --git a/drizzle-orm/src/node-postgres/driver.ts b/drizzle-orm/src/node-postgres/driver.ts index 4c233f891..15ac8fc06 100644 --- a/drizzle-orm/src/node-postgres/driver.ts +++ b/drizzle-orm/src/node-postgres/driver.ts @@ -45,9 +45,11 @@ export class NodePgDriver { } } -export type NodePgDatabase< +export class NodePgDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static readonly [entityKind]: string = 'NodePgDatabase'; +} export function drizzle = Record>( client: NodePgClient, @@ -76,5 +78,5 @@ export function drizzle = Record; + return new NodePgDatabase(dialect, session, schema as any) as NodePgDatabase; } diff --git a/drizzle-orm/src/node-postgres/session.ts b/drizzle-orm/src/node-postgres/session.ts index 91a21312a..ef6779354 100644 --- a/drizzle-orm/src/node-postgres/session.ts +++ b/drizzle-orm/src/node-postgres/session.ts @@ -8,7 +8,7 @@ import type { SelectedFieldsOrdered } from '~/pg-core/query-builders/select.type import type { PgQueryResultHKT, PgTransactionConfig, PreparedQueryConfig } from '~/pg-core/session.ts'; import { PgPreparedQuery, PgSession } from '~/pg-core/session.ts'; import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; -import { fillPlaceholders, type Query, sql } from '~/sql/sql.ts'; +import { fillPlaceholders, type Query, type SQL, sql } from '~/sql/sql.ts'; import { tracer } from '~/tracing.ts'; import { type Assume, mapResultRow } from '~/utils.ts'; @@ -164,6 +164,13 @@ export class NodePgSession< } } } + + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + return Number( + res['rows'][0]['count'], + ); + } } export class NodePgTransaction< diff --git a/drizzle-orm/src/op-sqlite/driver.ts b/drizzle-orm/src/op-sqlite/driver.ts index 24c663abf..94ee6e866 100644 --- a/drizzle-orm/src/op-sqlite/driver.ts +++ b/drizzle-orm/src/op-sqlite/driver.ts @@ -1,4 +1,5 @@ import type { OPSQLiteConnection, QueryResult } from '@op-engineering/op-sqlite'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { createTableRelationsHelpers, @@ -11,9 +12,11 @@ import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts'; import type { DrizzleConfig } from '~/utils.ts'; import { OPSQLiteSession } from './session.ts'; -export type OPSQLiteDatabase< +export class OPSQLiteDatabase< TSchema extends Record = Record, -> = BaseSQLiteDatabase<'async', QueryResult, TSchema>; +> extends BaseSQLiteDatabase<'async', QueryResult, TSchema> { + static readonly [entityKind]: string = 'OPSQLiteDatabase'; +} export function drizzle = Record>( client: OPSQLiteConnection, @@ -41,5 +44,5 @@ export function drizzle = Record; + return new OPSQLiteDatabase('async', dialect, session, schema) as OPSQLiteDatabase; } diff --git a/drizzle-orm/src/operations.ts b/drizzle-orm/src/operations.ts index 492bb3f2a..6fb5cbd2e 100644 --- a/drizzle-orm/src/operations.ts +++ b/drizzle-orm/src/operations.ts @@ -21,6 +21,7 @@ export type OptionalKeyOnly< : T['_']['generated'] extends object ? T['_']['generated']['type'] extends 'byDefault' ? TKey : never : never; +// TODO: SQL -> SQLWrapper export type SelectedFieldsFlat = Record< string, TColumn | SQL | SQL.Aliased diff --git a/drizzle-orm/src/pg-core/db.ts b/drizzle-orm/src/pg-core/db.ts index 4e8d2f354..62b64fb8f 100644 --- a/drizzle-orm/src/pg-core/db.ts +++ b/drizzle-orm/src/pg-core/db.ts @@ -19,15 +19,17 @@ import type { PgTable } from '~/pg-core/table.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; -import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; +import type { ColumnsSelection, SQL, SQLWrapper } from '~/sql/sql.ts'; import { WithSubquery } from '~/subquery.ts'; import type { DrizzleTypeError } from '~/utils.ts'; import type { PgColumn } from './columns/index.ts'; +import { PgCountBuilder } from './query-builders/count.ts'; import { RelationalQueryBuilder } from './query-builders/query.ts'; import { PgRaw } from './query-builders/raw.ts'; import { PgRefreshMaterializedView } from './query-builders/refresh-materialized-view.ts'; import type { SelectedFields } from './query-builders/select.types.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; +import type { PgViewBase } from './view-base.ts'; import type { PgMaterializedView } from './view.ts'; export class PgDatabase< @@ -135,6 +137,13 @@ export class PgDatabase< }; } + $count( + source: PgTable | PgViewBase | SQL | SQLWrapper, + filters?: SQL, + ) { + return new PgCountBuilder({ source, filters, session: this.session }); + } + /** * Incorporates a previously defined CTE (using `$with`) into the main query. * @@ -622,7 +631,11 @@ export const withReplicas = < HKT extends PgQueryResultHKT, TFullSchema extends Record, TSchema extends TablesRelationalConfig, - Q extends PgDatabase, + Q extends PgDatabase< + HKT, + TFullSchema, + TSchema extends Record ? ExtractTablesWithRelations : TSchema + >, >( primary: Q, replicas: [Q, ...Q[]], diff --git a/drizzle-orm/src/pg-core/query-builders/count.ts b/drizzle-orm/src/pg-core/query-builders/count.ts new file mode 100644 index 000000000..c93cbb18d --- /dev/null +++ b/drizzle-orm/src/pg-core/query-builders/count.ts @@ -0,0 +1,79 @@ +import { entityKind, sql } from '~/index.ts'; +import type { SQLWrapper } from '~/sql/sql.ts'; +import { SQL } from '~/sql/sql.ts'; +import type { PgSession } from '../session.ts'; +import type { PgTable } from '../table.ts'; + +export class PgCountBuilder< + TSession extends PgSession, +> extends SQL implements Promise, SQLWrapper { + private sql: SQL; + + static readonly [entityKind] = 'PgCountBuilder'; + [Symbol.toStringTag] = 'PgCountBuilder'; + + private session: TSession; + + private static buildEmbeddedCount( + source: PgTable | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`; + } + + private static buildCount( + source: PgTable | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`select count(*) as count from ${source}${sql.raw(' where ').if(filters)}${filters};`; + } + + constructor( + readonly params: { + source: PgTable | SQL | SQLWrapper; + filters?: SQL; + session: TSession; + }, + ) { + super(PgCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks); + + this.mapWith(Number); + + this.session = params.session; + + this.sql = PgCountBuilder.buildCount( + params.source, + params.filters, + ); + } + + then( + onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined, + ): Promise { + return Promise.resolve(this.session.count(this.sql)) + .then( + onfulfilled, + onrejected, + ); + } + + catch( + onRejected?: ((reason: any) => never | PromiseLike) | null | undefined, + ): Promise { + return this.then(undefined, onRejected); + } + + finally(onFinally?: (() => void) | null | undefined): Promise { + return this.then( + (value) => { + onFinally?.(); + return value; + }, + (reason) => { + onFinally?.(); + throw reason; + }, + ); + } +} diff --git a/drizzle-orm/src/pg-core/session.ts b/drizzle-orm/src/pg-core/session.ts index 434ebc086..ea820f2d8 100644 --- a/drizzle-orm/src/pg-core/session.ts +++ b/drizzle-orm/src/pg-core/session.ts @@ -86,6 +86,14 @@ export abstract class PgSession< ).all(); } + async count(sql: SQL): Promise { + const res = await this.execute<[{ count: string }]>(sql); + + return Number( + res[0]['count'], + ); + } + abstract transaction( transaction: (tx: PgTransaction) => Promise, config?: PgTransactionConfig, diff --git a/drizzle-orm/src/pg-proxy/driver.ts b/drizzle-orm/src/pg-proxy/driver.ts index cdffa15c1..d82e86962 100644 --- a/drizzle-orm/src/pg-proxy/driver.ts +++ b/drizzle-orm/src/pg-proxy/driver.ts @@ -1,3 +1,4 @@ +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { PgDatabase } from '~/pg-core/db.ts'; import { PgDialect } from '~/pg-core/dialect.ts'; @@ -10,9 +11,11 @@ import { import type { DrizzleConfig } from '~/utils.ts'; import { type PgRemoteQueryResultHKT, PgRemoteSession } from './session.ts'; -export type PgRemoteDatabase< +export class PgRemoteDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static readonly [entityKind]: string = 'PgRemoteDatabase'; +} export type RemoteCallback = ( sql: string, @@ -48,5 +51,5 @@ export function drizzle = Record; + return new PgRemoteDatabase(dialect, session, schema as any) as PgRemoteDatabase; } diff --git a/drizzle-orm/src/pg-proxy/session.ts b/drizzle-orm/src/pg-proxy/session.ts index eb6a1b1a3..1a30c0a3c 100644 --- a/drizzle-orm/src/pg-proxy/session.ts +++ b/drizzle-orm/src/pg-proxy/session.ts @@ -130,7 +130,8 @@ export class PreparedQuery extends PreparedQueryB }); } - async all() {} + async all() { + } /** @internal */ isResponseInArrayMode(): boolean { diff --git a/drizzle-orm/src/pglite/driver.ts b/drizzle-orm/src/pglite/driver.ts index 7de2ce110..a801005d8 100644 --- a/drizzle-orm/src/pglite/driver.ts +++ b/drizzle-orm/src/pglite/driver.ts @@ -34,9 +34,11 @@ export class PgliteDriver { } } -export type PgliteDatabase< +export class PgliteDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static readonly [entityKind]: string = 'PgliteDatabase'; +} export function drizzle = Record>( client: PgliteClient, @@ -65,5 +67,5 @@ export function drizzle = Record; + return new PgliteDatabase(dialect, session, schema as any) as PgliteDatabase; } diff --git a/drizzle-orm/src/pglite/session.ts b/drizzle-orm/src/pglite/session.ts index c7a1dbb5d..ebf7701a6 100644 --- a/drizzle-orm/src/pglite/session.ts +++ b/drizzle-orm/src/pglite/session.ts @@ -7,7 +7,7 @@ import type { SelectedFieldsOrdered } from '~/pg-core/query-builders/select.type import type { PgQueryResultHKT, PgTransactionConfig, PreparedQueryConfig } from '~/pg-core/session.ts'; import { PgPreparedQuery, PgSession } from '~/pg-core/session.ts'; import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; -import { fillPlaceholders, type Query, sql } from '~/sql/sql.ts'; +import { fillPlaceholders, type Query, type SQL, sql } from '~/sql/sql.ts'; import { type Assume, mapResultRow } from '~/utils.ts'; import { types } from '@electric-sql/pglite'; @@ -140,6 +140,13 @@ export class PgliteSession< return transaction(tx); }) as Promise; } + + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + return Number( + res['rows'][0]['count'], + ); + } } export class PgliteTransaction< diff --git a/drizzle-orm/src/planetscale-serverless/driver.ts b/drizzle-orm/src/planetscale-serverless/driver.ts index b1d2d6e6f..fd1327bbc 100644 --- a/drizzle-orm/src/planetscale-serverless/driver.ts +++ b/drizzle-orm/src/planetscale-serverless/driver.ts @@ -1,5 +1,6 @@ import type { Connection } from '@planetscale/database'; import { Client } from '@planetscale/database'; +import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; import { MySqlDatabase } from '~/mysql-core/db.ts'; @@ -18,9 +19,11 @@ export interface PlanetscaleSDriverOptions { logger?: Logger; } -export type PlanetScaleDatabase< +export class PlanetScaleDatabase< TSchema extends Record = Record, -> = MySqlDatabase; +> extends MySqlDatabase { + static readonly [entityKind]: string = 'PlanetScaleDatabase'; +} export function drizzle = Record>( client: Client | Connection, @@ -82,5 +85,5 @@ Starting from version 0.30.0, you will encounter an error if you attempt to use } const session = new PlanetscaleSession(client, dialect, undefined, schema, { logger }); - return new MySqlDatabase(dialect, session, schema, 'planetscale') as PlanetScaleDatabase; + return new PlanetScaleDatabase(dialect, session, schema as any, 'planetscale') as PlanetScaleDatabase; } diff --git a/drizzle-orm/src/planetscale-serverless/migrator.ts b/drizzle-orm/src/planetscale-serverless/migrator.ts index 5a668ae01..8b3713602 100644 --- a/drizzle-orm/src/planetscale-serverless/migrator.ts +++ b/drizzle-orm/src/planetscale-serverless/migrator.ts @@ -4,8 +4,15 @@ import type { PlanetScaleDatabase } from './driver.ts'; export async function migrate>( db: PlanetScaleDatabase, - config: MigrationConfig, + config: MigrationConfig | string, ) { const migrations = readMigrationFiles(config); - await db.dialect.migrate(migrations, db.session, config); + + const preparedConfig = typeof config === 'string' + ? { + migrationsFolder: config, + } + : config; + + await db.dialect.migrate(migrations, db.session, preparedConfig); } diff --git a/drizzle-orm/src/planetscale-serverless/session.ts b/drizzle-orm/src/planetscale-serverless/session.ts index f2275b7f2..987529d7c 100644 --- a/drizzle-orm/src/planetscale-serverless/session.ts +++ b/drizzle-orm/src/planetscale-serverless/session.ts @@ -164,6 +164,14 @@ export class PlanetscaleSession< ) => eQuery.rows as T[]); } + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + + return Number( + res['rows'][0]['count'], + ); + } + override transaction( transaction: (tx: PlanetScaleTransaction) => Promise, ): Promise { diff --git a/drizzle-orm/src/postgres-js/driver.ts b/drizzle-orm/src/postgres-js/driver.ts index 7f44344e8..6714cff8d 100644 --- a/drizzle-orm/src/postgres-js/driver.ts +++ b/drizzle-orm/src/postgres-js/driver.ts @@ -1,4 +1,5 @@ import type { Sql } from 'postgres'; +import { entityKind } from '~/entity.ts'; import { DefaultLogger } from '~/logger.ts'; import { PgDatabase } from '~/pg-core/db.ts'; import { PgDialect } from '~/pg-core/dialect.ts'; @@ -12,9 +13,11 @@ import type { DrizzleConfig } from '~/utils.ts'; import type { PostgresJsQueryResultHKT } from './session.ts'; import { PostgresJsSession } from './session.ts'; -export type PostgresJsDatabase< +export class PostgresJsDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static readonly [entityKind]: string = 'PostgresJsDatabase'; +} export function drizzle = Record>( client: Sql, @@ -52,5 +55,5 @@ export function drizzle = Record; + return new PostgresJsDatabase(dialect, session, schema as any) as PostgresJsDatabase; } diff --git a/drizzle-orm/src/singlestore-core/alias.ts b/drizzle-orm/src/singlestore-core/alias.ts new file mode 100644 index 000000000..08e7ecc67 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/alias.ts @@ -0,0 +1,11 @@ +import { TableAliasProxyHandler } from '~/alias.ts'; +import type { BuildAliasTable } from './query-builders/select.types.ts'; +import type { SingleStoreTable } from './table.ts'; +import type { SingleStoreViewBase } from './view-base.ts'; + +export function alias( + table: TTable, + alias: TAlias, +): BuildAliasTable { + return new Proxy(table, new TableAliasProxyHandler(alias, false)) as any; +} diff --git a/drizzle-orm/src/singlestore-core/columns/bigint.ts b/drizzle-orm/src/singlestore-core/columns/bigint.ts new file mode 100644 index 000000000..6ea4a7297 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/bigint.ts @@ -0,0 +1,115 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreBigInt53BuilderInitial = SingleStoreBigInt53Builder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreBigInt53'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBigInt53Builder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreBigInt53Builder'; + + constructor(name: T['name'], unsigned: boolean = false) { + super(name, 'number', 'SingleStoreBigInt53'); + this.config.unsigned = unsigned; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBigInt53> { + return new SingleStoreBigInt53>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBigInt53> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreBigInt53'; + + getSQLType(): string { + return `bigint${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'number') { + return value; + } + return Number(value); + } +} + +export type SingleStoreBigInt64BuilderInitial = SingleStoreBigInt64Builder<{ + name: TName; + dataType: 'bigint'; + columnType: 'SingleStoreBigInt64'; + data: bigint; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBigInt64Builder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreBigInt64Builder'; + + constructor(name: T['name'], unsigned: boolean = false) { + super(name, 'bigint', 'SingleStoreBigInt64'); + this.config.unsigned = unsigned; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBigInt64> { + return new SingleStoreBigInt64>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBigInt64> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreBigInt64'; + + getSQLType(): string { + return `bigint${this.config.unsigned ? ' unsigned' : ''}`; + } + + // eslint-disable-next-line unicorn/prefer-native-coercion-functions + override mapFromDriverValue(value: string): bigint { + return BigInt(value); + } +} + +interface SingleStoreBigIntConfig { + mode: T; + unsigned?: boolean; +} + +export function bigint( + name: TName, + config: SingleStoreBigIntConfig, +): TMode extends 'number' ? SingleStoreBigInt53BuilderInitial : SingleStoreBigInt64BuilderInitial; +export function bigint(name: string, config: SingleStoreBigIntConfig) { + if (config.mode === 'number') { + return new SingleStoreBigInt53Builder(name, config.unsigned); + } + return new SingleStoreBigInt64Builder(name, config.unsigned); +} diff --git a/drizzle-orm/src/singlestore-core/columns/binary.ts b/drizzle-orm/src/singlestore-core/columns/binary.ts new file mode 100644 index 000000000..9cb05ac53 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/binary.ts @@ -0,0 +1,63 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreBinaryBuilderInitial = SingleStoreBinaryBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreBinary'; + data: string; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBinaryBuilder> + extends SingleStoreColumnBuilder< + T, + SingleStoreBinaryConfig + > +{ + static readonly [entityKind]: string = 'SingleStoreBinaryBuilder'; + + constructor(name: T['name'], length: number | undefined) { + super(name, 'string', 'SingleStoreBinary'); + this.config.length = length; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBinary> { + return new SingleStoreBinary>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBinary> extends SingleStoreColumn< + T, + SingleStoreBinaryConfig +> { + static readonly [entityKind]: string = 'SingleStoreBinary'; + + length: number | undefined = this.config.length; + + getSQLType(): string { + return this.length === undefined ? `binary` : `binary(${this.length})`; + } +} + +export interface SingleStoreBinaryConfig { + length?: number; +} + +export function binary( + name: TName, + config: SingleStoreBinaryConfig = {}, +): SingleStoreBinaryBuilderInitial { + return new SingleStoreBinaryBuilder(name, config.length); +} diff --git a/drizzle-orm/src/singlestore-core/columns/blob.ts b/drizzle-orm/src/singlestore-core/columns/blob.ts new file mode 100644 index 000000000..0885253f1 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/blob.ts @@ -0,0 +1,168 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { Equal } from '~/utils'; +import type { AnySingleStoreTable } from '../table'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +type BlobMode = 'buffer' | 'json' | 'bigint'; + +export type SingleStoreBigIntBuilderInitial = SingleStoreBigIntBuilder<{ + name: TName; + dataType: 'bigint'; + columnType: 'SingleStoreBigInt'; + data: bigint; + driverParam: Buffer; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBigIntBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreBigIntBuilder'; + + constructor(name: T['name']) { + super(name, 'bigint', 'SingleStoreBigInt'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBigInt> { + return new SingleStoreBigInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBigInt> extends SingleStoreColumn { + static readonly [entityKind]: string = 'SingleStoreBigInt'; + + getSQLType(): string { + return 'blob'; + } + + override mapFromDriverValue(value: Buffer): bigint { + return BigInt(value.toString()); + } + + override mapToDriverValue(value: bigint): Buffer { + return Buffer.from(value.toString()); + } +} + +export type SingleStoreBlobJsonBuilderInitial = SingleStoreBlobJsonBuilder<{ + name: TName; + dataType: 'json'; + columnType: 'SingleStoreBlobJson'; + data: unknown; + driverParam: Buffer; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBlobJsonBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreBlobJsonBuilder'; + + constructor(name: T['name']) { + super(name, 'json', 'SingleStoreBlobJson'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBlobJson> { + return new SingleStoreBlobJson>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBlobJson> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreBlobJson'; + + getSQLType(): string { + return 'blob'; + } + + override mapFromDriverValue(value: Buffer): T['data'] { + return JSON.parse(value.toString()); + } + + override mapToDriverValue(value: T['data']): Buffer { + return Buffer.from(JSON.stringify(value)); + } +} + +export type SingleStoreBlobBufferBuilderInitial = SingleStoreBlobBufferBuilder<{ + name: TName; + dataType: 'buffer'; + columnType: 'SingleStoreBlobBuffer'; + data: Buffer; + driverParam: Buffer; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBlobBufferBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreBlobBufferBuilder'; + + constructor(name: T['name']) { + super(name, 'buffer', 'SingleStoreBlobBuffer'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBlobBuffer> { + return new SingleStoreBlobBuffer>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBlobBuffer> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreBlobBuffer'; + + getSQLType(): string { + return 'blob'; + } +} + +export interface BlobConfig { + mode: TMode; +} + +/** + * It's recommended to use `text('...', { mode: 'json' })` instead of `blob` in JSON mode, because it supports JSON functions: + * >All JSON functions currently throw an error if any of their arguments are BLOBs because BLOBs are reserved for a future enhancement in which BLOBs will store the binary encoding for JSON. + * + * https://www.sqlite.org/json1.html + */ +export function blob( + name: TName, + config?: BlobConfig, +): Equal extends true ? SingleStoreBigIntBuilderInitial + : Equal extends true ? SingleStoreBlobBufferBuilderInitial + : SingleStoreBlobJsonBuilderInitial; +export function blob(name: string, config?: BlobConfig) { + if (config?.mode === 'json') { + return new SingleStoreBlobJsonBuilder(name); + } + if (config?.mode === 'bigint') { + return new SingleStoreBigIntBuilder(name); + } + return new SingleStoreBlobBufferBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/columns/boolean.ts b/drizzle-orm/src/singlestore-core/columns/boolean.ts new file mode 100644 index 000000000..795b12e7f --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/boolean.ts @@ -0,0 +1,56 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreBooleanBuilderInitial = SingleStoreBooleanBuilder<{ + name: TName; + dataType: 'boolean'; + columnType: 'SingleStoreBoolean'; + data: boolean; + driverParam: number | boolean; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreBooleanBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreBooleanBuilder'; + + constructor(name: T['name']) { + super(name, 'boolean', 'SingleStoreBoolean'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreBoolean> { + return new SingleStoreBoolean>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreBoolean> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreBoolean'; + + getSQLType(): string { + return 'boolean'; + } + + override mapFromDriverValue(value: number | boolean): boolean { + if (typeof value === 'boolean') { + return value; + } + return value === 1; + } +} + +export function boolean(name: TName): SingleStoreBooleanBuilderInitial { + return new SingleStoreBooleanBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/columns/char.ts b/drizzle-orm/src/singlestore-core/columns/char.ts new file mode 100644 index 000000000..f59c173cb --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/char.ts @@ -0,0 +1,67 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Writable } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreCharBuilderInitial = + SingleStoreCharBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreChar'; + data: TEnum[number]; + driverParam: number | string; + enumValues: TEnum; + generated: undefined; + }>; + +export class SingleStoreCharBuilder> + extends SingleStoreColumnBuilder< + T, + SingleStoreCharConfig + > +{ + static readonly [entityKind]: string = 'SingleStoreCharBuilder'; + + constructor(name: T['name'], config: SingleStoreCharConfig) { + super(name, 'string', 'SingleStoreChar'); + this.config.length = config.length; + this.config.enum = config.enum; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreChar & { enumValues: T['enumValues'] }> { + return new SingleStoreChar & { enumValues: T['enumValues'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreChar> + extends SingleStoreColumn> +{ + static readonly [entityKind]: string = 'SingleStoreChar'; + + readonly length: number | undefined = this.config.length; + override readonly enumValues = this.config.enum; + + getSQLType(): string { + return this.length === undefined ? `char` : `char(${this.length})`; + } +} + +export interface SingleStoreCharConfig { + length?: number; + enum?: TEnum; +} + +export function char>( + name: TName, + config: SingleStoreCharConfig> = {}, +): SingleStoreCharBuilderInitial> { + return new SingleStoreCharBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/common.ts b/drizzle-orm/src/singlestore-core/columns/common.ts new file mode 100644 index 000000000..176265677 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/common.ts @@ -0,0 +1,116 @@ +import { ColumnBuilder } from '~/column-builder.ts'; +import type { + ColumnBuilderBase, + ColumnBuilderBaseConfig, + ColumnBuilderExtraConfig, + ColumnBuilderRuntimeConfig, + ColumnDataType, + HasDefault, + HasGenerated, + IsAutoincrement, + MakeColumnConfig, +} from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { Column } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable, SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { SQL } from '~/sql/sql.ts'; +import type { Update } from '~/utils.ts'; +import { uniqueKeyName } from '../unique-constraint.ts'; + +export interface SingleStoreColumnBuilderBase< + T extends ColumnBuilderBaseConfig = ColumnBuilderBaseConfig, + TTypeConfig extends object = object, +> extends ColumnBuilderBase {} + +export interface SingleStoreGeneratedColumnConfig { + mode?: 'virtual' | 'stored'; +} + +export abstract class SingleStoreColumnBuilder< + T extends ColumnBuilderBaseConfig = ColumnBuilderBaseConfig & { + data: any; + }, + TRuntimeConfig extends object = object, + TTypeConfig extends object = object, + TExtraConfig extends ColumnBuilderExtraConfig = ColumnBuilderExtraConfig, +> extends ColumnBuilder + implements SingleStoreColumnBuilderBase +{ + static readonly [entityKind]: string = 'SingleStoreColumnBuilder'; + + unique(name?: string): this { + this.config.isUnique = true; + this.config.uniqueName = name; + return this; + } + + generatedAlwaysAs(as: SQL | T['data'] | (() => SQL), config?: SingleStoreGeneratedColumnConfig): HasGenerated { + this.config.generated = { + as, + type: 'always', + mode: config?.mode ?? 'virtual', + }; + return this as any; + } + + /** @internal */ + abstract build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreColumn>; +} + +// To understand how to use `SingleStoreColumn` and `AnySingleStoreColumn`, see `Column` and `AnyColumn` documentation. +export abstract class SingleStoreColumn< + T extends ColumnBaseConfig = ColumnBaseConfig, + TRuntimeConfig extends object = object, +> extends Column { + static readonly [entityKind]: string = 'SingleStoreColumn'; + + constructor( + override readonly table: SingleStoreTable, + config: ColumnBuilderRuntimeConfig, + ) { + if (!config.uniqueName) { + config.uniqueName = uniqueKeyName(table, [config.name]); + } + super(table, config); + } +} + +export type AnySingleStoreColumn> = {}> = + SingleStoreColumn< + Required, TPartial>> + >; + +export interface SingleStoreColumnWithAutoIncrementConfig { + autoIncrement: boolean; +} + +export abstract class SingleStoreColumnBuilderWithAutoIncrement< + T extends ColumnBuilderBaseConfig = ColumnBuilderBaseConfig, + TRuntimeConfig extends object = object, + TExtraConfig extends ColumnBuilderExtraConfig = ColumnBuilderExtraConfig, +> extends SingleStoreColumnBuilder { + static readonly [entityKind]: string = 'SingleStoreColumnBuilderWithAutoIncrement'; + + constructor(name: NonNullable, dataType: T['dataType'], columnType: T['columnType']) { + super(name, dataType, columnType); + this.config.autoIncrement = false; + } + + autoincrement(): IsAutoincrement> { + this.config.autoIncrement = true; + this.config.hasDefault = true; + return this as IsAutoincrement>; + } +} + +export abstract class SingleStoreColumnWithAutoIncrement< + T extends ColumnBaseConfig = ColumnBaseConfig, + TRuntimeConfig extends object = object, +> extends SingleStoreColumn { + static readonly [entityKind]: string = 'SingleStoreColumnWithAutoIncrement'; + + readonly autoIncrement: boolean = this.config.autoIncrement; +} diff --git a/drizzle-orm/src/singlestore-core/columns/custom.ts b/drizzle-orm/src/singlestore-core/columns/custom.ts new file mode 100644 index 000000000..727099884 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/custom.ts @@ -0,0 +1,227 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { SQL } from '~/sql/sql.ts'; +import type { Equal } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type ConvertCustomConfig> = + & { + name: TName; + dataType: 'custom'; + columnType: 'SingleStoreCustomColumn'; + data: T['data']; + driverParam: T['driverData']; + enumValues: undefined; + generated: undefined; + } + & (T['notNull'] extends true ? { notNull: true } : {}) + & (T['default'] extends true ? { hasDefault: true } : {}); + +export interface SingleStoreCustomColumnInnerConfig { + customTypeValues: CustomTypeValues; +} + +export class SingleStoreCustomColumnBuilder> + extends SingleStoreColumnBuilder< + T, + { + fieldConfig: CustomTypeValues['config']; + customTypeParams: CustomTypeParams; + }, + { + singlestoreColumnBuilderBrand: 'SingleStoreCustomColumnBuilderBrand'; + } + > +{ + static readonly [entityKind]: string = 'SingleStoreCustomColumnBuilder'; + + constructor( + name: T['name'], + fieldConfig: CustomTypeValues['config'], + customTypeParams: CustomTypeParams, + ) { + super(name, 'custom', 'SingleStoreCustomColumn'); + this.config.fieldConfig = fieldConfig; + this.config.customTypeParams = customTypeParams; + } + + /** @internal */ + build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreCustomColumn> { + return new SingleStoreCustomColumn>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreCustomColumn> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreCustomColumn'; + + private sqlName: string; + private mapTo?: (value: T['data']) => T['driverParam']; + private mapFrom?: (value: T['driverParam']) => T['data']; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreCustomColumnBuilder['config'], + ) { + super(table, config); + this.sqlName = config.customTypeParams.dataType(config.fieldConfig); + this.mapTo = config.customTypeParams.toDriver; + this.mapFrom = config.customTypeParams.fromDriver; + } + + getSQLType(): string { + return this.sqlName; + } + + override mapFromDriverValue(value: T['driverParam']): T['data'] { + return typeof this.mapFrom === 'function' ? this.mapFrom(value) : value as T['data']; + } + + override mapToDriverValue(value: T['data']): T['driverParam'] { + return typeof this.mapTo === 'function' ? this.mapTo(value) : value as T['data']; + } +} + +export type CustomTypeValues = { + /** + * Required type for custom column, that will infer proper type model + * + * Examples: + * + * If you want your column to be `string` type after selecting/or on inserting - use `data: string`. Like `text`, `varchar` + * + * If you want your column to be `number` type after selecting/or on inserting - use `data: number`. Like `integer` + */ + data: unknown; + + /** + * Type helper, that represents what type database driver is accepting for specific database data type + */ + driverData?: unknown; + + /** + * What config type should be used for {@link CustomTypeParams} `dataType` generation + */ + config?: unknown; + + /** + * Whether the config argument should be required or not + * @default false + */ + configRequired?: boolean; + + /** + * If your custom data type should be notNull by default you can use `notNull: true` + * + * @example + * const customSerial = customType<{ data: number, notNull: true, default: true }>({ + * dataType() { + * return 'serial'; + * }, + * }); + */ + notNull?: boolean; + + /** + * If your custom data type has default you can use `default: true` + * + * @example + * const customSerial = customType<{ data: number, notNull: true, default: true }>({ + * dataType() { + * return 'serial'; + * }, + * }); + */ + default?: boolean; +}; + +export interface CustomTypeParams { + /** + * Database data type string representation, that is used for migrations + * @example + * ``` + * `jsonb`, `text` + * ``` + * + * If database data type needs additional params you can use them from `config` param + * @example + * ``` + * `varchar(256)`, `numeric(2,3)` + * ``` + * + * To make `config` be of specific type please use config generic in {@link CustomTypeValues} + * + * @example + * Usage example + * ``` + * dataType() { + * return 'boolean'; + * }, + * ``` + * Or + * ``` + * dataType(config) { + * return typeof config.length !== 'undefined' ? `varchar(${config.length})` : `varchar`; + * } + * ``` + */ + dataType: (config: T['config'] | (Equal extends true ? never : undefined)) => string; + + /** + * Optional mapping function, between user input and driver + * @example + * For example, when using jsonb we need to map JS/TS object to string before writing to database + * ``` + * toDriver(value: TData): string { + * return JSON.stringify(value); + * } + * ``` + */ + toDriver?: (value: T['data']) => T['driverData'] | SQL; + + /** + * Optional mapping function, that is responsible for data mapping from database to JS/TS code + * @example + * For example, when using timestamp we need to map string Date representation to JS Date + * ``` + * fromDriver(value: string): Date { + * return new Date(value); + * }, + * ``` + */ + fromDriver?: (value: T['driverData']) => T['data']; +} + +/** + * Custom singlestore database data type generator + */ +export function customType( + customTypeParams: CustomTypeParams, +): Equal extends true ? ( + dbName: TName, + fieldConfig: T['config'], + ) => SingleStoreCustomColumnBuilder> + : ( + dbName: TName, + fieldConfig?: T['config'], + ) => SingleStoreCustomColumnBuilder> +{ + return ( + dbName: TName, + fieldConfig?: T['config'], + ): SingleStoreCustomColumnBuilder> => { + return new SingleStoreCustomColumnBuilder( + dbName as ConvertCustomConfig['name'], + fieldConfig, + customTypeParams, + ); + }; +} diff --git a/drizzle-orm/src/singlestore-core/columns/date.common.ts b/drizzle-orm/src/singlestore-core/columns/date.common.ts new file mode 100644 index 000000000..a02e7cc00 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/date.common.ts @@ -0,0 +1,52 @@ +import type { + ColumnBuilderBaseConfig, + ColumnBuilderExtraConfig, + ColumnDataType, + HasDefault, +} from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import { sql } from '~/sql/sql.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; +import type { DatetimeFsp } from './datetime.ts'; + +export interface SingleStoreDateColumnBaseConfig { + hasOnUpdateNow: boolean; +} + +export abstract class SingleStoreDateColumnBaseBuilder< + T extends ColumnBuilderBaseConfig, + TRuntimeConfig extends object = object, + TExtraConfig extends ColumnBuilderExtraConfig = ColumnBuilderExtraConfig, +> extends SingleStoreColumnBuilder { + static readonly [entityKind]: string = 'SingleStoreDateColumnBuilder'; + + defaultNow(fsp?: DatetimeFsp | undefined) { + return fsp + ? this.default(sql`(now(${fsp}))`) + : this.default(sql`(now())`); + } + + defaultCurrentTimestamp(fsp?: DatetimeFsp | undefined) { + return fsp + ? this.default(sql`(current_timestamp(${fsp}))`) + : this.default(sql`(current_timestamp())`); + } + + // "on update now" also adds an implicit default value to the column - https://dev.mysql.com/doc/refman/8.0/en/timestamp-initialization.html + // TODO(singlestore) + onUpdateNow(): HasDefault { + this.config.hasOnUpdateNow = true; + this.config.hasDefault = true; + return this as HasDefault; + } +} + +export abstract class SingleStoreDateBaseColumn< + T extends ColumnBaseConfig, + TRuntimeConfig extends object = object, +> extends SingleStoreColumn { + static readonly [entityKind]: string = 'SingleStoreDateColumn'; + + readonly hasOnUpdateNow: boolean = this.config.hasOnUpdateNow; +} diff --git a/drizzle-orm/src/singlestore-core/columns/date.ts b/drizzle-orm/src/singlestore-core/columns/date.ts new file mode 100644 index 000000000..1c64fe3f1 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/date.ts @@ -0,0 +1,118 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Equal } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreDateBuilderInitial = SingleStoreDateBuilder<{ + name: TName; + dataType: 'date'; + columnType: 'SingleStoreDate'; + data: Date; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDateBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreDateBuilder'; + + constructor(name: T['name']) { + super(name, 'date', 'SingleStoreDate'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDate> { + return new SingleStoreDate>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDate> extends SingleStoreColumn { + static readonly [entityKind]: string = 'SingleStoreDate'; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreDateBuilder['config'], + ) { + super(table, config); + } + + getSQLType(): string { + return `date`; + } + + override mapFromDriverValue(value: string): Date { + return new Date(value); + } +} + +export type SingleStoreDateStringBuilderInitial = SingleStoreDateStringBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreDateString'; + data: string; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDateStringBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreDateStringBuilder'; + + constructor(name: T['name']) { + super(name, 'string', 'SingleStoreDateString'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDateString> { + return new SingleStoreDateString>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDateString> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreDateString'; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreDateStringBuilder['config'], + ) { + super(table, config); + } + + getSQLType(): string { + return `date`; + } +} + +export interface SingleStoreDateConfig { + mode?: TMode; +} + +export function date( + name: TName, + config?: SingleStoreDateConfig, +): Equal extends true ? SingleStoreDateStringBuilderInitial + : SingleStoreDateBuilderInitial; +export function date(name: string, config: SingleStoreDateConfig = {}) { + if (config.mode === 'string') { + return new SingleStoreDateStringBuilder(name); + } + return new SingleStoreDateBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/columns/datetime.ts b/drizzle-orm/src/singlestore-core/columns/datetime.ts new file mode 100644 index 000000000..0b000c030 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/datetime.ts @@ -0,0 +1,150 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { sql } from '~/sql/sql.ts'; +import type { Equal } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreDateTimeBuilderInitial = SingleStoreDateTimeBuilder<{ + name: TName; + dataType: 'date'; + columnType: 'SingleStoreDateTime'; + data: Date; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDateTimeBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreDateTimeBuilder'; + + constructor(name: T['name'], config: SingleStoreDatetimeConfig | undefined) { + super(name, 'date', 'SingleStoreDateTime'); + this.config.fsp = config?.fsp; + } + + defaultNow(fsp?: DatetimeFsp | undefined) { + return fsp + ? this.default(sql`(now(${fsp}))`) + : this.default(sql`(now())`); + } + + defaultCurrentTimestamp(fsp?: DatetimeFsp | undefined) { + return fsp + ? this.default(sql`(current_timestamp(${fsp}))`) + : this.default(sql`(current_timestamp())`); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDateTime> { + return new SingleStoreDateTime>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDateTime> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreDateTime'; + + readonly fsp: number | undefined; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreDateTimeBuilder['config'], + ) { + super(table, config); + this.fsp = config.fsp; + } + + getSQLType(): string { + const precision = this.fsp === undefined ? '' : `(${this.fsp})`; + return `datetime${precision}`; + } + + override mapToDriverValue(value: Date): unknown { + return value.toISOString().replace('T', ' ').replace('Z', ''); + } + + override mapFromDriverValue(value: string): Date { + return new Date(value.replace(' ', 'T') + 'Z'); + } +} + +export type SingleStoreDateTimeStringBuilderInitial = SingleStoreDateTimeStringBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreDateTimeString'; + data: string; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDateTimeStringBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreDateTimeStringBuilder'; + + constructor(name: T['name'], config: SingleStoreDatetimeConfig | undefined) { + super(name, 'string', 'SingleStoreDateTimeString'); + this.config.fsp = config?.fsp; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDateTimeString> { + return new SingleStoreDateTimeString>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDateTimeString> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreDateTimeString'; + + readonly fsp: number | undefined; + + constructor( + table: AnySingleStoreTable<{ name: T['tableName'] }>, + config: SingleStoreDateTimeStringBuilder['config'], + ) { + super(table, config); + this.fsp = config.fsp; + } + + getSQLType(): string { + const precision = this.fsp === undefined ? '' : `(${this.fsp})`; + return `datetime${precision}`; + } +} + +export type DatetimeFsp = 0 | 1 | 2 | 3 | 4 | 5 | 6; + +export interface SingleStoreDatetimeConfig { + mode?: TMode; + fsp?: DatetimeFsp; +} + +export function datetime( + name: TName, + config?: SingleStoreDatetimeConfig, +): Equal extends true ? SingleStoreDateTimeStringBuilderInitial + : SingleStoreDateTimeBuilderInitial; +export function datetime(name: string, config: SingleStoreDatetimeConfig = {}) { + if (config.mode === 'string') { + return new SingleStoreDateTimeStringBuilder(name, config); + } + return new SingleStoreDateTimeBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/decimal.ts b/drizzle-orm/src/singlestore-core/columns/decimal.ts new file mode 100644 index 000000000..e5095c4d8 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/decimal.ts @@ -0,0 +1,68 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreDecimalBuilderInitial = SingleStoreDecimalBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreDecimal'; + data: string; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDecimalBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SingleStoreDecimal'>, +> extends SingleStoreColumnBuilderWithAutoIncrement { + static readonly [entityKind]: string = 'SingleStoreDecimalBuilder'; + + constructor(name: T['name'], precision?: number, scale?: number) { + super(name, 'string', 'SingleStoreDecimal'); + this.config.precision = precision; + this.config.scale = scale; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDecimal> { + return new SingleStoreDecimal>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDecimal> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreDecimal'; + + readonly precision: number | undefined = this.config.precision; + readonly scale: number | undefined = this.config.scale; + + getSQLType(): string { + if (this.precision !== undefined && this.scale !== undefined) { + return `decimal(${this.precision},${this.scale})`; + } else if (this.precision === undefined) { + return 'decimal'; + } else { + return `decimal(${this.precision})`; + } + } +} + +export interface SingleStoreDecimalConfig { + precision?: number; + scale?: number; +} + +export function decimal( + name: TName, + config: SingleStoreDecimalConfig = {}, +): SingleStoreDecimalBuilderInitial { + return new SingleStoreDecimalBuilder(name, config.precision, config.scale); +} diff --git a/drizzle-orm/src/singlestore-core/columns/double.ts b/drizzle-orm/src/singlestore-core/columns/double.ts new file mode 100644 index 000000000..6ca945431 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/double.ts @@ -0,0 +1,68 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreDoubleBuilderInitial = SingleStoreDoubleBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreDouble'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreDoubleBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreDoubleBuilder'; + + constructor(name: T['name'], config: SingleStoreDoubleConfig | undefined) { + super(name, 'number', 'SingleStoreDouble'); + this.config.precision = config?.precision; + this.config.scale = config?.scale; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreDouble> { + return new SingleStoreDouble>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreDouble> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreDouble'; + + precision: number | undefined = this.config.precision; + scale: number | undefined = this.config.scale; + + getSQLType(): string { + if (this.precision !== undefined && this.scale !== undefined) { + return `double(${this.precision},${this.scale})`; + } else if (this.precision === undefined) { + return 'double'; + } else { + return `double(${this.precision})`; + } + } +} + +export interface SingleStoreDoubleConfig { + precision?: number; + scale?: number; +} + +export function double( + name: TName, + config?: SingleStoreDoubleConfig, +): SingleStoreDoubleBuilderInitial { + return new SingleStoreDoubleBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/enum.ts b/drizzle-orm/src/singlestore-core/columns/enum.ts new file mode 100644 index 000000000..af04c50a5 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/enum.ts @@ -0,0 +1,61 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Writable } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreEnumColumnBuilderInitial = + SingleStoreEnumColumnBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreEnumColumn'; + data: TEnum[number]; + driverParam: string; + enumValues: TEnum; + generated: undefined; + }>; + +export class SingleStoreEnumColumnBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreEnumColumnBuilder'; + + constructor(name: T['name'], values: T['enumValues']) { + super(name, 'string', 'SingleStoreEnumColumn'); + this.config.enumValues = values; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreEnumColumn & { enumValues: T['enumValues'] }> { + return new SingleStoreEnumColumn & { enumValues: T['enumValues'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreEnumColumn> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreEnumColumn'; + + override readonly enumValues = this.config.enumValues; + + getSQLType(): string { + return `enum(${this.enumValues!.map((value) => `'${value}'`).join(',')})`; + } +} + +export function singlestoreEnum>( + name: TName, + values: T | Writable, +): SingleStoreEnumColumnBuilderInitial> { + if (values.length === 0) { + throw new Error(`You have an empty array for "${name}" enum values`); + } + + return new SingleStoreEnumColumnBuilder(name, values); +} diff --git a/drizzle-orm/src/singlestore-core/columns/float.ts b/drizzle-orm/src/singlestore-core/columns/float.ts new file mode 100644 index 000000000..a54ee06a1 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/float.ts @@ -0,0 +1,49 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreFloatBuilderInitial = SingleStoreFloatBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreFloat'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreFloatBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreFloatBuilder'; + + constructor(name: T['name']) { + super(name, 'number', 'SingleStoreFloat'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreFloat> { + return new SingleStoreFloat>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreFloat> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreFloat'; + + getSQLType(): string { + return 'float'; + } +} + +export function float(name: TName): SingleStoreFloatBuilderInitial { + return new SingleStoreFloatBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/columns/index.ts b/drizzle-orm/src/singlestore-core/columns/index.ts new file mode 100644 index 000000000..b51f0fac4 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/index.ts @@ -0,0 +1,25 @@ +export * from './bigint.ts'; +export * from './binary.ts'; +export * from './boolean.ts'; +export * from './char.ts'; +export * from './common.ts'; +export * from './custom.ts'; +export * from './date.ts'; +export * from './datetime.ts'; +export * from './decimal.ts'; +export * from './double.ts'; +export * from './enum.ts'; +export * from './float.ts'; +export * from './int.ts'; +export * from './json.ts'; +export * from './mediumint.ts'; +export * from './real.ts'; +export * from './serial.ts'; +export * from './smallint.ts'; +export * from './text.ts'; +export * from './time.ts'; +export * from './timestamp.ts'; +export * from './tinyint.ts'; +export * from './varbinary.ts'; +export * from './varchar.ts'; +export * from './year.ts'; diff --git a/drizzle-orm/src/singlestore-core/columns/int.ts b/drizzle-orm/src/singlestore-core/columns/int.ts new file mode 100644 index 000000000..e9ca0a682 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/int.ts @@ -0,0 +1,64 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreIntBuilderInitial = SingleStoreIntBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreIntBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreIntBuilder'; + + constructor(name: T['name'], config?: SingleStoreIntConfig) { + super(name, 'number', 'SingleStoreInt'); + this.config.unsigned = config ? config.unsigned : false; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreInt> { + return new SingleStoreInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreInt> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreInt'; + + getSQLType(): string { + return `int${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export interface SingleStoreIntConfig { + unsigned?: boolean; +} + +export function int( + name: TName, + config?: SingleStoreIntConfig, +): SingleStoreIntBuilderInitial { + return new SingleStoreIntBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/json.ts b/drizzle-orm/src/singlestore-core/columns/json.ts new file mode 100644 index 000000000..0b069f256 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/json.ts @@ -0,0 +1,51 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreJsonBuilderInitial = SingleStoreJsonBuilder<{ + name: TName; + dataType: 'json'; + columnType: 'SingleStoreJson'; + data: unknown; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreJsonBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreJsonBuilder'; + + constructor(name: T['name']) { + super(name, 'json', 'SingleStoreJson'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreJson> { + return new SingleStoreJson>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreJson> extends SingleStoreColumn { + static readonly [entityKind]: string = 'SingleStoreJson'; + + getSQLType(): string { + return 'json'; + } + + override mapToDriverValue(value: T['data']): string { + return JSON.stringify(value); + } +} + +export function json(name: TName): SingleStoreJsonBuilderInitial { + return new SingleStoreJsonBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/columns/mediumint.ts b/drizzle-orm/src/singlestore-core/columns/mediumint.ts new file mode 100644 index 000000000..b963ee6e5 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/mediumint.ts @@ -0,0 +1,61 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; +import type { SingleStoreIntConfig } from './int.ts'; + +export type SingleStoreMediumIntBuilderInitial = SingleStoreMediumIntBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreMediumInt'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreMediumIntBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreMediumIntBuilder'; + + constructor(name: T['name'], config?: SingleStoreIntConfig) { + super(name, 'number', 'SingleStoreMediumInt'); + this.config.unsigned = config ? config.unsigned : false; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreMediumInt> { + return new SingleStoreMediumInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreMediumInt> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreMediumInt'; + + getSQLType(): string { + return `mediumint${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export function mediumint( + name: TName, + config?: SingleStoreIntConfig, +): SingleStoreMediumIntBuilderInitial { + return new SingleStoreMediumIntBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/real.ts b/drizzle-orm/src/singlestore-core/columns/real.ts new file mode 100644 index 000000000..cc66f6c56 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/real.ts @@ -0,0 +1,74 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreRealBuilderInitial = SingleStoreRealBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreReal'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreRealBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement< + T, + SingleStoreRealConfig + > +{ + static readonly [entityKind]: string = 'SingleStoreRealBuilder'; + + constructor(name: T['name'], config: SingleStoreRealConfig | undefined) { + super(name, 'number', 'SingleStoreReal'); + this.config.precision = config?.precision; + this.config.scale = config?.scale; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreReal> { + return new SingleStoreReal>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreReal> + extends SingleStoreColumnWithAutoIncrement< + T, + SingleStoreRealConfig + > +{ + static readonly [entityKind]: string = 'SingleStoreReal'; + + precision: number | undefined = this.config.precision; + scale: number | undefined = this.config.scale; + + getSQLType(): string { + if (this.precision !== undefined && this.scale !== undefined) { + return `real(${this.precision}, ${this.scale})`; + } else if (this.precision === undefined) { + return 'real'; + } else { + return `real(${this.precision})`; + } + } +} + +export interface SingleStoreRealConfig { + precision?: number; + scale?: number; +} + +export function real( + name: TName, + config: SingleStoreRealConfig = {}, +): SingleStoreRealBuilderInitial { + return new SingleStoreRealBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/serial.ts b/drizzle-orm/src/singlestore-core/columns/serial.ts new file mode 100644 index 000000000..30fb7a40e --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/serial.ts @@ -0,0 +1,74 @@ +import type { + ColumnBuilderBaseConfig, + ColumnBuilderRuntimeConfig, + HasDefault, + IsAutoincrement, + IsPrimaryKey, + MakeColumnConfig, + NotNull, +} from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; + +export type SingleStoreSerialBuilderInitial = IsAutoincrement< + IsPrimaryKey< + NotNull< + HasDefault< + SingleStoreSerialBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + enumValues: undefined; + generated: undefined; + }> + > + > + > +>; + +export class SingleStoreSerialBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreSerialBuilder'; + + constructor(name: T['name']) { + super(name, 'number', 'SingleStoreSerial'); + this.config.hasDefault = true; + this.config.autoIncrement = true; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreSerial> { + return new SingleStoreSerial>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreSerial< + T extends ColumnBaseConfig<'number', 'SingleStoreSerial'>, +> extends SingleStoreColumnWithAutoIncrement { + static readonly [entityKind]: string = 'SingleStoreSerial'; + + getSQLType(): string { + return 'serial'; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export function serial(name: TName): SingleStoreSerialBuilderInitial { + return new SingleStoreSerialBuilder(name) as SingleStoreSerialBuilderInitial; +} diff --git a/drizzle-orm/src/singlestore-core/columns/smallint.ts b/drizzle-orm/src/singlestore-core/columns/smallint.ts new file mode 100644 index 000000000..02e172608 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/smallint.ts @@ -0,0 +1,61 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; +import type { SingleStoreIntConfig } from './int.ts'; + +export type SingleStoreSmallIntBuilderInitial = SingleStoreSmallIntBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreSmallInt'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreSmallIntBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreSmallIntBuilder'; + + constructor(name: T['name'], config?: SingleStoreIntConfig) { + super(name, 'number', 'SingleStoreSmallInt'); + this.config.unsigned = config ? config.unsigned : false; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreSmallInt> { + return new SingleStoreSmallInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreSmallInt> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreSmallInt'; + + getSQLType(): string { + return `smallint${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export function smallint( + name: TName, + config?: SingleStoreIntConfig, +): SingleStoreSmallIntBuilderInitial { + return new SingleStoreSmallIntBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/text.ts b/drizzle-orm/src/singlestore-core/columns/text.ts new file mode 100644 index 000000000..8c40039f0 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/text.ts @@ -0,0 +1,90 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Writable } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreTextColumnType = 'tinytext' | 'text' | 'mediumtext' | 'longtext'; + +export type SingleStoreTextBuilderInitial = + SingleStoreTextBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreText'; + data: TEnum[number]; + driverParam: string; + enumValues: TEnum; + generated: undefined; + }>; + +export class SingleStoreTextBuilder> + extends SingleStoreColumnBuilder< + T, + { textType: SingleStoreTextColumnType; enumValues: T['enumValues'] } + > +{ + static readonly [entityKind]: string = 'SingleStoreTextBuilder'; + + constructor(name: T['name'], textType: SingleStoreTextColumnType, config: SingleStoreTextConfig) { + super(name, 'string', 'SingleStoreText'); + this.config.textType = textType; + this.config.enumValues = config.enum; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreText> { + return new SingleStoreText>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreText> + extends SingleStoreColumn +{ + static readonly [entityKind]: string = 'SingleStoreText'; + + private textType: SingleStoreTextColumnType = this.config.textType; + + override readonly enumValues = this.config.enumValues; + + getSQLType(): string { + return this.textType; + } +} + +export interface SingleStoreTextConfig { + enum?: TEnum; +} + +export function text>( + name: TName, + config: SingleStoreTextConfig> = {}, +): SingleStoreTextBuilderInitial> { + return new SingleStoreTextBuilder(name, 'text', config); +} + +export function tinytext>( + name: TName, + config: SingleStoreTextConfig> = {}, +): SingleStoreTextBuilderInitial> { + return new SingleStoreTextBuilder(name, 'tinytext', config); +} + +export function mediumtext>( + name: TName, + config: SingleStoreTextConfig> = {}, +): SingleStoreTextBuilderInitial> { + return new SingleStoreTextBuilder(name, 'mediumtext', config); +} + +export function longtext>( + name: TName, + config: SingleStoreTextConfig> = {}, +): SingleStoreTextBuilderInitial> { + return new SingleStoreTextBuilder(name, 'longtext', config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/time.ts b/drizzle-orm/src/singlestore-core/columns/time.ts new file mode 100644 index 000000000..a5259adbb --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/time.ts @@ -0,0 +1,63 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreTimeBuilderInitial = SingleStoreTimeBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreTime'; + data: string; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreTimeBuilder> + extends SingleStoreColumnBuilder< + T, + TimeConfig + > +{ + static readonly [entityKind]: string = 'SingleStoreTimeBuilder'; + + constructor( + name: T['name'], + config: TimeConfig | undefined, + ) { + super(name, 'string', 'SingleStoreTime'); + this.config.fsp = config?.fsp; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreTime> { + return new SingleStoreTime>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreTime< + T extends ColumnBaseConfig<'string', 'SingleStoreTime'>, +> extends SingleStoreColumn { + static readonly [entityKind]: string = 'SingleStoreTime'; + + readonly fsp: number | undefined = this.config.fsp; + + getSQLType(): string { + const precision = this.fsp === undefined ? '' : `(${this.fsp})`; + return `time${precision}`; + } +} + +export type TimeConfig = { + fsp?: 0 | 1 | 2 | 3 | 4 | 5 | 6; +}; + +export function time(name: TName, config?: TimeConfig): SingleStoreTimeBuilderInitial { + return new SingleStoreTimeBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/timestamp.ts b/drizzle-orm/src/singlestore-core/columns/timestamp.ts new file mode 100644 index 000000000..db770b6c7 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/timestamp.ts @@ -0,0 +1,121 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Equal } from '~/utils.ts'; +import { SingleStoreDateBaseColumn, SingleStoreDateColumnBaseBuilder } from './date.common.ts'; + +export type SingleStoreTimestampBuilderInitial = SingleStoreTimestampBuilder<{ + name: TName; + dataType: 'date'; + columnType: 'SingleStoreTimestamp'; + data: Date; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreTimestampBuilder> + extends SingleStoreDateColumnBaseBuilder +{ + static readonly [entityKind]: string = 'SingleStoreTimestampBuilder'; + + constructor(name: T['name'], config: SingleStoreTimestampConfig | undefined) { + super(name, 'date', 'SingleStoreTimestamp'); + this.config.fsp = config?.fsp; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreTimestamp> { + return new SingleStoreTimestamp>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreTimestamp> + extends SingleStoreDateBaseColumn +{ + static readonly [entityKind]: string = 'SingleStoreTimestamp'; + + readonly fsp: number | undefined = this.config.fsp; + + getSQLType(): string { + const precision = this.fsp === undefined ? '' : `(${this.fsp})`; + return `timestamp${precision}`; + } + + override mapFromDriverValue(value: string): Date { + return new Date(value + '+0000'); + } + + override mapToDriverValue(value: Date): string { + return value.toISOString().slice(0, -1).replace('T', ' '); + } +} + +export type SingleStoreTimestampStringBuilderInitial = SingleStoreTimestampStringBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreTimestampString'; + data: string; + driverParam: string | number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreTimestampStringBuilder< + T extends ColumnBuilderBaseConfig<'string', 'SingleStoreTimestampString'>, +> extends SingleStoreDateColumnBaseBuilder { + static readonly [entityKind]: string = 'SingleStoreTimestampStringBuilder'; + + constructor(name: T['name'], config: SingleStoreTimestampConfig | undefined) { + super(name, 'string', 'SingleStoreTimestampString'); + this.config.fsp = config?.fsp; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreTimestampString> { + return new SingleStoreTimestampString>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreTimestampString> + extends SingleStoreDateBaseColumn +{ + static readonly [entityKind]: string = 'SingleStoreTimestampString'; + + readonly fsp: number | undefined = this.config.fsp; + + getSQLType(): string { + const precision = this.fsp === undefined ? '' : `(${this.fsp})`; + return `timestamp${precision}`; + } +} + +export type TimestampFsp = 0 | 1 | 2 | 3 | 4 | 5 | 6; + +export interface SingleStoreTimestampConfig { + mode?: TMode; + fsp?: TimestampFsp; +} + +export function timestamp( + name: TName, + config?: SingleStoreTimestampConfig, +): Equal extends true ? SingleStoreTimestampStringBuilderInitial + : SingleStoreTimestampBuilderInitial; +export function timestamp(name: string, config: SingleStoreTimestampConfig = {}) { + if (config.mode === 'string') { + return new SingleStoreTimestampStringBuilder(name, config); + } + return new SingleStoreTimestampBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/tinyint.ts b/drizzle-orm/src/singlestore-core/columns/tinyint.ts new file mode 100644 index 000000000..d911cae96 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/tinyint.ts @@ -0,0 +1,61 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumnBuilderWithAutoIncrement, SingleStoreColumnWithAutoIncrement } from './common.ts'; +import type { SingleStoreIntConfig } from './int.ts'; + +export type SingleStoreTinyIntBuilderInitial = SingleStoreTinyIntBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreTinyInt'; + data: number; + driverParam: number | string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreTinyIntBuilder> + extends SingleStoreColumnBuilderWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreTinyIntBuilder'; + + constructor(name: T['name'], config?: SingleStoreIntConfig) { + super(name, 'number', 'SingleStoreTinyInt'); + this.config.unsigned = config ? config.unsigned : false; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreTinyInt> { + return new SingleStoreTinyInt>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreTinyInt> + extends SingleStoreColumnWithAutoIncrement +{ + static readonly [entityKind]: string = 'SingleStoreTinyInt'; + + getSQLType(): string { + return `tinyint${this.config.unsigned ? ' unsigned' : ''}`; + } + + override mapFromDriverValue(value: number | string): number { + if (typeof value === 'string') { + return Number(value); + } + return value; + } +} + +export function tinyint( + name: TName, + config?: SingleStoreIntConfig, +): SingleStoreTinyIntBuilderInitial { + return new SingleStoreTinyIntBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/varbinary.ts b/drizzle-orm/src/singlestore-core/columns/varbinary.ts new file mode 100644 index 000000000..545b87476 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/varbinary.ts @@ -0,0 +1,60 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreVarBinaryBuilderInitial = SingleStoreVarBinaryBuilder<{ + name: TName; + dataType: 'string'; + columnType: 'SingleStoreVarBinary'; + data: string; + driverParam: string; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreVarBinaryBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreVarBinaryBuilder'; + + /** @internal */ + constructor(name: T['name'], config: SingleStoreVarbinaryOptions) { + super(name, 'string', 'SingleStoreVarBinary'); + this.config.length = config?.length; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreVarBinary> { + return new SingleStoreVarBinary>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreVarBinary< + T extends ColumnBaseConfig<'string', 'SingleStoreVarBinary'>, +> extends SingleStoreColumn { + static readonly [entityKind]: string = 'SingleStoreVarBinary'; + + length: number | undefined = this.config.length; + + getSQLType(): string { + return this.length === undefined ? `varbinary` : `varbinary(${this.length})`; + } +} + +export interface SingleStoreVarbinaryOptions { + length: number; +} + +export function varbinary( + name: TName, + options: SingleStoreVarbinaryOptions, +): SingleStoreVarBinaryBuilderInitial { + return new SingleStoreVarBinaryBuilder(name, options); +} diff --git a/drizzle-orm/src/singlestore-core/columns/varchar.ts b/drizzle-orm/src/singlestore-core/columns/varchar.ts new file mode 100644 index 000000000..415b3c27b --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/varchar.ts @@ -0,0 +1,68 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Writable } from '~/utils.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreVarCharBuilderInitial = + SingleStoreVarCharBuilder< + { + name: TName; + dataType: 'string'; + columnType: 'SingleStoreVarChar'; + data: TEnum[number]; + driverParam: number | string; + enumValues: TEnum; + generated: undefined; + } + >; + +export class SingleStoreVarCharBuilder> + extends SingleStoreColumnBuilder> +{ + static readonly [entityKind]: string = 'SingleStoreVarCharBuilder'; + + /** @internal */ + constructor(name: T['name'], config: SingleStoreVarCharConfig) { + super(name, 'string', 'SingleStoreVarChar'); + this.config.length = config.length; + this.config.enum = config.enum; + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreVarChar & { enumValues: T['enumValues'] }> { + return new SingleStoreVarChar & { enumValues: T['enumValues'] }>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreVarChar> + extends SingleStoreColumn> +{ + static readonly [entityKind]: string = 'SingleStoreVarChar'; + + readonly length: number | undefined = this.config.length; + + override readonly enumValues = this.config.enum; + + getSQLType(): string { + return this.length === undefined ? `varchar` : `varchar(${this.length})`; + } +} + +export interface SingleStoreVarCharConfig { + length: number; + enum?: TEnum; +} + +export function varchar>( + name: TName, + config: SingleStoreVarCharConfig>, +): SingleStoreVarCharBuilderInitial> { + return new SingleStoreVarCharBuilder(name, config); +} diff --git a/drizzle-orm/src/singlestore-core/columns/year.ts b/drizzle-orm/src/singlestore-core/columns/year.ts new file mode 100644 index 000000000..c774b44d8 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/columns/year.ts @@ -0,0 +1,49 @@ +import type { ColumnBuilderBaseConfig, ColumnBuilderRuntimeConfig, MakeColumnConfig } from '~/column-builder.ts'; +import type { ColumnBaseConfig } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreTable } from '~/singlestore-core/table.ts'; +import { SingleStoreColumn, SingleStoreColumnBuilder } from './common.ts'; + +export type SingleStoreYearBuilderInitial = SingleStoreYearBuilder<{ + name: TName; + dataType: 'number'; + columnType: 'SingleStoreYear'; + data: number; + driverParam: number; + enumValues: undefined; + generated: undefined; +}>; + +export class SingleStoreYearBuilder> + extends SingleStoreColumnBuilder +{ + static readonly [entityKind]: string = 'SingleStoreYearBuilder'; + + constructor(name: T['name']) { + super(name, 'number', 'SingleStoreYear'); + } + + /** @internal */ + override build( + table: AnySingleStoreTable<{ name: TTableName }>, + ): SingleStoreYear> { + return new SingleStoreYear>( + table, + this.config as ColumnBuilderRuntimeConfig, + ); + } +} + +export class SingleStoreYear< + T extends ColumnBaseConfig<'number', 'SingleStoreYear'>, +> extends SingleStoreColumn { + static readonly [entityKind]: string = 'SingleStoreYear'; + + getSQLType(): string { + return `year`; + } +} + +export function year(name: TName): SingleStoreYearBuilderInitial { + return new SingleStoreYearBuilder(name); +} diff --git a/drizzle-orm/src/singlestore-core/db.ts b/drizzle-orm/src/singlestore-core/db.ts new file mode 100644 index 000000000..7c2c16ec4 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/db.ts @@ -0,0 +1,569 @@ +import type { ResultSetHeader } from 'mysql2/promise'; +import { entityKind } from '~/entity.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; +import { WithSubquery } from '~/subquery.ts'; +import type { DrizzleTypeError } from '~/utils.ts'; +import type { SingleStoreDialect } from './dialect.ts'; +import { SingleStoreAttachBase } from './query-builders/attach.ts'; +import { SingleStoreBranchBase } from './query-builders/branch.ts'; +import { SingleStoreCreateMilestoneBase } from './query-builders/createMilestone.ts'; +import { SingleStoreDetachBase } from './query-builders/detach.ts'; +import { SingleStoreDropMilestoneBase } from './query-builders/dropMilestone.ts'; +import { + QueryBuilder, + SingleStoreDeleteBase, + SingleStoreInsertBuilder, + SingleStoreSelectBuilder, + SingleStoreUpdateBuilder, +} from './query-builders/index.ts'; +import { SingleStoreOptimizeTableBase } from './query-builders/optimizeTable.ts'; +import type { OptimizeTableArgument } from './query-builders/optimizeTable.types.ts'; +import { RelationalQueryBuilder } from './query-builders/query.ts'; +import type { SelectedFields } from './query-builders/select.types.ts'; +import type { + Mode, + PreparedQueryHKTBase, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, + SingleStoreTransaction, + SingleStoreTransactionConfig, +} from './session.ts'; +import type { WithSubqueryWithSelection } from './subquery.ts'; +import type { SingleStoreTable } from './table.ts'; + +export class SingleStoreDatabase< + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TFullSchema extends Record = {}, + TSchema extends TablesRelationalConfig = ExtractTablesWithRelations, +> { + static readonly [entityKind]: string = 'SingleStoreDatabase'; + + declare readonly _: { + readonly schema: TSchema | undefined; + readonly fullSchema: TFullSchema; + readonly tableNamesMap: Record; + }; + + query: TFullSchema extends Record + ? DrizzleTypeError<'Seems like the schema generic is missing - did you forget to add it to your DB type?'> + : { + [K in keyof TSchema]: RelationalQueryBuilder; + }; + + constructor( + /** @internal */ + readonly dialect: SingleStoreDialect, + /** @internal */ + readonly session: SingleStoreSession, + schema: RelationalSchemaConfig | undefined, + protected readonly mode: Mode, + ) { + this._ = schema + ? { + schema: schema.schema, + fullSchema: schema.fullSchema as TFullSchema, + tableNamesMap: schema.tableNamesMap, + } + : { + schema: undefined, + fullSchema: {} as TFullSchema, + tableNamesMap: {}, + }; + this.query = {} as typeof this['query']; + if (this._.schema) { + for (const [tableName, columns] of Object.entries(this._.schema)) { + (this.query as SingleStoreDatabase>['query'])[tableName] = + new RelationalQueryBuilder( + schema!.fullSchema, + this._.schema, + this._.tableNamesMap, + schema!.fullSchema[tableName] as SingleStoreTable, + columns, + dialect, + session, + this.mode, + ); + } + } + } + + /** + * Creates a subquery that defines a temporary named result set as a CTE. + * + * It is useful for breaking down complex queries into simpler parts and for reusing the result set in subsequent parts of the query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param alias The alias for the subquery. + * + * Failure to provide an alias will result in a DrizzleTypeError, preventing the subquery from being referenced in other queries. + * + * @example + * + * ```ts + * // Create a subquery with alias 'sq' and use it in the select query + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * const result = await db.with(sq).select().from(sq); + * ``` + * + * To select arbitrary SQL values as fields in a CTE and reference them in other CTEs or in the main query, you need to add aliases to them: + * + * ```ts + * // Select an arbitrary SQL value as a field in a CTE and reference it in the main query + * const sq = db.$with('sq').as(db.select({ + * name: sql`upper(${users.name})`.as('name'), + * }) + * .from(users)); + * + * const result = await db.with(sq).select({ name: sq.name }).from(sq); + * ``` + */ + $with(alias: TAlias) { + return { + as( + qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), + ): WithSubqueryWithSelection { + if (typeof qb === 'function') { + qb = qb(new QueryBuilder()); + } + + return new Proxy( + new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true), + new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }), + ) as WithSubqueryWithSelection; + }, + }; + } + + /** + * Incorporates a previously defined CTE (using `$with`) into the main query. + * + * This method allows the main query to reference a temporary named result set. + * + * See docs: {@link https://orm.drizzle.team/docs/select#with-clause} + * + * @param queries The CTEs to incorporate into the main query. + * + * @example + * + * ```ts + * // Define a subquery 'sq' as a CTE using $with + * const sq = db.$with('sq').as(db.select().from(users).where(eq(users.id, 42))); + * + * // Incorporate the CTE 'sq' into the main query and select from it + * const result = await db.with(sq).select().from(sq); + * ``` + */ + with(...queries: WithSubquery[]) { + const self = this; + + /** + * Creates a select query. + * + * Calling this method with no arguments will select all columns from the table. Pass a selection object to specify the columns you want to select. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select} + * + * @param fields The selection object. + * + * @example + * + * ```ts + * // Select all columns and all rows from the 'cars' table + * const allCars: Car[] = await db.select().from(cars); + * + * // Select specific columns and all rows from the 'cars' table + * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ + * id: cars.id, + * brand: cars.brand + * }) + * .from(cars); + * ``` + * + * Like in SQL, you can use arbitrary expressions as selection fields, not just table columns: + * + * ```ts + * // Select specific columns along with expression and all rows from the 'cars' table + * const carsIdsAndLowerNames: { id: number; lowerBrand: string }[] = await db.select({ + * id: cars.id, + * lowerBrand: sql`lower(${cars.brand})`, + * }) + * .from(cars); + * ``` + */ + function select(): SingleStoreSelectBuilder; + function select( + fields: TSelection, + ): SingleStoreSelectBuilder; + function select(fields?: SelectedFields): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: self.session, + dialect: self.dialect, + withList: queries, + }); + } + + /** + * Adds `distinct` expression to the select query. + * + * Calling this method will return only unique values. When multiple columns are selected, it returns rows with unique combinations of values in these columns. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select#distinct} + * + * @param fields The selection object. + * + * @example + * ```ts + * // Select all unique rows from the 'cars' table + * await db.selectDistinct() + * .from(cars) + * .orderBy(cars.id, cars.brand, cars.color); + * + * // Select all unique brands from the 'cars' table + * await db.selectDistinct({ brand: cars.brand }) + * .from(cars) + * .orderBy(cars.brand); + * ``` + */ + function selectDistinct(): SingleStoreSelectBuilder; + function selectDistinct( + fields: TSelection, + ): SingleStoreSelectBuilder; + function selectDistinct( + fields?: SelectedFields, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: self.session, + dialect: self.dialect, + withList: queries, + distinct: true, + }); + } + + /** + * Creates an update query. + * + * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated. + * + * Use `.set()` method to specify which values to update. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param table The table to update. + * + * @example + * + * ```ts + * // Update all rows in the 'cars' table + * await db.update(cars).set({ color: 'red' }); + * + * // Update rows with filters and conditions + * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW')); + * ``` + */ + function update( + table: TTable, + ): SingleStoreUpdateBuilder { + return new SingleStoreUpdateBuilder(table, self.session, self.dialect, queries); + } + + /** + * Creates a delete query. + * + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param table The table to delete from. + * + * @example + * + * ```ts + * // Delete all rows in the 'cars' table + * await db.delete(cars); + * + * // Delete rows with filters and conditions + * await db.delete(cars).where(eq(cars.color, 'green')); + * ``` + */ + function delete_( + table: TTable, + ): SingleStoreDeleteBase { + return new SingleStoreDeleteBase(table, self.session, self.dialect, queries); + } + + return { select, selectDistinct, update, delete: delete_ }; + } + + /** + * Creates a select query. + * + * Calling this method with no arguments will select all columns from the table. Pass a selection object to specify the columns you want to select. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select} + * + * @param fields The selection object. + * + * @example + * + * ```ts + * // Select all columns and all rows from the 'cars' table + * const allCars: Car[] = await db.select().from(cars); + * + * // Select specific columns and all rows from the 'cars' table + * const carsIdsAndBrands: { id: number; brand: string }[] = await db.select({ + * id: cars.id, + * brand: cars.brand + * }) + * .from(cars); + * ``` + * + * Like in SQL, you can use arbitrary expressions as selection fields, not just table columns: + * + * ```ts + * // Select specific columns along with expression and all rows from the 'cars' table + * const carsIdsAndLowerNames: { id: number; lowerBrand: string }[] = await db.select({ + * id: cars.id, + * lowerBrand: sql`lower(${cars.brand})`, + * }) + * .from(cars); + * ``` + */ + select(): SingleStoreSelectBuilder; + select( + fields: TSelection, + ): SingleStoreSelectBuilder; + select(fields?: SelectedFields): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ fields: fields ?? undefined, session: this.session, dialect: this.dialect }); + } + + /** + * Adds `distinct` expression to the select query. + * + * Calling this method will return only unique values. When multiple columns are selected, it returns rows with unique combinations of values in these columns. + * + * Use `.from()` method to specify which table to select from. + * + * See docs: {@link https://orm.drizzle.team/docs/select#distinct} + * + * @param fields The selection object. + * + * @example + * ```ts + * // Select all unique rows from the 'cars' table + * await db.selectDistinct() + * .from(cars) + * .orderBy(cars.id, cars.brand, cars.color); + * + * // Select all unique brands from the 'cars' table + * await db.selectDistinct({ brand: cars.brand }) + * .from(cars) + * .orderBy(cars.brand); + * ``` + */ + selectDistinct(): SingleStoreSelectBuilder; + selectDistinct( + fields: TSelection, + ): SingleStoreSelectBuilder; + selectDistinct(fields?: SelectedFields): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: this.session, + dialect: this.dialect, + distinct: true, + }); + } + + /** + * Creates an update query. + * + * Calling this method without `.where()` clause will update all rows in a table. The `.where()` clause specifies which rows should be updated. + * + * Use `.set()` method to specify which values to update. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param table The table to update. + * + * @example + * + * ```ts + * // Update all rows in the 'cars' table + * await db.update(cars).set({ color: 'red' }); + * + * // Update rows with filters and conditions + * await db.update(cars).set({ color: 'red' }).where(eq(cars.brand, 'BMW')); + * ``` + */ + update( + table: TTable, + ): SingleStoreUpdateBuilder { + return new SingleStoreUpdateBuilder(table, this.session, this.dialect); + } + + /** + * Creates an insert query. + * + * Calling this method will create new rows in a table. Use `.values()` method to specify which values to insert. + * + * See docs: {@link https://orm.drizzle.team/docs/insert} + * + * @param table The table to insert into. + * + * @example + * + * ```ts + * // Insert one row + * await db.insert(cars).values({ brand: 'BMW' }); + * + * // Insert multiple rows + * await db.insert(cars).values([{ brand: 'BMW' }, { brand: 'Porsche' }]); + * ``` + */ + insert( + table: TTable, + ): SingleStoreInsertBuilder { + return new SingleStoreInsertBuilder(table, this.session, this.dialect); + } + + /** + * Creates a delete query. + * + * Calling this method without `.where()` clause will delete all rows in a table. The `.where()` clause specifies which rows should be deleted. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param table The table to delete from. + * + * @example + * + * ```ts + * // Delete all rows in the 'cars' table + * await db.delete(cars); + * + * // Delete rows with filters and conditions + * await db.delete(cars).where(eq(cars.color, 'green')); + * ``` + */ + delete( + table: TTable, + ): SingleStoreDeleteBase { + return new SingleStoreDeleteBase(table, this.session, this.dialect); + } + + execute( + query: SQLWrapper, + ): Promise> { + return this.session.execute(query.getSQL()); + } + + transaction( + transaction: ( + tx: SingleStoreTransaction, + config?: SingleStoreTransactionConfig, + ) => Promise, + config?: SingleStoreTransactionConfig, + ): Promise { + return this.session.transaction(transaction, config); + } + + detach( + database: TDatabase, + ): SingleStoreDetachBase { + return new SingleStoreDetachBase(database, this.session, this.dialect); + } + + attach( + database: TDatabase, + ): SingleStoreAttachBase { + return new SingleStoreAttachBase(database, this.session, this.dialect); + } + + branch( + database: TDatabase, + branchName: string, + ): SingleStoreBranchBase { + return new SingleStoreBranchBase(database, branchName, this.session, this.dialect); + } + + createMilestone( + milestone: TMilestone, + ): SingleStoreCreateMilestoneBase { + return new SingleStoreCreateMilestoneBase(milestone, this.session, this.dialect); + } + + dropMilestone( + milestone: TMilestone, + ): SingleStoreDropMilestoneBase { + return new SingleStoreDropMilestoneBase(milestone, this.session, this.dialect); + } + + optimizeTable< + TTable extends SingleStoreTable, + TArg extends OptimizeTableArgument, + >( + table: TTable, + arg: TArg | undefined = undefined, + ): SingleStoreOptimizeTableBase { + return new SingleStoreOptimizeTableBase(table, arg, this.session, this.dialect); + } +} + +export type SingleStoreWithReplicas = Q & { $primary: Q }; + +export const withReplicas = < + HKT extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, + Q extends SingleStoreDatabase< + HKT, + TPreparedQueryHKT, + TFullSchema, + TSchema extends Record ? ExtractTablesWithRelations : TSchema + >, +>( + primary: Q, + replicas: [Q, ...Q[]], + getReplica: (replicas: Q[]) => Q = () => replicas[Math.floor(Math.random() * replicas.length)]!, +): SingleStoreWithReplicas => { + const select: Q['select'] = (...args: []) => getReplica(replicas).select(...args); + const selectDistinct: Q['selectDistinct'] = (...args: []) => getReplica(replicas).selectDistinct(...args); + const $with: Q['with'] = (...args: []) => getReplica(replicas).with(...args); + + const update: Q['update'] = (...args: [any]) => primary.update(...args); + const insert: Q['insert'] = (...args: [any]) => primary.insert(...args); + const $delete: Q['delete'] = (...args: [any]) => primary.delete(...args); + const execute: Q['execute'] = (...args: [any]) => primary.execute(...args); + const transaction: Q['transaction'] = (...args: [any, any]) => primary.transaction(...args); + + return { + ...primary, + update, + insert, + delete: $delete, + execute, + transaction, + $primary: primary, + select, + selectDistinct, + with: $with, + get query() { + return getReplica(replicas).query; + }, + }; +}; diff --git a/drizzle-orm/src/singlestore-core/dialect.ts b/drizzle-orm/src/singlestore-core/dialect.ts new file mode 100644 index 000000000..7ae21b418 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/dialect.ts @@ -0,0 +1,1147 @@ +import { aliasedTable, aliasedTableColumn, mapColumnsInAliasedSQLToAlias, mapColumnsInSQLToAlias } from '~/alias.ts'; +import { Column } from '~/column.ts'; +import { entityKind, is } from '~/entity.ts'; +import { DrizzleError } from '~/errors.ts'; +import { and, eq } from '~/expressions.ts'; +import type { MigrationConfig, MigrationMeta } from '~/migrator.ts'; +import { + type BuildRelationalQueryResult, + type DBQueryConfig, + getOperators, + getOrderByOperators, + Many, + normalizeRelation, + One, + type Relation, + type TableRelationalConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import { Param, SQL, sql, View } from '~/sql/sql.ts'; +import type { Name, QueryWithTypings, SQLChunk } from '~/sql/sql.ts'; +import { Subquery } from '~/subquery.ts'; +import { getTableName, getTableUniqueName, Table } from '~/table.ts'; +import { orderSelectedFields, type UpdateSet } from '~/utils.ts'; +import { ViewBaseConfig } from '~/view-common.ts'; +import { SingleStoreColumn } from './columns/common.ts'; +import type { SingleStoreAttachConfig } from './query-builders/attach.ts'; +import type { SingleStoreBranchConfig } from './query-builders/branch.ts'; +import type { SingleStoreCreateMilestoneConfig } from './query-builders/createMilestone.ts'; +import type { SingleStoreDeleteConfig } from './query-builders/delete.ts'; +import type { SingleStoreDetachConfig } from './query-builders/detach.ts'; +import type { SingleStoreDropMilestoneConfig } from './query-builders/dropMilestone.ts'; +import type { SingleStoreInsertConfig } from './query-builders/insert.ts'; +import type { SingleStoreOptimizeTableConfig } from './query-builders/optimizeTable.ts'; +import type { + SelectedFieldsOrdered, + SingleStoreSelectConfig, + SingleStoreSelectJoinConfig, +} from './query-builders/select.types.ts'; +import type { SingleStoreUpdateConfig } from './query-builders/update.ts'; +import type { SingleStoreSession } from './session.ts'; +import { SingleStoreTable } from './table.ts'; +import { SingleStoreViewBase } from './view-base.ts'; + +export class SingleStoreDialect { + static readonly [entityKind]: string = 'SingleStoreDialect'; + + async migrate( + migrations: MigrationMeta[], + session: SingleStoreSession, + config: Omit, + ): Promise { + const migrationsTable = config.migrationsTable ?? '__drizzle_migrations'; + const migrationTableCreate = sql` + create table if not exists ${sql.identifier(migrationsTable)} ( + id serial primary key, + hash text not null, + created_at bigint + ) + `; + await session.execute(migrationTableCreate); + + const dbMigrations = await session.all<{ id: number; hash: string; created_at: string }>( + sql`select id, hash, created_at from ${sql.identifier(migrationsTable)} order by created_at desc limit 1`, + ); + + const lastDbMigration = dbMigrations[0]; + + await session.transaction(async (tx) => { + for (const migration of migrations) { + if ( + !lastDbMigration + || Number(lastDbMigration.created_at) < migration.folderMillis + ) { + for (const stmt of migration.sql) { + await tx.execute(sql.raw(stmt)); + } + await tx.execute( + sql`insert into ${ + sql.identifier(migrationsTable) + } (\`hash\`, \`created_at\`) values(${migration.hash}, ${migration.folderMillis})`, + ); + } + } + }); + } + + escapeName(name: string): string { + return `\`${name}\``; + } + + escapeParam(_num: number): string { + return `?`; + } + + escapeString(str: string): string { + return `'${str.replace(/'/g, "''")}'`; + } + + private buildWithCTE(queries: Subquery[] | undefined): SQL | undefined { + if (!queries?.length) return undefined; + + const withSqlChunks = [sql`with `]; + for (const [i, w] of queries.entries()) { + withSqlChunks.push(sql`${sql.identifier(w._.alias)} as (${w._.sql})`); + if (i < queries.length - 1) { + withSqlChunks.push(sql`, `); + } + } + withSqlChunks.push(sql` `); + return sql.join(withSqlChunks); + } + + buildDeleteQuery({ table, where, returning, withList }: SingleStoreDeleteConfig): SQL { + const withSql = this.buildWithCTE(withList); + + const returningSql = returning + ? sql` returning ${this.buildSelection(returning, { isSingleTable: true })}` + : undefined; + + const whereSql = where ? sql` where ${where}` : undefined; + + return sql`${withSql}delete from ${table}${whereSql}${returningSql}`; + } + + buildDetachQuery({ database, milestone, workspace }: SingleStoreDetachConfig): SQL { + const milestoneSql = milestone ? sql` at milestone ${milestone}` : undefined; + + const workspaceSql = workspace ? sql` from workspace ${workspace}` : undefined; + + return sql`detach database ${database}${milestoneSql}${workspaceSql}`; + } + + buildAttachQuery( + { database, milestone, time, databaseAlias, readOnly, ...rest }: SingleStoreAttachConfig | SingleStoreBranchConfig, + ): SQL { + const asSql = databaseAlias ? sql` as ${sql.identifier(databaseAlias)}` : undefined; + const milestoneSql = milestone ? sql` at milestone ${milestone}` : undefined; + const timeSql = time ? sql` at time ${time}` : undefined; + const readOnlySql = readOnly ? sql` read only` : undefined; + const fromWorkspaceGroupSql = 'fromWorkspaceGroup' in rest + ? sql` from workspace group ${rest.fromWorkspaceGroup}` + : undefined; + + return sql`attach database ${ + sql.raw(database) + }${fromWorkspaceGroupSql}${readOnlySql}${asSql}${milestoneSql}${timeSql}`; + } + + buildCreateMilestoneQuery({ database, milestone }: SingleStoreCreateMilestoneConfig): SQL { + const forSql = database ? sql` for ${sql.identifier(database)}` : undefined; + + return sql`create milestone ${milestone}${forSql}`; + } + + buildDropMilestoneQuery({ database, milestone }: SingleStoreDropMilestoneConfig): SQL { + const forSql = database ? sql` for ${sql.identifier(database)}` : undefined; + + return sql`drop milestone ${milestone}${forSql}`; + } + + buildOptimizeTable({ table, arg, selection }: SingleStoreOptimizeTableConfig): SQL { + const argSql = arg ? sql` ${sql.raw(arg)}` : undefined; + + let warmBlobCacheForColumnSql = undefined; + if (selection) { + const selectionField = selection.length > 0 + ? selection.map((column) => { + return { path: [], field: column }; + }) + : [{ path: [], field: sql.raw('*') }]; + warmBlobCacheForColumnSql = sql` warm blob cache for column ${ + this.buildSelection(selectionField, { isSingleTable: true }) + }`; + } + + return sql`optimize table ${table}${argSql}${warmBlobCacheForColumnSql}`; + } + + buildUpdateSet(table: SingleStoreTable, set: UpdateSet): SQL { + const tableColumns = table[Table.Symbol.Columns]; + + const columnNames = Object.keys(tableColumns).filter((colName) => + set[colName] !== undefined || tableColumns[colName]?.onUpdateFn !== undefined + ); + + const setSize = columnNames.length; + return sql.join(columnNames.flatMap((colName, i) => { + const col = tableColumns[colName]!; + + const value = set[colName] ?? sql.param(col.onUpdateFn!(), col); + const res = sql`${sql.identifier(col.name)} = ${value}`; + + if (i < setSize - 1) { + return [res, sql.raw(', ')]; + } + return [res]; + })); + } + + buildUpdateQuery({ table, set, where, returning, withList }: SingleStoreUpdateConfig): SQL { + const withSql = this.buildWithCTE(withList); + + const setSql = this.buildUpdateSet(table, set); + + const returningSql = returning + ? sql` returning ${this.buildSelection(returning, { isSingleTable: true })}` + : undefined; + + const whereSql = where ? sql` where ${where}` : undefined; + + return sql`${withSql}update ${table} set ${setSql}${whereSql}${returningSql}`; + } + + /** + * Builds selection SQL with provided fields/expressions + * + * Examples: + * + * `select from` + * + * `insert ... returning ` + * + * If `isSingleTable` is true, then columns won't be prefixed with table name + */ + private buildSelection( + fields: SelectedFieldsOrdered, + { isSingleTable = false }: { isSingleTable?: boolean } = {}, + ): SQL { + const columnsLen = fields.length; + + const chunks = fields + .flatMap(({ field }, i) => { + const chunk: SQLChunk[] = []; + + if (is(field, SQL.Aliased) && field.isSelectionField) { + chunk.push(sql.identifier(field.fieldAlias)); + } else if (is(field, SQL.Aliased) || is(field, SQL)) { + const query = is(field, SQL.Aliased) ? field.sql : field; + + if (isSingleTable) { + chunk.push( + new SQL( + query.queryChunks.map((c) => { + if (is(c, SingleStoreColumn)) { + return sql.identifier(c.name); + } + return c; + }), + ), + ); + } else { + chunk.push(query); + } + + if (is(field, SQL.Aliased)) { + chunk.push(sql` as ${sql.identifier(field.fieldAlias)}`); + } + } else if (is(field, Column)) { + if (isSingleTable) { + chunk.push(sql.identifier(field.name)); + } else { + chunk.push(field); + } + } + + if (i < columnsLen - 1) { + chunk.push(sql`, `); + } + + return chunk; + }); + + return sql.join(chunks); + } + + buildSelectQuery( + { + withList, + fields, + fieldsFlat, + where, + having, + table, + joins, + orderBy, + groupBy, + limit, + offset, + lockingClause, + distinct, + setOperators, + }: SingleStoreSelectConfig, + ): SQL { + const fieldsList = fieldsFlat ?? orderSelectedFields(fields); + for (const f of fieldsList) { + if ( + is(f.field, Column) + && getTableName(f.field.table) + !== (is(table, Subquery) + ? table._.alias + : is(table, SingleStoreViewBase) + ? table[ViewBaseConfig].name + : is(table, SQL) + ? undefined + : getTableName(table)) + && !((table) => + joins?.some(({ alias }) => + alias === (table[Table.Symbol.IsAlias] ? getTableName(table) : table[Table.Symbol.BaseName]) + ))(f.field.table) + ) { + const tableName = getTableName(f.field.table); + throw new Error( + `Your "${ + f.path.join('->') + }" field references a column "${tableName}"."${f.field.name}", but the table "${tableName}" is not part of the query! Did you forget to join it?`, + ); + } + } + + const isSingleTable = !joins || joins.length === 0; + + const withSql = this.buildWithCTE(withList); + + const distinctSql = distinct ? sql` distinct` : undefined; + + const selection = this.buildSelection(fieldsList, { isSingleTable }); + + const tableSql = (() => { + if (is(table, Table) && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) { + return sql`${sql.identifier(table[Table.Symbol.OriginalName])} ${sql.identifier(table[Table.Symbol.Name])}`; + } + + return table; + })(); + + const joinsArray: SQL[] = []; + + if (joins) { + for (const [index, joinMeta] of joins.entries()) { + if (index === 0) { + joinsArray.push(sql` `); + } + const table = joinMeta.table; + const lateralSql = joinMeta.lateral ? sql` lateral` : undefined; + + if (is(table, SingleStoreTable)) { + const tableName = table[SingleStoreTable.Symbol.Name]; + const tableSchema = table[SingleStoreTable.Symbol.Schema]; + const origTableName = table[SingleStoreTable.Symbol.OriginalName]; + const alias = tableName === origTableName ? undefined : joinMeta.alias; + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ + tableSchema ? sql`${sql.identifier(tableSchema)}.` : undefined + }${sql.identifier(origTableName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, + ); + } else if (is(table, View)) { + const viewName = table[ViewBaseConfig].name; + const viewSchema = table[ViewBaseConfig].schema; + const origViewName = table[ViewBaseConfig].originalName; + const alias = viewName === origViewName ? undefined : joinMeta.alias; + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${ + viewSchema ? sql`${sql.identifier(viewSchema)}.` : undefined + }${sql.identifier(origViewName)}${alias && sql` ${sql.identifier(alias)}`} on ${joinMeta.on}`, + ); + } else { + joinsArray.push( + sql`${sql.raw(joinMeta.joinType)} join${lateralSql} ${table} on ${joinMeta.on}`, + ); + } + if (index < joins.length - 1) { + joinsArray.push(sql` `); + } + } + } + + const joinsSql = sql.join(joinsArray); + + const whereSql = where ? sql` where ${where}` : undefined; + + const havingSql = having ? sql` having ${having}` : undefined; + + let orderBySql; + if (orderBy && orderBy.length > 0) { + orderBySql = sql` order by ${sql.join(orderBy, sql`, `)}`; + } + + let groupBySql; + if (groupBy && groupBy.length > 0) { + groupBySql = sql` group by ${sql.join(groupBy, sql`, `)}`; + } + + const limitSql = typeof limit === 'object' || (typeof limit === 'number' && limit >= 0) + ? sql` limit ${limit}` + : undefined; + + const offsetSql = offset ? sql` offset ${offset}` : undefined; + + let lockingClausesSql; + if (lockingClause) { + const { config, strength } = lockingClause; + lockingClausesSql = sql` for ${sql.raw(strength)}`; + if (config.noWait) { + lockingClausesSql.append(sql` no wait`); + } else if (config.skipLocked) { + lockingClausesSql.append(sql` skip locked`); + } + } + + const finalQuery = + sql`${withSql}select${distinctSql} ${selection} from ${tableSql}${joinsSql}${whereSql}${groupBySql}${havingSql}${orderBySql}${limitSql}${offsetSql}${lockingClausesSql}`; + + if (setOperators.length > 0) { + return this.buildSetOperations(finalQuery, setOperators); + } + + return finalQuery; + } + + buildSetOperations(leftSelect: SQL, setOperators: SingleStoreSelectConfig['setOperators']): SQL { + const [setOperator, ...rest] = setOperators; + + if (!setOperator) { + throw new Error('Cannot pass undefined values to any set operator'); + } + + if (rest.length === 0) { + return this.buildSetOperationQuery({ leftSelect, setOperator }); + } + + // Some recursive magic here + return this.buildSetOperations( + this.buildSetOperationQuery({ leftSelect, setOperator }), + rest, + ); + } + + buildSetOperationQuery({ + leftSelect, + setOperator: { type, isAll, rightSelect, limit, orderBy, offset }, + }: { leftSelect: SQL; setOperator: SingleStoreSelectConfig['setOperators'][number] }): SQL { + const leftChunk = sql`(${leftSelect.getSQL()}) `; + const rightChunk = sql`(${rightSelect.getSQL()})`; + + let orderBySql; + if (orderBy && orderBy.length > 0) { + const orderByValues: (SQL | Name)[] = []; + + // The next bit is necessary because the sql operator replaces ${table.column} with `table`.`column` + // which is invalid SingleStore syntax, Table from one of the SELECTs cannot be used in global ORDER clause + for (const orderByUnit of orderBy) { + if (is(orderByUnit, SingleStoreColumn)) { + orderByValues.push(sql.identifier(orderByUnit.name)); + } else if (is(orderByUnit, SQL)) { + for (let i = 0; i < orderByUnit.queryChunks.length; i++) { + const chunk = orderByUnit.queryChunks[i]; + + if (is(chunk, SingleStoreColumn)) { + orderByUnit.queryChunks[i] = sql.identifier(chunk.name); + } + } + + orderByValues.push(sql`${orderByUnit}`); + } else { + orderByValues.push(sql`${orderByUnit}`); + } + } + + orderBySql = sql` order by ${sql.join(orderByValues, sql`, `)} `; + } + + const limitSql = typeof limit === 'object' || (typeof limit === 'number' && limit >= 0) + ? sql` limit ${limit}` + : undefined; + + const operatorChunk = sql.raw(`${type} ${isAll ? 'all ' : ''}`); + + const offsetSql = offset ? sql` offset ${offset}` : undefined; + + return sql`${leftChunk}${operatorChunk}${rightChunk}${orderBySql}${limitSql}${offsetSql}`; + } + + buildInsertQuery( + { table, values, ignore, onConflict }: SingleStoreInsertConfig, + ): { sql: SQL; generatedIds: Record[] } { + // const isSingleValue = values.length === 1; + const valuesSqlList: ((SQLChunk | SQL)[] | SQL)[] = []; + const columns: Record = table[Table.Symbol.Columns]; + const colEntries: [string, SingleStoreColumn][] = Object.entries(columns).filter(([_, col]) => + !col.shouldDisableInsert() + ); + + const insertOrder = colEntries.map(([, column]) => sql.identifier(column.name)); + const generatedIdsResponse: Record[] = []; + + for (const [valueIndex, value] of values.entries()) { + const generatedIds: Record = {}; + + const valueList: (SQLChunk | SQL)[] = []; + for (const [fieldName, col] of colEntries) { + const colValue = value[fieldName]; + if (colValue === undefined || (is(colValue, Param) && colValue.value === undefined)) { + // eslint-disable-next-line unicorn/no-negated-condition + if (col.defaultFn !== undefined) { + const defaultFnResult = col.defaultFn(); + generatedIds[fieldName] = defaultFnResult; + const defaultValue = is(defaultFnResult, SQL) ? defaultFnResult : sql.param(defaultFnResult, col); + valueList.push(defaultValue); + // eslint-disable-next-line unicorn/no-negated-condition + } else if (!col.default && col.onUpdateFn !== undefined) { + const onUpdateFnResult = col.onUpdateFn(); + const newValue = is(onUpdateFnResult, SQL) ? onUpdateFnResult : sql.param(onUpdateFnResult, col); + valueList.push(newValue); + } else { + valueList.push(sql`default`); + } + } else { + if (col.defaultFn && is(colValue, Param)) { + generatedIds[fieldName] = colValue.value; + } + valueList.push(colValue); + } + } + + generatedIdsResponse.push(generatedIds); + valuesSqlList.push(valueList); + if (valueIndex < values.length - 1) { + valuesSqlList.push(sql`, `); + } + } + + const valuesSql = sql.join(valuesSqlList); + + const ignoreSql = ignore ? sql` ignore` : undefined; + + const onConflictSql = onConflict ? sql` on duplicate key ${onConflict}` : undefined; + + return { + sql: sql`insert${ignoreSql} into ${table} ${insertOrder} values ${valuesSql}${onConflictSql}`, + generatedIds: generatedIdsResponse, + }; + } + + sqlToQuery(sql: SQL, invokeSource?: 'indexes' | undefined): QueryWithTypings { + return sql.toQuery({ + escapeName: this.escapeName, + escapeParam: this.escapeParam, + escapeString: this.escapeString, + invokeSource, + }); + } + + buildRelationalQuery({ + fullSchema, + schema, + tableNamesMap, + table, + tableConfig, + queryConfig: config, + tableAlias, + nestedQueryRelation, + joinOn, + }: { + fullSchema: Record; + schema: TablesRelationalConfig; + tableNamesMap: Record; + table: SingleStoreTable; + tableConfig: TableRelationalConfig; + queryConfig: true | DBQueryConfig<'many', true>; + tableAlias: string; + nestedQueryRelation?: Relation; + joinOn?: SQL; + }): BuildRelationalQueryResult { + let selection: BuildRelationalQueryResult['selection'] = []; + let limit, offset, orderBy: SingleStoreSelectConfig['orderBy'], where; + const joins: SingleStoreSelectJoinConfig[] = []; + + if (config === true) { + const selectionEntries = Object.entries(tableConfig.columns); + selection = selectionEntries.map(( + [key, value], + ) => ({ + dbKey: value.name, + tsKey: key, + field: aliasedTableColumn(value as SingleStoreColumn, tableAlias), + relationTableTsKey: undefined, + isJson: false, + selection: [], + })); + } else { + const aliasedColumns = Object.fromEntries( + Object.entries(tableConfig.columns).map(([key, value]) => [key, aliasedTableColumn(value, tableAlias)]), + ); + + if (config.where) { + const whereSql = typeof config.where === 'function' + ? config.where(aliasedColumns, getOperators()) + : config.where; + where = whereSql && mapColumnsInSQLToAlias(whereSql, tableAlias); + } + + const fieldsSelection: { tsKey: string; value: SingleStoreColumn | SQL.Aliased }[] = []; + let selectedColumns: string[] = []; + + // Figure out which columns to select + if (config.columns) { + let isIncludeMode = false; + + for (const [field, value] of Object.entries(config.columns)) { + if (value === undefined) { + continue; + } + + if (field in tableConfig.columns) { + if (!isIncludeMode && value === true) { + isIncludeMode = true; + } + selectedColumns.push(field); + } + } + + if (selectedColumns.length > 0) { + selectedColumns = isIncludeMode + ? selectedColumns.filter((c) => config.columns?.[c] === true) + : Object.keys(tableConfig.columns).filter((key) => !selectedColumns.includes(key)); + } + } else { + // Select all columns if selection is not specified + selectedColumns = Object.keys(tableConfig.columns); + } + + for (const field of selectedColumns) { + const column = tableConfig.columns[field]! as SingleStoreColumn; + fieldsSelection.push({ tsKey: field, value: column }); + } + + let selectedRelations: { + tsKey: string; + queryConfig: true | DBQueryConfig<'many', false>; + relation: Relation; + }[] = []; + + // Figure out which relations to select + if (config.with) { + selectedRelations = Object.entries(config.with) + .filter((entry): entry is [typeof entry[0], NonNullable] => !!entry[1]) + .map(([tsKey, queryConfig]) => ({ tsKey, queryConfig, relation: tableConfig.relations[tsKey]! })); + } + + let extras; + + // Figure out which extras to select + if (config.extras) { + extras = typeof config.extras === 'function' + ? config.extras(aliasedColumns, { sql }) + : config.extras; + for (const [tsKey, value] of Object.entries(extras)) { + fieldsSelection.push({ + tsKey, + value: mapColumnsInAliasedSQLToAlias(value, tableAlias), + }); + } + } + + // Transform `fieldsSelection` into `selection` + // `fieldsSelection` shouldn't be used after this point + for (const { tsKey, value } of fieldsSelection) { + selection.push({ + dbKey: is(value, SQL.Aliased) ? value.fieldAlias : tableConfig.columns[tsKey]!.name, + tsKey, + field: is(value, Column) ? aliasedTableColumn(value, tableAlias) : value, + relationTableTsKey: undefined, + isJson: false, + selection: [], + }); + } + + let orderByOrig = typeof config.orderBy === 'function' + ? config.orderBy(aliasedColumns, getOrderByOperators()) + : config.orderBy ?? []; + if (!Array.isArray(orderByOrig)) { + orderByOrig = [orderByOrig]; + } + orderBy = orderByOrig.map((orderByValue) => { + if (is(orderByValue, Column)) { + return aliasedTableColumn(orderByValue, tableAlias) as SingleStoreColumn; + } + return mapColumnsInSQLToAlias(orderByValue, tableAlias); + }); + + limit = config.limit; + offset = config.offset; + + // Process all relations + for ( + const { + tsKey: selectedRelationTsKey, + queryConfig: selectedRelationConfigValue, + relation, + } of selectedRelations + ) { + const normalizedRelation = normalizeRelation(schema, tableNamesMap, relation); + const relationTableName = getTableUniqueName(relation.referencedTable); + const relationTableTsName = tableNamesMap[relationTableName]!; + const relationTableAlias = `${tableAlias}_${selectedRelationTsKey}`; + const joinOn = and( + ...normalizedRelation.fields.map((field, i) => + eq( + aliasedTableColumn(normalizedRelation.references[i]!, relationTableAlias), + aliasedTableColumn(field, tableAlias), + ) + ), + ); + const builtRelation = this.buildRelationalQuery({ + fullSchema, + schema, + tableNamesMap, + table: fullSchema[relationTableTsName] as SingleStoreTable, + tableConfig: schema[relationTableTsName]!, + queryConfig: is(relation, One) + ? (selectedRelationConfigValue === true + ? { limit: 1 } + : { ...selectedRelationConfigValue, limit: 1 }) + : selectedRelationConfigValue, + tableAlias: relationTableAlias, + joinOn, + nestedQueryRelation: relation, + }); + const field = sql`coalesce(${sql.identifier(relationTableAlias)}.${sql.identifier('data')}, "[]")`.as( + selectedRelationTsKey, + ); + joins.push({ + on: sql`true`, + table: new Subquery(builtRelation.sql as SQL, {}, relationTableAlias), + alias: relationTableAlias, + joinType: 'left', + lateral: true, + }); + selection.push({ + dbKey: selectedRelationTsKey, + tsKey: selectedRelationTsKey, + field, + relationTableTsKey: relationTableTsName, + isJson: true, + selection: builtRelation.selection, + }); + } + } + + if (selection.length === 0) { + throw new DrizzleError({ message: `No fields selected for table "${tableConfig.tsName}" ("${tableAlias}")` }); + } + + let result; + + where = and(joinOn, where); + + if (nestedQueryRelation) { + let field = sql`JSON_BUILD_OBJECT(${ + sql.join( + selection.map(({ field, tsKey, isJson }, index) => + isJson + ? sql`${index}, ${sql.identifier(`${tableAlias}_${tsKey}`)}.${sql.identifier('data')}` + : is(field, SQL.Aliased) + ? sql`${index}, ${field.sql}` + : sql`${index}, ${field}` + ), + sql`, `, + ) + })`; + if (is(nestedQueryRelation, Many)) { + field = sql`json_agg(${field})`; + } + const nestedSelection = [{ + dbKey: 'data', + tsKey: 'data', + field: field.as('data'), + isJson: true, + relationTableTsKey: tableConfig.tsName, + selection, + }]; + + const needsSubquery = limit !== undefined || offset !== undefined || (orderBy?.length ?? 0) > 0; + + if (needsSubquery) { + result = this.buildSelectQuery({ + table: aliasedTable(table, tableAlias), + fields: {}, + fieldsFlat: [ + { + path: [], + field: sql.raw('*'), + }, + ...(((orderBy?.length ?? 0) > 0) + ? [{ + path: [], + field: sql`row_number() over (order by ${sql.join(orderBy!, sql`, `)})`, + }] + : []), + ], + where, + limit, + offset, + setOperators: [], + }); + + where = undefined; + limit = undefined; + offset = undefined; + orderBy = undefined; + } else { + result = aliasedTable(table, tableAlias); + } + + result = this.buildSelectQuery({ + table: is(result, SingleStoreTable) ? result : new Subquery(result, {}, tableAlias), + fields: {}, + fieldsFlat: nestedSelection.map(({ field }) => ({ + path: [], + field: is(field, Column) ? aliasedTableColumn(field, tableAlias) : field, + })), + joins, + where, + limit, + offset, + orderBy, + setOperators: [], + }); + } else { + result = this.buildSelectQuery({ + table: aliasedTable(table, tableAlias), + fields: {}, + fieldsFlat: selection.map(({ field }) => ({ + path: [], + field: is(field, Column) ? aliasedTableColumn(field, tableAlias) : field, + })), + joins, + where, + limit, + offset, + orderBy, + setOperators: [], + }); + } + + return { + tableTsKey: tableConfig.tsName, + sql: result, + selection, + }; + } + + buildRelationalQueryWithoutLateralSubqueries({ + fullSchema, + schema, + tableNamesMap, + table, + tableConfig, + queryConfig: config, + tableAlias, + nestedQueryRelation, + joinOn, + }: { + fullSchema: Record; + schema: TablesRelationalConfig; + tableNamesMap: Record; + table: SingleStoreTable; + tableConfig: TableRelationalConfig; + queryConfig: true | DBQueryConfig<'many', true>; + tableAlias: string; + nestedQueryRelation?: Relation; + joinOn?: SQL; + }): BuildRelationalQueryResult { + let selection: BuildRelationalQueryResult['selection'] = []; + let limit, offset, orderBy: SingleStoreSelectConfig['orderBy'] = [], where; + + if (config === true) { + const selectionEntries = Object.entries(tableConfig.columns); + selection = selectionEntries.map(( + [key, value], + ) => ({ + dbKey: value.name, + tsKey: key, + field: aliasedTableColumn(value as SingleStoreColumn, tableAlias), + relationTableTsKey: undefined, + isJson: false, + selection: [], + })); + } else { + const aliasedColumns = Object.fromEntries( + Object.entries(tableConfig.columns).map(([key, value]) => [key, aliasedTableColumn(value, tableAlias)]), + ); + + if (config.where) { + const whereSql = typeof config.where === 'function' + ? config.where(aliasedColumns, getOperators()) + : config.where; + where = whereSql && mapColumnsInSQLToAlias(whereSql, tableAlias); + } + + const fieldsSelection: { tsKey: string; value: SingleStoreColumn | SQL.Aliased }[] = []; + let selectedColumns: string[] = []; + + // Figure out which columns to select + if (config.columns) { + let isIncludeMode = false; + + for (const [field, value] of Object.entries(config.columns)) { + if (value === undefined) { + continue; + } + + if (field in tableConfig.columns) { + if (!isIncludeMode && value === true) { + isIncludeMode = true; + } + selectedColumns.push(field); + } + } + + if (selectedColumns.length > 0) { + selectedColumns = isIncludeMode + ? selectedColumns.filter((c) => config.columns?.[c] === true) + : Object.keys(tableConfig.columns).filter((key) => !selectedColumns.includes(key)); + } + } else { + // Select all columns if selection is not specified + selectedColumns = Object.keys(tableConfig.columns); + } + + for (const field of selectedColumns) { + const column = tableConfig.columns[field]! as SingleStoreColumn; + fieldsSelection.push({ tsKey: field, value: column }); + } + + let selectedRelations: { + tsKey: string; + queryConfig: true | DBQueryConfig<'many', false>; + relation: Relation; + }[] = []; + + // Figure out which relations to select + if (config.with) { + selectedRelations = Object.entries(config.with) + .filter((entry): entry is [typeof entry[0], NonNullable] => !!entry[1]) + .map(([tsKey, queryConfig]) => ({ tsKey, queryConfig, relation: tableConfig.relations[tsKey]! })); + } + + let extras; + + // Figure out which extras to select + if (config.extras) { + extras = typeof config.extras === 'function' + ? config.extras(aliasedColumns, { sql }) + : config.extras; + for (const [tsKey, value] of Object.entries(extras)) { + fieldsSelection.push({ + tsKey, + value: mapColumnsInAliasedSQLToAlias(value, tableAlias), + }); + } + } + + // Transform `fieldsSelection` into `selection` + // `fieldsSelection` shouldn't be used after this point + for (const { tsKey, value } of fieldsSelection) { + selection.push({ + dbKey: is(value, SQL.Aliased) ? value.fieldAlias : tableConfig.columns[tsKey]!.name, + tsKey, + field: is(value, Column) ? aliasedTableColumn(value, tableAlias) : value, + relationTableTsKey: undefined, + isJson: false, + selection: [], + }); + } + + let orderByOrig = typeof config.orderBy === 'function' + ? config.orderBy(aliasedColumns, getOrderByOperators()) + : config.orderBy ?? []; + if (!Array.isArray(orderByOrig)) { + orderByOrig = [orderByOrig]; + } + orderBy = orderByOrig.map((orderByValue) => { + if (is(orderByValue, Column)) { + return aliasedTableColumn(orderByValue, tableAlias) as SingleStoreColumn; + } + return mapColumnsInSQLToAlias(orderByValue, tableAlias); + }); + + limit = config.limit; + offset = config.offset; + + // Process all relations + for ( + const { + tsKey: selectedRelationTsKey, + queryConfig: selectedRelationConfigValue, + relation, + } of selectedRelations + ) { + const normalizedRelation = normalizeRelation(schema, tableNamesMap, relation); + const relationTableName = getTableUniqueName(relation.referencedTable); + const relationTableTsName = tableNamesMap[relationTableName]!; + const relationTableAlias = `${tableAlias}_${selectedRelationTsKey}`; + const joinOn = and( + ...normalizedRelation.fields.map((field, i) => + eq( + aliasedTableColumn(normalizedRelation.references[i]!, relationTableAlias), + aliasedTableColumn(field, tableAlias), + ) + ), + ); + const builtRelation = this.buildRelationalQueryWithoutLateralSubqueries({ + fullSchema, + schema, + tableNamesMap, + table: fullSchema[relationTableTsName] as SingleStoreTable, + tableConfig: schema[relationTableTsName]!, + queryConfig: is(relation, One) + ? (selectedRelationConfigValue === true + ? { limit: 1 } + : { ...selectedRelationConfigValue, limit: 1 }) + : selectedRelationConfigValue, + tableAlias: relationTableAlias, + joinOn, + nestedQueryRelation: relation, + }); + let fieldSql = sql`(${builtRelation.sql})`; + if (is(relation, Many)) { + fieldSql = sql`coalesce(${fieldSql}, "[]")`; + } + const field = fieldSql.as(selectedRelationTsKey); + selection.push({ + dbKey: selectedRelationTsKey, + tsKey: selectedRelationTsKey, + field, + relationTableTsKey: relationTableTsName, + isJson: true, + selection: builtRelation.selection, + }); + } + } + + if (selection.length === 0) { + throw new DrizzleError({ + message: + `No fields selected for table "${tableConfig.tsName}" ("${tableAlias}"). You need to have at least one item in "columns", "with" or "extras". If you need to select all columns, omit the "columns" key or set it to undefined.`, + }); + } + + let result; + + where = and(joinOn, where); + + if (nestedQueryRelation) { + let field = sql`JSON_BUILD_OBJECT(${ + sql.join( + selection.map(({ field }, index) => + is(field, SingleStoreColumn) + ? sql`${index}, ${sql.identifier(field.name)}` + : is(field, SQL.Aliased) + ? sql`${index}, ${field.sql}` + : sql`${index}, ${field}` + ), + sql`, `, + ) + })`; + if (is(nestedQueryRelation, Many)) { + field = sql`json_agg(${field})`; + } + const nestedSelection = [{ + dbKey: 'data', + tsKey: 'data', + field, + isJson: true, + relationTableTsKey: tableConfig.tsName, + selection, + }]; + + const needsSubquery = limit !== undefined || offset !== undefined || orderBy.length > 0; + + if (needsSubquery) { + result = this.buildSelectQuery({ + table: aliasedTable(table, tableAlias), + fields: {}, + fieldsFlat: [ + { + path: [], + field: sql.raw('*'), + }, + ...(orderBy.length > 0) + ? [{ + path: [], + field: sql`row_number() over (order by ${sql.join(orderBy, sql`, `)})`, + }] + : [], + ], + where, + limit, + offset, + setOperators: [], + }); + + where = undefined; + limit = undefined; + offset = undefined; + orderBy = undefined; + } else { + result = aliasedTable(table, tableAlias); + } + + result = this.buildSelectQuery({ + table: is(result, SingleStoreTable) ? result : new Subquery(result, {}, tableAlias), + fields: {}, + fieldsFlat: nestedSelection.map(({ field }) => ({ + path: [], + field: is(field, Column) ? aliasedTableColumn(field, tableAlias) : field, + })), + where, + limit, + offset, + orderBy, + setOperators: [], + }); + } else { + result = this.buildSelectQuery({ + table: aliasedTable(table, tableAlias), + fields: {}, + fieldsFlat: selection.map(({ field }) => ({ + path: [], + field: is(field, Column) ? aliasedTableColumn(field, tableAlias) : field, + })), + where, + limit, + offset, + orderBy, + setOperators: [], + }); + } + + return { + tableTsKey: tableConfig.tsName, + sql: result, + selection, + }; + } +} diff --git a/drizzle-orm/src/singlestore-core/expressions.ts b/drizzle-orm/src/singlestore-core/expressions.ts new file mode 100644 index 000000000..6d4284d18 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/expressions.ts @@ -0,0 +1,25 @@ +import { bindIfParam } from '~/expressions.ts'; +import type { Placeholder, SQL, SQLChunk, SQLWrapper } from '~/sql/sql.ts'; +import { sql } from '~/sql/sql.ts'; +import type { SingleStoreColumn } from './columns/index.ts'; + +export * from '~/expressions.ts'; + +export function concat(column: SingleStoreColumn | SQL.Aliased, value: string | Placeholder | SQLWrapper): SQL { + return sql`${column} || ${bindIfParam(value, column)}`; +} + +export function substring( + column: SingleStoreColumn | SQL.Aliased, + { from, for: _for }: { from?: number | Placeholder | SQLWrapper; for?: number | Placeholder | SQLWrapper }, +): SQL { + const chunks: SQLChunk[] = [sql`substring(`, column]; + if (from !== undefined) { + chunks.push(sql` from `, bindIfParam(from, column)); + } + if (_for !== undefined) { + chunks.push(sql` for `, bindIfParam(_for, column)); + } + chunks.push(sql`)`); + return sql.join(chunks); +} diff --git a/drizzle-orm/src/singlestore-core/index.ts b/drizzle-orm/src/singlestore-core/index.ts new file mode 100644 index 000000000..4da014404 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/index.ts @@ -0,0 +1,16 @@ +export * from './alias.ts'; +export * from './columns/index.ts'; +export * from './db.ts'; +export * from './dialect.ts'; +export * from './indexes.ts'; +export * from './primary-keys.ts'; +export * from './query-builders/index.ts'; +export * from './schema.ts'; +export * from './session.ts'; +export * from './sql/index.ts'; +export * from './subquery.ts'; +export * from './table.ts'; +export * from './unique-constraint.ts'; +export * from './utils.ts'; +export * from './view-common.ts'; +export * from './view.ts'; diff --git a/drizzle-orm/src/singlestore-core/indexes.ts b/drizzle-orm/src/singlestore-core/indexes.ts new file mode 100644 index 000000000..172f524f5 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/indexes.ts @@ -0,0 +1,191 @@ +import { entityKind } from '~/entity.ts'; +import type { SQL } from '~/sql/sql.ts'; +import type { AnySingleStoreColumn, SingleStoreColumn } from './columns/index.ts'; +import type { SingleStoreTable } from './table.ts'; + +interface IndexConfig { + name: string; + + columns: IndexColumn[]; + + /** + * If true, the index will be created as `create unique index` instead of `create index`. + */ + unique?: boolean; + + /** + * If set, the index will be created as `create index ... using { 'btree' | 'hash' }`. + */ + using?: 'btree' | 'hash'; + + /** + * If set, the index will be created as `create index ... algorythm { 'default' | 'inplace' | 'copy' }`. + */ + algorythm?: 'default' | 'inplace' | 'copy'; + + /** + * If set, adds locks to the index creation. + */ + lock?: 'default' | 'none' | 'shared' | 'exclusive'; +} + +export type IndexColumn = SingleStoreColumn | SQL; + +export class IndexBuilderOn { + static readonly [entityKind]: string = 'SingleStoreIndexBuilderOn'; + + constructor(private name: string, private unique: boolean) {} + + on(...columns: [IndexColumn, ...IndexColumn[]]): IndexBuilder { + return new IndexBuilder(this.name, columns, this.unique); + } +} + +export interface AnyIndexBuilder { + build(table: SingleStoreTable): Index; +} + +export interface AnyFullTextIndexBuilder { + build(table: SingleStoreTable): FullTextIndex; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IndexBuilder extends AnyIndexBuilder {} + +export class IndexBuilder implements AnyIndexBuilder { + static readonly [entityKind]: string = 'SingleStoreIndexBuilder'; + + /** @internal */ + config: IndexConfig; + + constructor(name: string, columns: IndexColumn[], unique: boolean) { + this.config = { + name, + columns, + unique, + }; + } + + using(using: IndexConfig['using']): this { + this.config.using = using; + return this; + } + + algorythm(algorythm: IndexConfig['algorythm']): this { + this.config.algorythm = algorythm; + return this; + } + + lock(lock: IndexConfig['lock']): this { + this.config.lock = lock; + return this; + } + + /** @internal */ + build(table: SingleStoreTable): Index { + return new Index(this.config, table); + } +} + +export class Index { + static readonly [entityKind]: string = 'SingleStoreIndex'; + + readonly config: IndexConfig & { table: SingleStoreTable }; + + constructor(config: IndexConfig, table: SingleStoreTable) { + this.config = { ...config, table }; + } +} + +export type GetColumnsTableName = TColumns extends + AnySingleStoreColumn<{ tableName: infer TTableName extends string }> | AnySingleStoreColumn< + { tableName: infer TTableName extends string } + >[] ? TTableName + : never; + +export function index(name: string): IndexBuilderOn { + return new IndexBuilderOn(name, false); +} + +export function uniqueIndex(name: string): IndexBuilderOn { + return new IndexBuilderOn(name, true); +} + +interface FullTextIndexConfig { + version?: number; +} + +interface FullTextIndexFullConfig extends FullTextIndexConfig { + columns: IndexColumn[]; + + name: string; +} + +export class FullTextIndexBuilderOn { + static readonly [entityKind]: string = 'SingleStoreFullTextIndexBuilderOn'; + + constructor(private name: string, private config: FullTextIndexConfig) {} + + on(...columns: [IndexColumn, ...IndexColumn[]]): FullTextIndexBuilder { + return new FullTextIndexBuilder({ + name: this.name, + columns: columns, + ...this.config, + }); + } +} + +export interface FullTextIndexBuilder extends AnyFullTextIndexBuilder {} + +export class FullTextIndexBuilder implements AnyFullTextIndexBuilder { + static readonly [entityKind]: string = 'SingleStoreFullTextIndexBuilder'; + + /** @internal */ + config: FullTextIndexFullConfig; + + constructor(config: FullTextIndexFullConfig) { + this.config = config; + } + + /** @internal */ + build(table: SingleStoreTable): FullTextIndex { + return new FullTextIndex(this.config, table); + } +} + +export class FullTextIndex { + static readonly [entityKind]: string = 'SingleStoreFullTextIndex'; + + readonly config: FullTextIndexConfig & { table: SingleStoreTable }; + + constructor(config: FullTextIndexConfig, table: SingleStoreTable) { + this.config = { ...config, table }; + } +} + +export function fulltext(name: string, config: FullTextIndexConfig): FullTextIndexBuilderOn { + return new FullTextIndexBuilderOn(name, config); +} + +export type SortKeyColumn = SingleStoreColumn | SQL; + +export class SortKeyBuilder { + static readonly [entityKind]: string = 'SingleStoreSortKeyBuilder'; + + constructor(private columns: SortKeyColumn[]) {} + + /** @internal */ + build(table: SingleStoreTable): SortKey { + return new SortKey(this.columns, table); + } +} + +export class SortKey { + static readonly [entityKind]: string = 'SingleStoreSortKey'; + + constructor(public columns: SortKeyColumn[], public table: SingleStoreTable) {} +} + +export function sortKey(...columns: SortKeyColumn[]): SortKeyBuilder { + return new SortKeyBuilder(columns); +} diff --git a/drizzle-orm/src/singlestore-core/primary-keys.ts b/drizzle-orm/src/singlestore-core/primary-keys.ts new file mode 100644 index 000000000..47dc0a19c --- /dev/null +++ b/drizzle-orm/src/singlestore-core/primary-keys.ts @@ -0,0 +1,63 @@ +import { entityKind } from '~/entity.ts'; +import type { AnySingleStoreColumn, SingleStoreColumn } from './columns/index.ts'; +import { SingleStoreTable } from './table.ts'; + +export function primaryKey< + TTableName extends string, + TColumn extends AnySingleStoreColumn<{ tableName: TTableName }>, + TColumns extends AnySingleStoreColumn<{ tableName: TTableName }>[], +>(config: { name?: string; columns: [TColumn, ...TColumns] }): PrimaryKeyBuilder; +/** + * @deprecated: Please use primaryKey({ columns: [] }) instead of this function + * @param columns + */ +export function primaryKey< + TTableName extends string, + TColumns extends AnySingleStoreColumn<{ tableName: TTableName }>[], +>(...columns: TColumns): PrimaryKeyBuilder; +export function primaryKey(...config: any) { + if (config[0].columns) { + return new PrimaryKeyBuilder(config[0].columns, config[0].name); + } + return new PrimaryKeyBuilder(config); +} + +export class PrimaryKeyBuilder { + static readonly [entityKind]: string = 'SingleStorePrimaryKeyBuilder'; + + /** @internal */ + columns: SingleStoreColumn[]; + + /** @internal */ + name?: string; + + constructor( + columns: SingleStoreColumn[], + name?: string, + ) { + this.columns = columns; + this.name = name; + } + + /** @internal */ + build(table: SingleStoreTable): PrimaryKey { + return new PrimaryKey(table, this.columns, this.name); + } +} + +export class PrimaryKey { + static readonly [entityKind]: string = 'SingleStorePrimaryKey'; + + readonly columns: SingleStoreColumn[]; + readonly name?: string; + + constructor(readonly table: SingleStoreTable, columns: SingleStoreColumn[], name?: string) { + this.columns = columns; + this.name = name; + } + + getName(): string { + return this.name + ?? `${this.table[SingleStoreTable.Symbol.Name]}_${this.columns.map((column) => column.name).join('_')}_pk`; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/attach.ts b/drizzle-orm/src/singlestore-core/query-builders/attach.ts new file mode 100644 index 000000000..0b5bbac1a --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/attach.ts @@ -0,0 +1,198 @@ +import { entityKind } from '~/entity.ts'; +import { DrizzleError } from '~/errors.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; + +export type SingleStoreAttachWithout< + T extends AnySingleStoreAttachBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreAttachBase< + T['_']['database'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreAttach< + TDatabase extends string = string, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreAttachBase; + +export interface SingleStoreAttachConfig { + milestone?: string | undefined; + time?: Date | undefined; + database: string; + databaseAlias?: string | undefined; + readOnly?: boolean | undefined; +} + +export type SingleStoreAttachPrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +type SingleStoreAttachDynamic = SingleStoreAttach< + T['_']['database'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +type AnySingleStoreAttachBase = SingleStoreAttachBase; + +export interface SingleStoreAttachBase< + TDatabase extends string, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise> { + readonly _: { + readonly database: TDatabase; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreAttachBase< + TDatabase extends string, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static readonly [entityKind]: string = 'SingleStoreAttach'; + + private config: SingleStoreAttachConfig; + + constructor( + private database: TDatabase, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) { + super(); + this.config = { database }; + } + + as(dabataseAlias: string): SingleStoreAttachWithout { + if (this.config.readOnly) { + throw new DrizzleError({ message: 'Cannot set both databaseAlias and readOnly' }); + } + this.config.databaseAlias = dabataseAlias; + return this as any; + } + + /** + * Adds a `where` clause to the query. + * + * Calling this method will delete only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be deleted. + * + * ```ts + * // Attach all cars with green color + * db.delete(cars).where(eq(cars.color, 'green')); + * // or + * db.delete(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Attach all BMW cars with a green color + * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Attach all cars with the green or blue color + * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + // TODO(singlestore): docs + atMilestone(milestone: string): SingleStoreAttachWithout { + if (this.config.time) { + throw new DrizzleError({ message: 'Cannot set both time and milestone' }); + } + this.config.milestone = milestone; + return this as any; + } + + // TODO(singlestore): docs + atTime(time: Date): SingleStoreAttachWithout { + if (this.config.milestone) { + throw new DrizzleError({ message: 'Cannot set both time and milestone' }); + } + this.config.time = time; + return this as any; + } + + // TODO(singlestore): docs + readOnly(): SingleStoreAttachWithout { + if (this.config.databaseAlias) { + throw new DrizzleError({ message: 'Cannot set both databaseAlias and readOnly' }); + } + this.config.readOnly = true; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildAttachQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreAttachPrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + undefined, + ) as SingleStoreAttachPrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreAttachDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/branch.ts b/drizzle-orm/src/singlestore-core/query-builders/branch.ts new file mode 100644 index 000000000..5ed8b6831 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/branch.ts @@ -0,0 +1,186 @@ +import { entityKind } from '~/entity.ts'; +import { DrizzleError } from '~/errors.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import type { SingleStoreAttachConfig } from './attach.ts'; + +export type SingleStoreBranchWithout< + T extends AnySingleStoreBranchBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreBranchBase< + T['_']['database'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreBranch< + TDatabase extends string = string, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreBranchBase; + +export interface SingleStoreBranchConfig extends SingleStoreAttachConfig { + databaseAlias: string; + fromWorkspaceGroup?: string | undefined; +} + +export type SingleStoreBranchPrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +type SingleStoreBranchDynamic = SingleStoreBranch< + T['_']['database'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +type AnySingleStoreBranchBase = SingleStoreBranchBase; + +export interface SingleStoreBranchBase< + TDatabase extends string, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise> { + readonly _: { + readonly database: TDatabase; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreBranchBase< + TDatabase extends string, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static readonly [entityKind]: string = 'SingleStoreBranch'; + + private config: SingleStoreBranchConfig; + + constructor( + private database: TDatabase, + private branchName: string, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) { + super(); + this.config = { database, databaseAlias: branchName }; + } + + /** + * Adds a `where` clause to the query. + * + * Calling this method will delete only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be deleted. + * + * ```ts + * // Attach all cars with green color + * db.delete(cars).where(eq(cars.color, 'green')); + * // or + * db.delete(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Attach all BMW cars with a green color + * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Attach all cars with the green or blue color + * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + // TODO(singlestore): docs + atMilestone(milestone: string): SingleStoreBranchWithout { + if (this.config.time) { + throw new DrizzleError({ message: 'Cannot set both time and milestone' }); + } + this.config.milestone = milestone; + return this as any; + } + + // TODO(singlestore): docs + atTime(time: Date): SingleStoreBranchWithout { + if (this.config.milestone) { + throw new DrizzleError({ message: 'Cannot set both time and milestone' }); + } + this.config.time = time; + return this as any; + } + + // TODO(singlestore): docs + fromWorkspaceGroup(groupID: string): SingleStoreBranchWithout { + this.config.fromWorkspaceGroup = groupID; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildAttachQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreBranchPrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + undefined, + ) as SingleStoreBranchPrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreBranchDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/createMilestone.ts b/drizzle-orm/src/singlestore-core/query-builders/createMilestone.ts new file mode 100644 index 000000000..7922e39a1 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/createMilestone.ts @@ -0,0 +1,136 @@ +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; + +export type SingleStoreCreateMilestoneWithout< + T extends AnySingleStoreCreateMilestoneBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreCreateMilestoneBase< + T['_']['milestone'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreCreateMilestone< + TDatabase extends string = string, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreCreateMilestoneBase; + +export interface SingleStoreCreateMilestoneConfig { + milestone: string; + database?: string | undefined; +} + +export type SingleStoreCreateMilestonePrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +type SingleStoreCreateMilestoneDynamic = SingleStoreCreateMilestone< + T['_']['milestone'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +type AnySingleStoreCreateMilestoneBase = SingleStoreCreateMilestoneBase; + +export interface SingleStoreCreateMilestoneBase< + TMilestone extends string, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise> { + readonly _: { + readonly milestone: TMilestone; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreCreateMilestoneBase< + TMilestone extends string, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static readonly [entityKind]: string = 'SingleStoreCreateMilestone'; + + private config: SingleStoreCreateMilestoneConfig; + + constructor( + private milestone: TMilestone, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) { + super(); + this.config = { milestone }; + } + + // TODO(singlestore): docs + for(database: string): SingleStoreCreateMilestoneWithout { + this.config.database = database; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildCreateMilestoneQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreCreateMilestonePrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + undefined, + ) as SingleStoreCreateMilestonePrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreCreateMilestoneDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/delete.ts b/drizzle-orm/src/singlestore-core/query-builders/delete.ts new file mode 100644 index 000000000..e0a463784 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/delete.ts @@ -0,0 +1,170 @@ +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import type { Subquery } from '~/subquery.ts'; +import type { SelectedFieldsOrdered } from './select.types.ts'; + +export type SingleStoreDeleteWithout< + T extends AnySingleStoreDeleteBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreDeleteBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreDelete< + TTable extends SingleStoreTable = SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreDeleteBase; + +export interface SingleStoreDeleteConfig { + where?: SQL | undefined; + table: SingleStoreTable; + returning?: SelectedFieldsOrdered; + withList?: Subquery[]; +} + +export type SingleStoreDeletePrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +type SingleStoreDeleteDynamic = SingleStoreDelete< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +type AnySingleStoreDeleteBase = SingleStoreDeleteBase; + +export interface SingleStoreDeleteBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise> { + readonly _: { + readonly table: TTable; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreDeleteBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static readonly [entityKind]: string = 'SingleStoreDelete'; + + private config: SingleStoreDeleteConfig; + + constructor( + private table: TTable, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + withList?: Subquery[], + ) { + super(); + this.config = { table, withList }; + } + + /** + * Adds a `where` clause to the query. + * + * Calling this method will delete only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be deleted. + * + * ```ts + * // Delete all cars with green color + * db.delete(cars).where(eq(cars.color, 'green')); + * // or + * db.delete(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Delete all BMW cars with a green color + * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Delete all cars with the green or blue color + * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + where(where: SQL | undefined): SingleStoreDeleteWithout { + this.config.where = where; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildDeleteQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreDeletePrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + this.config.returning, + ) as SingleStoreDeletePrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreDeleteDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/detach.ts b/drizzle-orm/src/singlestore-core/query-builders/detach.ts new file mode 100644 index 000000000..df3452074 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/detach.ts @@ -0,0 +1,172 @@ +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; + +export type SingleStoreDetachWithout< + T extends AnySingleStoreDetachBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreDetachBase< + T['_']['database'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreDetach< + TDatabase extends string = string, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreDetachBase; + +export interface SingleStoreDetachConfig { + milestone?: string | undefined; + database: string; + workspace?: string | undefined; +} + +export type SingleStoreDetachPrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +type SingleStoreDetachDynamic = SingleStoreDetach< + T['_']['database'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +type AnySingleStoreDetachBase = SingleStoreDetachBase; + +export interface SingleStoreDetachBase< + TDatabase extends string, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise> { + readonly _: { + readonly database: TDatabase; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreDetachBase< + TDatabase extends string, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static readonly [entityKind]: string = 'SingleStoreDetach'; + + private config: SingleStoreDetachConfig; + + constructor( + private database: TDatabase, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) { + super(); + this.config = { database }; + } + + /** + * Adds a `where` clause to the query. + * + * Calling this method will delete only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/delete} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be deleted. + * + * ```ts + * // Detach all cars with green color + * db.delete(cars).where(eq(cars.color, 'green')); + * // or + * db.delete(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Detach all BMW cars with a green color + * db.delete(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Detach all cars with the green or blue color + * db.delete(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + // TODO(singlestore): docs + atMilestone(milestone: string): SingleStoreDetachWithout { + this.config.milestone = milestone; + return this as any; + } + + // TODO(singlestore): docs + fromWorkspace(workspace: string): SingleStoreDetachWithout { + this.config.workspace = workspace; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildDetachQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreDetachPrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + undefined, + ) as SingleStoreDetachPrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreDetachDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/dropMilestone.ts b/drizzle-orm/src/singlestore-core/query-builders/dropMilestone.ts new file mode 100644 index 000000000..7fba4f05e --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/dropMilestone.ts @@ -0,0 +1,136 @@ +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; + +export type SingleStoreDropMilestoneWithout< + T extends AnySingleStoreDropMilestoneBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreDropMilestoneBase< + T['_']['milestone'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreDropMilestone< + TDatabase extends string = string, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreDropMilestoneBase; + +export interface SingleStoreDropMilestoneConfig { + milestone: string; + database?: string | undefined; +} + +export type SingleStoreDropMilestonePrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +type SingleStoreDropMilestoneDynamic = SingleStoreDropMilestone< + T['_']['milestone'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +type AnySingleStoreDropMilestoneBase = SingleStoreDropMilestoneBase; + +export interface SingleStoreDropMilestoneBase< + TMilestone extends string, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise> { + readonly _: { + readonly milestone: TMilestone; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreDropMilestoneBase< + TMilestone extends string, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static readonly [entityKind]: string = 'SingleStoreDropMilestone'; + + private config: SingleStoreDropMilestoneConfig; + + constructor( + private milestone: TMilestone, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) { + super(); + this.config = { milestone }; + } + + // TODO(singlestore): docs + for(database: string): SingleStoreDropMilestoneWithout { + this.config.database = database; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildDropMilestoneQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreDropMilestonePrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + undefined, + ) as SingleStoreDropMilestonePrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreDropMilestoneDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/index.ts b/drizzle-orm/src/singlestore-core/query-builders/index.ts new file mode 100644 index 000000000..16f0e1d4d --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/index.ts @@ -0,0 +1,6 @@ +export * from './delete.ts'; +export * from './insert.ts'; +export * from './query-builder.ts'; +export * from './select.ts'; +export * from './select.types.ts'; +export * from './update.ts'; diff --git a/drizzle-orm/src/singlestore-core/query-builders/insert.ts b/drizzle-orm/src/singlestore-core/query-builders/insert.ts new file mode 100644 index 000000000..129bf2214 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/insert.ts @@ -0,0 +1,305 @@ +import { entityKind, is } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { RunnableQuery } from '~/runnable-query.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Placeholder, Query, SQLWrapper } from '~/sql/sql.ts'; +import { Param, SQL, sql } from '~/sql/sql.ts'; +import type { InferModelFromColumns } from '~/table.ts'; +import { Table } from '~/table.ts'; +import { mapUpdateSet, orderSelectedFields } from '~/utils.ts'; +import type { AnySingleStoreColumn, SingleStoreColumn } from '../columns/common.ts'; +import type { SelectedFieldsOrdered } from './select.types.ts'; +import type { SingleStoreUpdateSetSource } from './update.ts'; + +export interface SingleStoreInsertConfig { + table: TTable; + values: Record[]; + ignore: boolean; + onConflict?: SQL; + returning?: SelectedFieldsOrdered; +} + +export type AnySingleStoreInsertConfig = SingleStoreInsertConfig; + +export type SingleStoreInsertValue = + & { + [Key in keyof TTable['$inferInsert']]: TTable['$inferInsert'][Key] | SQL | Placeholder; + } + & {}; + +export class SingleStoreInsertBuilder< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, +> { + static readonly [entityKind]: string = 'SingleStoreInsertBuilder'; + + private shouldIgnore = false; + + constructor( + private table: TTable, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) {} + + ignore(): this { + this.shouldIgnore = true; + return this; + } + + values(value: SingleStoreInsertValue): SingleStoreInsertBase; + values(values: SingleStoreInsertValue[]): SingleStoreInsertBase; + values( + values: SingleStoreInsertValue | SingleStoreInsertValue[], + ): SingleStoreInsertBase { + values = Array.isArray(values) ? values : [values]; + if (values.length === 0) { + throw new Error('values() must be called with at least one value'); + } + const mappedValues = values.map((entry) => { + const result: Record = {}; + const cols = this.table[Table.Symbol.Columns]; + for (const colKey of Object.keys(entry)) { + const colValue = entry[colKey as keyof typeof entry]; + result[colKey] = is(colValue, SQL) ? colValue : new Param(colValue, cols[colKey]); + } + return result; + }); + + return new SingleStoreInsertBase(this.table, mappedValues, this.shouldIgnore, this.session, this.dialect); + } +} + +export type SingleStoreInsertWithout< + T extends AnySingleStoreInsert, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreInsertBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + T['_']['returning'], + TDynamic, + T['_']['excludedMethods'] | '$returning' + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreInsertDynamic = SingleStoreInsert< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + T['_']['returning'] +>; + +export type SingleStoreInsertPrepare< + T extends AnySingleStoreInsert, + TReturning extends Record | undefined = undefined, +> = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: TReturning extends undefined ? SingleStoreQueryResultKind : TReturning[]; + iterator: never; + }, + true +>; + +export type SingleStoreInsertOnDuplicateKeyUpdateConfig = { + set: SingleStoreUpdateSetSource; +}; + +export type SingleStoreInsert< + TTable extends SingleStoreTable = SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TReturning extends Record | undefined = Record | undefined, +> = SingleStoreInsertBase; + +export type SingleStoreInsertReturning< + T extends AnySingleStoreInsert, + TDynamic extends boolean, +> = SingleStoreInsertBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + InferModelFromColumns>, + TDynamic, + T['_']['excludedMethods'] | '$returning' +>; + +export type AnySingleStoreInsert = SingleStoreInsertBase; + +export interface SingleStoreInsertBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TReturning extends Record | undefined = undefined, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends + QueryPromise : TReturning[]>, + RunnableQuery< + TReturning extends undefined ? SingleStoreQueryResultKind : TReturning[], + 'singlestore' + >, + SQLWrapper +{ + readonly _: { + readonly dialect: 'singlestore'; + readonly table: TTable; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + readonly returning: TReturning; + readonly result: TReturning extends undefined ? SingleStoreQueryResultKind : TReturning[]; + }; +} + +export type PrimaryKeyKeys> = { + [K in keyof T]: T[K]['_']['isPrimaryKey'] extends true ? T[K]['_']['isAutoincrement'] extends true ? K + : T[K]['_']['hasRuntimeDefault'] extends true ? T[K]['_']['isPrimaryKey'] extends true ? K : never + : never + : T[K]['_']['hasRuntimeDefault'] extends true ? T[K]['_']['isPrimaryKey'] extends true ? K : never + : never; +}[keyof T]; + +export type GetPrimarySerialOrDefaultKeys> = { + [K in PrimaryKeyKeys]: T[K]; +}; + +export class SingleStoreInsertBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TReturning extends Record | undefined = undefined, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise : TReturning[]> + implements + RunnableQuery< + TReturning extends undefined ? SingleStoreQueryResultKind : TReturning[], + 'singlestore' + >, + SQLWrapper +{ + static readonly [entityKind]: string = 'SingleStoreInsert'; + + declare protected $table: TTable; + + private config: SingleStoreInsertConfig; + + constructor( + table: TTable, + values: SingleStoreInsertConfig['values'], + ignore: boolean, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) { + super(); + this.config = { table, values, ignore }; + } + + /** + * Adds an `on duplicate key update` clause to the query. + * + * Calling this method will update update the row if any unique index conflicts. SingleStore will automatically determine the conflict target based on the primary key and unique indexes. + * + * See docs: {@link https://orm.drizzle.team/docs/insert#on-duplicate-key-update} + * + * @param config The `set` clause + * + * @example + * ```ts + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW'}) + * .onDuplicateKeyUpdate({ set: { brand: 'Porsche' }}); + * ``` + * + * While SingleStore does not directly support doing nothing on conflict, you can perform a no-op by setting any column's value to itself and achieve the same effect: + * + * ```ts + * import { sql } from 'drizzle-orm'; + * + * await db.insert(cars) + * .values({ id: 1, brand: 'BMW' }) + * .onDuplicateKeyUpdate({ set: { id: sql`id` } }); + * ``` + */ + onDuplicateKeyUpdate( + config: SingleStoreInsertOnDuplicateKeyUpdateConfig, + ): SingleStoreInsertWithout { + const setSql = this.dialect.buildUpdateSet(this.config.table, mapUpdateSet(this.config.table, config.set)); + this.config.onConflict = sql`update ${setSql}`; + return this as any; + } + + $returningId(): SingleStoreInsertWithout< + SingleStoreInsertReturning, + TDynamic, + '$returningId' + > { + const returning: SelectedFieldsOrdered = []; + for (const [key, value] of Object.entries(this.config.table[Table.Symbol.Columns])) { + if (value.primary) { + returning.push({ field: value, path: [key] }); + } + } + this.config.returning = orderSelectedFields(this.config.table[Table.Symbol.Columns]); + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildInsertQuery(this.config).sql; + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreInsertPrepare { + const { sql, generatedIds } = this.dialect.buildInsertQuery(this.config); + return this.session.prepareQuery( + this.dialect.sqlToQuery(sql), + undefined, + undefined, + generatedIds, + this.config.returning, + ) as SingleStoreInsertPrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreInsertDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/optimizeTable.ts b/drizzle-orm/src/singlestore-core/query-builders/optimizeTable.ts new file mode 100644 index 000000000..daecb90e9 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/optimizeTable.ts @@ -0,0 +1,153 @@ +import { entityKind } from '~/entity.ts'; +import type { ColumnBaseConfig, ColumnDataType } from '~/index.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import type { SingleStoreColumn } from '../columns/common.ts'; +import type { SingleStoreTable } from '../table.ts'; +import type { OptimizeTableArgument } from './optimizeTable.types.ts'; + +export type SingleStoreOptimizeTableWithout< + T extends AnySingleStoreOptimizeTableBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T + : Omit< + SingleStoreOptimizeTableBase< + T['_']['table'], + T['_']['arg'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K + >; + +export type SingleStoreOptimizeTable< + TTable extends SingleStoreTable = SingleStoreTable, + TArg extends OptimizeTableArgument = OptimizeTableArgument, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreOptimizeTableBase; + +export interface SingleStoreOptimizeTableConfig { + table: SingleStoreTable; + arg?: OptimizeTableArgument | undefined; + selection?: SingleStoreColumn, object>[] | undefined; +} + +export type SingleStoreOptimizeTablePrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +type SingleStoreOptimizeTableDynamic = SingleStoreOptimizeTable< + T['_']['table'], + T['_']['arg'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +type AnySingleStoreOptimizeTableBase = SingleStoreOptimizeTableBase; + +export interface SingleStoreOptimizeTableBase< + TTable extends SingleStoreTable, + TArg extends OptimizeTableArgument, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise> { + readonly _: { + readonly table: TTable; + readonly arg: TArg | undefined; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreOptimizeTableBase< + TTable extends SingleStoreTable, + TArg extends OptimizeTableArgument, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static readonly [entityKind]: string = 'SingleStoreOptimizeTable'; + + private config: SingleStoreOptimizeTableConfig; + + constructor( + private table: TTable, + private arg: TArg | undefined, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + ) { + super(); + this.config = { table, arg }; + } + + // TODO(singlestore): docs + warmBlobCacheForColumn( + ...selection: SingleStoreColumn, object>[] + ): SingleStoreOptimizeTableWithout { + if (this.config.arg) { + throw new Error('Cannot call warmBlobCacheForColumn with an argument'); + } + this.config.selection = selection; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildOptimizeTable(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreOptimizeTablePrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + undefined, + ) as SingleStoreOptimizeTablePrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreOptimizeTableDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/optimizeTable.types.ts b/drizzle-orm/src/singlestore-core/query-builders/optimizeTable.types.ts new file mode 100644 index 000000000..bb5993ab7 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/optimizeTable.types.ts @@ -0,0 +1,5 @@ +export type OptimizeTableArgument = + | 'FULL' + | 'FLUSH' + | 'FIX_ALTER' + | 'INDEX'; diff --git a/drizzle-orm/src/singlestore-core/query-builders/query-builder.ts b/drizzle-orm/src/singlestore-core/query-builders/query-builder.ts new file mode 100644 index 000000000..b76a0457e --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/query-builder.ts @@ -0,0 +1,107 @@ +import { entityKind } from '~/entity.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { WithSubqueryWithSelection } from '~/singlestore-core/subquery.ts'; +import type { ColumnsSelection } from '~/sql/sql.ts'; +import { WithSubquery } from '~/subquery.ts'; +import { SingleStoreSelectBuilder } from './select.ts'; +import type { SelectedFields } from './select.types.ts'; + +export class QueryBuilder { + static readonly [entityKind]: string = 'SingleStoreQueryBuilder'; + + private dialect: SingleStoreDialect | undefined; + + $with(alias: TAlias) { + const queryBuilder = this; + + return { + as( + qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), + ): WithSubqueryWithSelection { + if (typeof qb === 'function') { + qb = qb(queryBuilder); + } + + return new Proxy( + new WithSubquery(qb.getSQL(), qb.getSelectedFields() as SelectedFields, alias, true), + new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }), + ) as WithSubqueryWithSelection; + }, + }; + } + + with(...queries: WithSubquery[]) { + const self = this; + + function select(): SingleStoreSelectBuilder; + function select( + fields: TSelection, + ): SingleStoreSelectBuilder; + function select( + fields?: TSelection, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: undefined, + dialect: self.getDialect(), + withList: queries, + }); + } + + function selectDistinct(): SingleStoreSelectBuilder; + function selectDistinct( + fields: TSelection, + ): SingleStoreSelectBuilder; + function selectDistinct( + fields?: TSelection, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: undefined, + dialect: self.getDialect(), + withList: queries, + distinct: true, + }); + } + + return { select, selectDistinct }; + } + + select(): SingleStoreSelectBuilder; + select(fields: TSelection): SingleStoreSelectBuilder; + select( + fields?: TSelection, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: undefined, + dialect: this.getDialect(), + }); + } + + selectDistinct(): SingleStoreSelectBuilder; + selectDistinct( + fields: TSelection, + ): SingleStoreSelectBuilder; + selectDistinct( + fields?: TSelection, + ): SingleStoreSelectBuilder { + return new SingleStoreSelectBuilder({ + fields: fields ?? undefined, + session: undefined, + dialect: this.getDialect(), + distinct: true, + }); + } + + // Lazy load dialect to avoid circular dependency + private getDialect() { + if (!this.dialect) { + this.dialect = new SingleStoreDialect(); + } + + return this.dialect; + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/query.ts b/drizzle-orm/src/singlestore-core/query-builders/query.ts new file mode 100644 index 000000000..40d3f05ad --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/query.ts @@ -0,0 +1,157 @@ +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import { + type BuildQueryResult, + type BuildRelationalQueryResult, + type DBQueryConfig, + mapRelationalRow, + type TableRelationalConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import type { Query, QueryWithTypings, SQL } from '~/sql/sql.ts'; +import type { KnownKeysOnly } from '~/utils.ts'; +import type { SingleStoreDialect } from '../dialect.ts'; +import type { + Mode, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreSession, +} from '../session.ts'; +import type { SingleStoreTable } from '../table.ts'; + +export class RelationalQueryBuilder< + TPreparedQueryHKT extends PreparedQueryHKTBase, + TSchema extends TablesRelationalConfig, + TFields extends TableRelationalConfig, +> { + static readonly [entityKind]: string = 'SingleStoreRelationalQueryBuilder'; + + constructor( + private fullSchema: Record, + private schema: TSchema, + private tableNamesMap: Record, + private table: SingleStoreTable, + private tableConfig: TableRelationalConfig, + private dialect: SingleStoreDialect, + private session: SingleStoreSession, + private mode: Mode, + ) {} + + findMany>( + config?: KnownKeysOnly>, + ): SingleStoreRelationalQuery[]> { + return new SingleStoreRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.dialect, + this.session, + config ? (config as DBQueryConfig<'many', true>) : {}, + 'many', + this.mode, + ); + } + + findFirst, 'limit'>>( + config?: KnownKeysOnly, 'limit'>>, + ): SingleStoreRelationalQuery | undefined> { + return new SingleStoreRelationalQuery( + this.fullSchema, + this.schema, + this.tableNamesMap, + this.table, + this.tableConfig, + this.dialect, + this.session, + config ? { ...(config as DBQueryConfig<'many', true> | undefined), limit: 1 } : { limit: 1 }, + 'first', + this.mode, + ); + } +} + +export class SingleStoreRelationalQuery< + TPreparedQueryHKT extends PreparedQueryHKTBase, + TResult, +> extends QueryPromise { + static readonly [entityKind]: string = 'SingleStoreRelationalQuery'; + + declare protected $brand: 'SingleStoreRelationalQuery'; + + constructor( + private fullSchema: Record, + private schema: TablesRelationalConfig, + private tableNamesMap: Record, + private table: SingleStoreTable, + private tableConfig: TableRelationalConfig, + private dialect: SingleStoreDialect, + private session: SingleStoreSession, + private config: DBQueryConfig<'many', true> | true, + private queryMode: 'many' | 'first', + private mode?: Mode, + ) { + super(); + } + + prepare() { + const { query, builtQuery } = this._toSQL(); + return this.session.prepareQuery( + builtQuery, + undefined, + (rawRows) => { + const rows = rawRows.map((row) => mapRelationalRow(this.schema, this.tableConfig, row, query.selection)); + if (this.queryMode === 'first') { + return rows[0] as TResult; + } + return rows as TResult; + }, + ) as PreparedQueryKind; + } + + private _getQuery() { + const query = this.mode === 'planetscale' + ? this.dialect.buildRelationalQueryWithoutLateralSubqueries({ + fullSchema: this.fullSchema, + schema: this.schema, + tableNamesMap: this.tableNamesMap, + table: this.table, + tableConfig: this.tableConfig, + queryConfig: this.config, + tableAlias: this.tableConfig.tsName, + }) + : this.dialect.buildRelationalQuery({ + fullSchema: this.fullSchema, + schema: this.schema, + tableNamesMap: this.tableNamesMap, + table: this.table, + tableConfig: this.tableConfig, + queryConfig: this.config, + tableAlias: this.tableConfig.tsName, + }); + return query; + } + + private _toSQL(): { query: BuildRelationalQueryResult; builtQuery: QueryWithTypings } { + const query = this._getQuery(); + + const builtQuery = this.dialect.sqlToQuery(query.sql as SQL); + + return { builtQuery, query }; + } + + /** @internal */ + getSQL(): SQL { + return this._getQuery().sql as SQL; + } + + toSQL(): Query { + return this._toSQL().builtQuery; + } + + override execute(): Promise { + return this.prepare().execute(); + } +} diff --git a/drizzle-orm/src/singlestore-core/query-builders/select.ts b/drizzle-orm/src/singlestore-core/query-builders/select.ts new file mode 100644 index 000000000..a7652412e --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/select.ts @@ -0,0 +1,1251 @@ +import { entityKind, is } from '~/entity.ts'; +import { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import type { + BuildSubquerySelection, + GetSelectTableName, + GetSelectTableSelection, + JoinNullability, + JoinType, + SelectMode, + SelectResult, + SetOperator, +} from '~/query-builders/select.types.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { SingleStoreColumn } from '~/singlestore-core/columns/index.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + PreparedQueryHKTBase, + SingleStorePreparedQueryConfig, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { SubqueryWithSelection } from '~/singlestore-core/subquery.ts'; +import type { SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { ColumnsSelection, Query } from '~/sql/sql.ts'; +import { SQL, View } from '~/sql/sql.ts'; +import { Subquery } from '~/subquery.ts'; +import { Table } from '~/table.ts'; +import { applyMixins, getTableColumns, getTableLikeName, haveSameKeys, type ValueOrArray } from '~/utils.ts'; +import { orderSelectedFields } from '~/utils.ts'; +import { ViewBaseConfig } from '~/view-common.ts'; +import { SingleStoreViewBase } from '../view-base.ts'; +import type { + AnySingleStoreSelect, + CreateSingleStoreSelectFromBuilderMode, + GetSingleStoreSetOperators, + LockConfig, + LockStrength, + SelectedFields, + SetOperatorRightSelect, + SingleStoreCreateSetOperatorFn, + SingleStoreJoinFn, + SingleStoreSelectConfig, + SingleStoreSelectDynamic, + SingleStoreSelectHKT, + SingleStoreSelectHKTBase, + SingleStoreSelectPrepare, + SingleStoreSelectWithout, + SingleStoreSetOperatorExcludedMethods, + SingleStoreSetOperatorWithResult, +} from './select.types.ts'; + +export class SingleStoreSelectBuilder< + TSelection extends SelectedFields | undefined, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TBuilderMode extends 'db' | 'qb' = 'db', +> { + static readonly [entityKind]: string = 'SingleStoreSelectBuilder'; + + private fields: TSelection; + private session: SingleStoreSession | undefined; + private dialect: SingleStoreDialect; + private withList: Subquery[] = []; + private distinct: boolean | undefined; + + constructor( + config: { + fields: TSelection; + session: SingleStoreSession | undefined; + dialect: SingleStoreDialect; + withList?: Subquery[]; + distinct?: boolean; + }, + ) { + this.fields = config.fields; + this.session = config.session; + this.dialect = config.dialect; + if (config.withList) { + this.withList = config.withList; + } + this.distinct = config.distinct; + } + + from( + source: TFrom, + ): CreateSingleStoreSelectFromBuilderMode< + TBuilderMode, + GetSelectTableName, + TSelection extends undefined ? GetSelectTableSelection : TSelection, + TSelection extends undefined ? 'single' : 'partial', + TPreparedQueryHKT + > { + const isPartialSelect = !!this.fields; + + let fields: SelectedFields; + if (this.fields) { + fields = this.fields; + } else if (is(source, Subquery)) { + // This is required to use the proxy handler to get the correct field values from the subquery + fields = Object.fromEntries( + Object.keys(source._.selectedFields).map(( + key, + ) => [key, source[key as unknown as keyof typeof source] as unknown as SelectedFields[string]]), + ); + } else if (is(source, SingleStoreViewBase)) { + fields = source[ViewBaseConfig].selectedFields as SelectedFields; + } else if (is(source, SQL)) { + fields = {}; + } else { + fields = getTableColumns(source); + } + + return new SingleStoreSelectBase( + { + table: source, + fields, + isPartialSelect, + session: this.session, + dialect: this.dialect, + withList: this.withList, + distinct: this.distinct, + }, + ) as any; + } +} + +export abstract class SingleStoreSelectQueryBuilderBase< + THKT extends SingleStoreSelectHKTBase, + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, +> extends TypedQueryBuilder { + static readonly [entityKind]: string = 'SingleStoreSelectQueryBuilder'; + + override readonly _: { + readonly hkt: THKT; + readonly tableName: TTableName; + readonly selection: TSelection; + readonly selectMode: TSelectMode; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly nullabilityMap: TNullabilityMap; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + readonly result: TResult; + readonly selectedFields: TSelectedFields; + }; + + protected config: SingleStoreSelectConfig; + protected joinsNotNullableMap: Record; + private tableName: string | undefined; + private isPartialSelect: boolean; + /** @internal */ + readonly session: SingleStoreSession | undefined; + protected dialect: SingleStoreDialect; + + constructor( + { table, fields, isPartialSelect, session, dialect, withList, distinct }: { + table: SingleStoreSelectConfig['table']; + fields: SingleStoreSelectConfig['fields']; + isPartialSelect: boolean; + session: SingleStoreSession | undefined; + dialect: SingleStoreDialect; + withList: Subquery[]; + distinct: boolean | undefined; + }, + ) { + super(); + this.config = { + withList, + table, + fields: { ...fields }, + distinct, + setOperators: [], + }; + this.isPartialSelect = isPartialSelect; + this.session = session; + this.dialect = dialect; + this._ = { + selectedFields: fields as TSelectedFields, + } as this['_']; + this.tableName = getTableLikeName(table); + this.joinsNotNullableMap = typeof this.tableName === 'string' ? { [this.tableName]: true } : {}; + } + + private createJoin( + joinType: TJoinType, + ): SingleStoreJoinFn { + return ( + table: SingleStoreTable | Subquery | SingleStoreViewBase | SQL, + on: ((aliases: TSelection) => SQL | undefined) | SQL | undefined, + ) => { + const baseTableName = this.tableName; + const tableName = getTableLikeName(table); + + if (typeof tableName === 'string' && this.config.joins?.some((join) => join.alias === tableName)) { + throw new Error(`Alias "${tableName}" is already used in this query`); + } + + if (!this.isPartialSelect) { + // If this is the first join and this is not a partial select and we're not selecting from raw SQL, "move" the fields from the main table to the nested object + if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === 'string') { + this.config.fields = { + [baseTableName]: this.config.fields, + }; + } + if (typeof tableName === 'string' && !is(table, SQL)) { + const selection = is(table, Subquery) + ? table._.selectedFields + : is(table, View) + ? table[ViewBaseConfig].selectedFields + : table[Table.Symbol.Columns]; + this.config.fields[tableName] = selection; + } + } + + if (typeof on === 'function') { + on = on( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as TSelection, + ); + } + + if (!this.config.joins) { + this.config.joins = []; + } + + this.config.joins.push({ on, table, joinType, alias: tableName }); + + if (typeof tableName === 'string') { + switch (joinType) { + case 'left': { + this.joinsNotNullableMap[tableName] = false; + break; + } + case 'right': { + this.joinsNotNullableMap = Object.fromEntries( + Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false]), + ); + this.joinsNotNullableMap[tableName] = true; + break; + } + case 'inner': { + this.joinsNotNullableMap[tableName] = true; + break; + } + case 'full': { + this.joinsNotNullableMap = Object.fromEntries( + Object.entries(this.joinsNotNullableMap).map(([key]) => [key, false]), + ); + this.joinsNotNullableMap[tableName] = false; + break; + } + } + } + + return this as any; + }; + } + + /** + * Executes a `left join` operation by adding another table to the current query. + * + * Calling this method associates each row of the table with the corresponding row from the joined table, if a match is found. If no matching row exists, it sets all columns of the joined table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#left-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet | null }[] = await db.select() + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .leftJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ + leftJoin = this.createJoin('left'); + + /** + * Executes a `right join` operation by adding another table to the current query. + * + * Calling this method associates each row of the joined table with the corresponding row from the main table, if a match is found. If no matching row exists, it sets all columns of the main table to null. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#right-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet }[] = await db.select() + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .rightJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ + rightJoin = this.createJoin('right'); + + /** + * Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values. + * + * Calling this method retrieves rows that have corresponding entries in both joined tables. Rows without matching entries in either table are excluded, resulting in a table that includes only matching pairs. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#inner-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User; pets: Pet }[] = await db.select() + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number; petId: number }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .innerJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ + innerJoin = this.createJoin('inner'); + + /** + * Executes a `full join` operation by combining rows from two tables into a new table. + * + * Calling this method retrieves all rows from both main and joined tables, merging rows with matching values and filling in `null` for non-matching columns. + * + * See docs: {@link https://orm.drizzle.team/docs/joins#full-join} + * + * @param table the table to join. + * @param on the `on` clause. + * + * @example + * + * ```ts + * // Select all users and their pets + * const usersWithPets: { user: User | null; pets: Pet | null }[] = await db.select() + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * + * // Select userId and petId + * const usersIdsAndPetIds: { userId: number | null; petId: number | null }[] = await db.select({ + * userId: users.id, + * petId: pets.id, + * }) + * .from(users) + * .fullJoin(pets, eq(users.id, pets.ownerId)) + * ``` + */ + fullJoin = this.createJoin('full'); + + private createSetOperator( + type: SetOperator, + isAll: boolean, + ): >( + rightSelection: + | ((setOperators: GetSingleStoreSetOperators) => SetOperatorRightSelect) + | SetOperatorRightSelect, + ) => SingleStoreSelectWithout< + this, + TDynamic, + SingleStoreSetOperatorExcludedMethods, + true + > { + return (rightSelection) => { + const rightSelect = (typeof rightSelection === 'function' + ? rightSelection(getSingleStoreSetOperators()) + : rightSelection) as TypedQueryBuilder< + any, + TResult + >; + + if (!haveSameKeys(this.getSelectedFields(), rightSelect.getSelectedFields())) { + throw new Error( + 'Set operator error (union / intersect / except): selected fields are not the same or are in a different order', + ); + } + + this.config.setOperators.push({ type, isAll, rightSelect }); + return this as any; + }; + } + + /** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * import { union } from 'drizzle-orm/singlestore-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ + union = this.createSetOperator('union', false); + + /** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * import { unionAll } from 'drizzle-orm/singlestore-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ + unionAll = this.createSetOperator('union', true); + + /** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { intersect } from 'drizzle-orm/singlestore-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ + intersect = this.createSetOperator('intersect', false); + + /** + * Adds `intersect all` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets including all duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all} + * + * @example + * + * ```ts + * // Select all products and quantities that are ordered by both regular and VIP customers + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders) + * .intersectAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * import { intersectAll } from 'drizzle-orm/pg-core' + * + * await intersectAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ + intersectAll = this.createSetOperator('intersect', true); + + /** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { except } from 'drizzle-orm/singlestore-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ + except = this.createSetOperator('except', false); + + /** + * Adds `except all` set operator to the query. + * + * Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all} + * + * @example + * + * ```ts + * // Select all products that are ordered by regular customers but not by VIP customers + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered, + * }) + * .from(regularCustomerOrders) + * .exceptAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered, + * }) + * .from(vipCustomerOrders) + * ); + * // or + * import { exceptAll } from 'drizzle-orm/pg-core' + * + * await exceptAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ + exceptAll = this.createSetOperator('except', true); + + /** + * Adds `minus` set operator to the query. + * + * This is an alias of `except` supported by SingleStore. + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .minus( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * import { minus } from 'drizzle-orm/singlestore-core' + * + * await minus( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ + minus = this.createSetOperator('except', false); + + /** @internal */ + addSetOperators(setOperators: SingleStoreSelectConfig['setOperators']): SingleStoreSelectWithout< + this, + TDynamic, + SingleStoreSetOperatorExcludedMethods, + true + > { + this.config.setOperators.push(...setOperators); + return this as any; + } + + /** + * Adds a `where` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#filtering} + * + * @param where the `where` clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be selected. + * + * ```ts + * // Select all cars with green color + * await db.select().from(cars).where(eq(cars.color, 'green')); + * // or + * await db.select().from(cars).where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Select all BMW cars with a green color + * await db.select().from(cars).where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Select all cars with the green or blue color + * await db.select().from(cars).where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + where( + where: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, + ): SingleStoreSelectWithout { + if (typeof where === 'function') { + where = where( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as TSelection, + ); + } + this.config.where = where; + return this as any; + } + + /** + * Adds a `having` clause to the query. + * + * Calling this method will select only those rows that fulfill a specified condition. It is typically used with aggregate functions to filter the aggregated data based on a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @param having the `having` clause. + * + * @example + * + * ```ts + * // Select all brands with more than one car + * await db.select({ + * brand: cars.brand, + * count: sql`cast(count(${cars.id}) as int)`, + * }) + * .from(cars) + * .groupBy(cars.brand) + * .having(({ count }) => gt(count, 1)); + * ``` + */ + having( + having: ((aliases: this['_']['selection']) => SQL | undefined) | SQL | undefined, + ): SingleStoreSelectWithout { + if (typeof having === 'function') { + having = having( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'sql', sqlBehavior: 'sql' }), + ) as TSelection, + ); + } + this.config.having = having; + return this as any; + } + + /** + * Adds a `group by` clause to the query. + * + * Calling this method will group rows that have the same values into summary rows, often used for aggregation purposes. + * + * See docs: {@link https://orm.drizzle.team/docs/select#aggregations} + * + * @example + * + * ```ts + * // Group and count people by their last names + * await db.select({ + * lastName: people.lastName, + * count: sql`cast(count(*) as int)` + * }) + * .from(people) + * .groupBy(people.lastName); + * ``` + */ + groupBy( + builder: (aliases: this['_']['selection']) => ValueOrArray, + ): SingleStoreSelectWithout; + groupBy(...columns: (SingleStoreColumn | SQL | SQL.Aliased)[]): SingleStoreSelectWithout; + groupBy( + ...columns: + | [(aliases: this['_']['selection']) => ValueOrArray] + | (SingleStoreColumn | SQL | SQL.Aliased)[] + ): SingleStoreSelectWithout { + if (typeof columns[0] === 'function') { + const groupBy = columns[0]( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as TSelection, + ); + this.config.groupBy = Array.isArray(groupBy) ? groupBy : [groupBy]; + } else { + this.config.groupBy = columns as (SingleStoreColumn | SQL | SQL.Aliased)[]; + } + return this as any; + } + + /** + * Adds an `order by` clause to the query. + * + * Calling this method will sort the result-set in ascending or descending order. By default, the sort order is ascending. + * + * See docs: {@link https://orm.drizzle.team/docs/select#order-by} + * + * @example + * + * ``` + * // Select cars ordered by year + * await db.select().from(cars).orderBy(cars.year); + * ``` + * + * You can specify whether results are in ascending or descending order with the `asc()` and `desc()` operators. + * + * ```ts + * // Select cars ordered by year in descending order + * await db.select().from(cars).orderBy(desc(cars.year)); + * + * // Select cars ordered by year and price + * await db.select().from(cars).orderBy(asc(cars.year), desc(cars.price)); + * ``` + */ + orderBy( + builder: (aliases: this['_']['selection']) => ValueOrArray, + ): SingleStoreSelectWithout; + orderBy(...columns: (SingleStoreColumn | SQL | SQL.Aliased)[]): SingleStoreSelectWithout; + orderBy( + ...columns: + | [(aliases: this['_']['selection']) => ValueOrArray] + | (SingleStoreColumn | SQL | SQL.Aliased)[] + ): SingleStoreSelectWithout { + if (typeof columns[0] === 'function') { + const orderBy = columns[0]( + new Proxy( + this.config.fields, + new SelectionProxyHandler({ sqlAliasedBehavior: 'alias', sqlBehavior: 'sql' }), + ) as TSelection, + ); + + const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; + + if (this.config.setOperators.length > 0) { + this.config.setOperators.at(-1)!.orderBy = orderByArray; + } else { + this.config.orderBy = orderByArray; + } + } else { + const orderByArray = columns as (SingleStoreColumn | SQL | SQL.Aliased)[]; + + if (this.config.setOperators.length > 0) { + this.config.setOperators.at(-1)!.orderBy = orderByArray; + } else { + this.config.orderBy = orderByArray; + } + } + return this as any; + } + + /** + * Adds a `limit` clause to the query. + * + * Calling this method will set the maximum number of rows that will be returned by this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param limit the `limit` clause. + * + * @example + * + * ```ts + * // Get the first 10 people from this query. + * await db.select().from(people).limit(10); + * ``` + */ + limit(limit: number): SingleStoreSelectWithout { + if (this.config.setOperators.length > 0) { + this.config.setOperators.at(-1)!.limit = limit; + } else { + this.config.limit = limit; + } + return this as any; + } + + /** + * Adds an `offset` clause to the query. + * + * Calling this method will skip a number of rows when returning results from this query. + * + * See docs: {@link https://orm.drizzle.team/docs/select#limit--offset} + * + * @param offset the `offset` clause. + * + * @example + * + * ```ts + * // Get the 10th-20th people from this query. + * await db.select().from(people).offset(10).limit(10); + * ``` + */ + offset(offset: number): SingleStoreSelectWithout { + if (this.config.setOperators.length > 0) { + this.config.setOperators.at(-1)!.offset = offset; + } else { + this.config.offset = offset; + } + return this as any; + } + + /** + * Adds a `for` clause to the query. + * + * Calling this method will specify a lock strength for this query that controls how strictly it acquires exclusive access to the rows being queried. + * + * See docs: {@link https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html} + * TODO(singlestore) + * + * @param strength the lock strength. + * @param config the lock configuration. + */ + for(strength: LockStrength, config: LockConfig = {}): SingleStoreSelectWithout { + this.config.lockingClause = { strength, config }; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildSelectQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + as( + alias: TAlias, + ): SubqueryWithSelection { + return new Proxy( + new Subquery(this.getSQL(), this.config.fields, alias), + new SelectionProxyHandler({ alias, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }), + ) as SubqueryWithSelection; + } + + /** @internal */ + override getSelectedFields(): this['_']['selectedFields'] { + return new Proxy( + this.config.fields, + new SelectionProxyHandler({ alias: this.tableName, sqlAliasedBehavior: 'alias', sqlBehavior: 'error' }), + ) as this['_']['selectedFields']; + } + + $dynamic(): SingleStoreSelectDynamic { + return this as any; + } +} + +export interface SingleStoreSelectBase< + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, +> extends + SingleStoreSelectQueryBuilderBase< + SingleStoreSelectHKT, + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields + >, + QueryPromise +{} + +export class SingleStoreSelectBase< + TTableName extends string | undefined, + TSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult = SelectResult[], + TSelectedFields = BuildSubquerySelection, +> extends SingleStoreSelectQueryBuilderBase< + SingleStoreSelectHKT, + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields +> { + static readonly [entityKind]: string = 'SingleStoreSelect'; + + prepare(): SingleStoreSelectPrepare { + if (!this.session) { + throw new Error('Cannot execute a query on a query builder. Please use a database instance instead.'); + } + const fieldsList = orderSelectedFields(this.config.fields); + const query = this.session.prepareQuery< + SingleStorePreparedQueryConfig & { execute: SelectResult[] }, + TPreparedQueryHKT + >(this.dialect.sqlToQuery(this.getSQL()), fieldsList); + query.joinsNotNullableMap = this.joinsNotNullableMap; + return query as SingleStoreSelectPrepare; + } + + execute = ((placeholderValues) => { + return this.prepare().execute(placeholderValues); + }) as ReturnType['execute']; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); +} + +applyMixins(SingleStoreSelectBase, [QueryPromise]); + +function createSetOperator(type: SetOperator, isAll: boolean): SingleStoreCreateSetOperatorFn { + return (leftSelect, rightSelect, ...restSelects) => { + const setOperators = [rightSelect, ...restSelects].map((select) => ({ + type, + isAll, + rightSelect: select as AnySingleStoreSelect, + })); + + for (const setOperator of setOperators) { + if (!haveSameKeys((leftSelect as any).getSelectedFields(), setOperator.rightSelect.getSelectedFields())) { + throw new Error( + 'Set operator error (union / intersect / except): selected fields are not the same or are in a different order', + ); + } + } + + return (leftSelect as AnySingleStoreSelect).addSetOperators(setOperators) as any; + }; +} + +const getSingleStoreSetOperators = () => ({ + union, + unionAll, + intersect, + intersectAll, + except, + exceptAll, + minus, +}); + +/** + * Adds `union` set operator to the query. + * + * Calling this method will combine the result sets of the `select` statements and remove any duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union} + * + * @example + * + * ```ts + * // Select all unique names from customers and users tables + * import { union } from 'drizzle-orm/singlestore-core' + * + * await union( + * db.select({ name: users.name }).from(users), + * db.select({ name: customers.name }).from(customers) + * ); + * // or + * await db.select({ name: users.name }) + * .from(users) + * .union( + * db.select({ name: customers.name }).from(customers) + * ); + * ``` + */ +export const union = createSetOperator('union', false); + +/** + * Adds `union all` set operator to the query. + * + * Calling this method will combine the result-set of the `select` statements and keep all duplicate rows that appear across them. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#union-all} + * + * @example + * + * ```ts + * // Select all transaction ids from both online and in-store sales + * import { unionAll } from 'drizzle-orm/singlestore-core' + * + * await unionAll( + * db.select({ transaction: onlineSales.transactionId }).from(onlineSales), + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * // or + * await db.select({ transaction: onlineSales.transactionId }) + * .from(onlineSales) + * .unionAll( + * db.select({ transaction: inStoreSales.transactionId }).from(inStoreSales) + * ); + * ``` + */ +export const unionAll = createSetOperator('union', true); + +/** + * Adds `intersect` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets and eliminate duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect} + * + * @example + * + * ```ts + * // Select course names that are offered in both departments A and B + * import { intersect } from 'drizzle-orm/singlestore-core' + * + * await intersect( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .intersect( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ +export const intersect = createSetOperator('intersect', false); + +/** + * Adds `intersect all` set operator to the query. + * + * Calling this method will retain only the rows that are present in both result sets including all duplicates. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#intersect-all} + * + * @example + * + * ```ts + * // Select all products and quantities that are ordered by both regular and VIP customers + * import { intersectAll } from 'drizzle-orm/mysql-core' + * + * await intersectAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders) + * .intersectAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ +export const intersectAll = createSetOperator('intersect', true); + +/** + * Adds `except` set operator to the query. + * + * Calling this method will retrieve all unique rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except} + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * import { except } from 'drizzle-orm/singlestore-core' + * + * await except( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .except( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ +export const except = createSetOperator('except', false); + +/** + * Adds `except all` set operator to the query. + * + * Calling this method will retrieve all rows from the left query, except for the rows that are present in the result set of the right query. + * + * See docs: {@link https://orm.drizzle.team/docs/set-operations#except-all} + * + * @example + * + * ```ts + * // Select all products that are ordered by regular customers but not by VIP customers + * import { exceptAll } from 'drizzle-orm/mysql-core' + * + * await exceptAll( + * db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered + * }) + * .from(regularCustomerOrders), + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered + * }) + * .from(vipCustomerOrders) + * ); + * // or + * await db.select({ + * productId: regularCustomerOrders.productId, + * quantityOrdered: regularCustomerOrders.quantityOrdered, + * }) + * .from(regularCustomerOrders) + * .exceptAll( + * db.select({ + * productId: vipCustomerOrders.productId, + * quantityOrdered: vipCustomerOrders.quantityOrdered, + * }) + * .from(vipCustomerOrders) + * ); + * ``` + */ +export const exceptAll = createSetOperator('except', true); + +/** + * Adds `minus` set operator to the query. + * + * This is an alias of `except` supported by SingleStore. + * + * @example + * + * ```ts + * // Select all courses offered in department A but not in department B + * import { minus } from 'drizzle-orm/singlestore-core' + * + * await minus( + * db.select({ courseName: depA.courseName }).from(depA), + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * // or + * await db.select({ courseName: depA.courseName }) + * .from(depA) + * .minus( + * db.select({ courseName: depB.courseName }).from(depB) + * ); + * ``` + */ +export const minus = createSetOperator('except', true); diff --git a/drizzle-orm/src/singlestore-core/query-builders/select.types.ts b/drizzle-orm/src/singlestore-core/query-builders/select.types.ts new file mode 100644 index 000000000..6db1cc357 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/select.types.ts @@ -0,0 +1,457 @@ +import type { + SelectedFields as SelectedFieldsBase, + SelectedFieldsFlat as SelectedFieldsFlatBase, + SelectedFieldsOrdered as SelectedFieldsOrderedBase, +} from '~/operations.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import type { + AppendToNullabilityMap, + AppendToResult, + BuildSubquerySelection, + GetSelectTableName, + JoinNullability, + JoinType, + MapColumnsToTableAlias, + SelectMode, + SelectResult, + SetOperator, +} from '~/query-builders/select.types.ts'; +import type { SingleStoreColumn } from '~/singlestore-core/columns/index.ts'; +import type { SingleStoreTable, SingleStoreTableWithColumns } from '~/singlestore-core/table.ts'; +import type { ColumnsSelection, Placeholder, SQL, View } from '~/sql/sql.ts'; +import type { Subquery } from '~/subquery.ts'; +import type { Table, UpdateTableConfig } from '~/table.ts'; +import type { Assume, ValidateShape } from '~/utils.ts'; +import type { PreparedQueryHKTBase, PreparedQueryKind, SingleStorePreparedQueryConfig } from '../session.ts'; +import type { SingleStoreViewBase } from '../view-base.ts'; +import type { SingleStoreViewWithSelection } from '../view.ts'; +import type { SingleStoreSelectBase, SingleStoreSelectQueryBuilderBase } from './select.ts'; + +export interface SingleStoreSelectJoinConfig { + on: SQL | undefined; + table: SingleStoreTable | Subquery | SingleStoreViewBase | SQL; + alias: string | undefined; + joinType: JoinType; + lateral?: boolean; +} + +export type BuildAliasTable = TTable extends Table + ? SingleStoreTableWithColumns< + UpdateTableConfig; + }> + > + : TTable extends View ? SingleStoreViewWithSelection< + TAlias, + TTable['_']['existing'], + MapColumnsToTableAlias + > + : never; + +export interface SingleStoreSelectConfig { + withList?: Subquery[]; + fields: Record; + fieldsFlat?: SelectedFieldsOrdered; + where?: SQL; + having?: SQL; + table: SingleStoreTable | Subquery | SingleStoreViewBase | SQL; + limit?: number | Placeholder; + offset?: number | Placeholder; + joins?: SingleStoreSelectJoinConfig[]; + orderBy?: (SingleStoreColumn | SQL | SQL.Aliased)[]; + groupBy?: (SingleStoreColumn | SQL | SQL.Aliased)[]; + lockingClause?: { + strength: LockStrength; + config: LockConfig; + }; + distinct?: boolean; + setOperators: { + rightSelect: TypedQueryBuilder; + type: SetOperator; + isAll: boolean; + orderBy?: (SingleStoreColumn | SQL | SQL.Aliased)[]; + limit?: number | Placeholder; + offset?: number | Placeholder; + }[]; +} + +export type SingleStoreJoin< + T extends AnySingleStoreSelectQueryBuilder, + TDynamic extends boolean, + TJoinType extends JoinType, + TJoinedTable extends SingleStoreTable | Subquery | SingleStoreViewBase | SQL, + TJoinedName extends GetSelectTableName = GetSelectTableName, +> = T extends any ? SingleStoreSelectWithout< + SingleStoreSelectKind< + T['_']['hkt'], + T['_']['tableName'], + AppendToResult< + T['_']['tableName'], + T['_']['selection'], + TJoinedName, + TJoinedTable extends SingleStoreTable ? TJoinedTable['_']['columns'] + : TJoinedTable extends Subquery ? Assume + : never, + T['_']['selectMode'] + >, + T['_']['selectMode'] extends 'partial' ? T['_']['selectMode'] : 'multiple', + T['_']['preparedQueryHKT'], + AppendToNullabilityMap, + TDynamic, + T['_']['excludedMethods'] + >, + TDynamic, + T['_']['excludedMethods'] + > + : never; + +export type SingleStoreJoinFn< + T extends AnySingleStoreSelectQueryBuilder, + TDynamic extends boolean, + TJoinType extends JoinType, +> = < + TJoinedTable extends SingleStoreTable | Subquery | SingleStoreViewBase | SQL, + TJoinedName extends GetSelectTableName = GetSelectTableName, +>( + table: TJoinedTable, + on: ((aliases: T['_']['selection']) => SQL | undefined) | SQL | undefined, +) => SingleStoreJoin; + +export type SelectedFieldsFlat = SelectedFieldsFlatBase; + +export type SelectedFields = SelectedFieldsBase; + +export type SelectedFieldsOrdered = SelectedFieldsOrderedBase; + +export type LockStrength = 'update' | 'share'; + +export type LockConfig = { + noWait: true; + skipLocked?: undefined; +} | { + noWait?: undefined; + skipLocked: true; +} | { + noWait?: undefined; + skipLocked?: undefined; +}; + +export interface SingleStoreSelectHKTBase { + tableName: string | undefined; + selection: unknown; + selectMode: SelectMode; + preparedQueryHKT: unknown; + nullabilityMap: unknown; + dynamic: boolean; + excludedMethods: string; + result: unknown; + selectedFields: unknown; + _type: unknown; +} + +export type SingleStoreSelectKind< + T extends SingleStoreSelectHKTBase, + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TNullabilityMap extends Record, + TDynamic extends boolean, + TExcludedMethods extends string, + TResult = SelectResult[], + TSelectedFields = BuildSubquerySelection, +> = (T & { + tableName: TTableName; + selection: TSelection; + selectMode: TSelectMode; + preparedQueryHKT: TPreparedQueryHKT; + nullabilityMap: TNullabilityMap; + dynamic: TDynamic; + excludedMethods: TExcludedMethods; + result: TResult; + selectedFields: TSelectedFields; +})['_type']; + +export interface SingleStoreSelectQueryBuilderHKT extends SingleStoreSelectHKTBase { + _type: SingleStoreSelectQueryBuilderBase< + SingleStoreSelectQueryBuilderHKT, + this['tableName'], + Assume, + this['selectMode'], + Assume, + Assume>, + this['dynamic'], + this['excludedMethods'], + Assume, + Assume + >; +} + +export interface SingleStoreSelectHKT extends SingleStoreSelectHKTBase { + _type: SingleStoreSelectBase< + this['tableName'], + Assume, + this['selectMode'], + Assume, + Assume>, + this['dynamic'], + this['excludedMethods'], + Assume, + Assume + >; +} + +export type SingleStoreSetOperatorExcludedMethods = + | 'where' + | 'having' + | 'groupBy' + | 'session' + | 'leftJoin' + | 'rightJoin' + | 'innerJoin' + | 'fullJoin' + | 'for'; + +export type SingleStoreSelectWithout< + T extends AnySingleStoreSelectQueryBuilder, + TDynamic extends boolean, + K extends keyof T & string, + TResetExcluded extends boolean = false, +> = TDynamic extends true ? T : Omit< + SingleStoreSelectKind< + T['_']['hkt'], + T['_']['tableName'], + T['_']['selection'], + T['_']['selectMode'], + T['_']['preparedQueryHKT'], + T['_']['nullabilityMap'], + TDynamic, + TResetExcluded extends true ? K : T['_']['excludedMethods'] | K, + T['_']['result'], + T['_']['selectedFields'] + >, + TResetExcluded extends true ? K : T['_']['excludedMethods'] | K +>; + +export type SingleStoreSelectPrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: T['_']['result']; + iterator: T['_']['result'][number]; + }, + true +>; + +export type SingleStoreSelectDynamic = SingleStoreSelectKind< + T['_']['hkt'], + T['_']['tableName'], + T['_']['selection'], + T['_']['selectMode'], + T['_']['preparedQueryHKT'], + T['_']['nullabilityMap'], + true, + never, + T['_']['result'], + T['_']['selectedFields'] +>; + +export type CreateSingleStoreSelectFromBuilderMode< + TBuilderMode extends 'db' | 'qb', + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase, +> = TBuilderMode extends 'db' ? SingleStoreSelectBase + : SingleStoreSelectQueryBuilderBase< + SingleStoreSelectQueryBuilderHKT, + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT + >; + +export type SingleStoreSelectQueryBuilder< + THKT extends SingleStoreSelectHKTBase = SingleStoreSelectQueryBuilderHKT, + TTableName extends string | undefined = string | undefined, + TSelection extends ColumnsSelection = ColumnsSelection, + TSelectMode extends SelectMode = SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TNullabilityMap extends Record = Record, + TResult extends any[] = unknown[], + TSelectedFields extends ColumnsSelection = ColumnsSelection, +> = SingleStoreSelectQueryBuilderBase< + THKT, + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + true, + never, + TResult, + TSelectedFields +>; + +export type AnySingleStoreSelectQueryBuilder = SingleStoreSelectQueryBuilderBase< + any, + any, + any, + any, + any, + any, + any, + any, + any +>; + +export type AnySingleStoreSetOperatorInterface = SingleStoreSetOperatorInterface< + any, + any, + any, + any, + any, + any, + any, + any, + any +>; + +export interface SingleStoreSetOperatorInterface< + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, +> { + _: { + readonly hkt: SingleStoreSelectHKT; + readonly tableName: TTableName; + readonly selection: TSelection; + readonly selectMode: TSelectMode; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly nullabilityMap: TNullabilityMap; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + readonly result: TResult; + readonly selectedFields: TSelectedFields; + }; +} + +export type SingleStoreSetOperatorWithResult = SingleStoreSetOperatorInterface< + any, + any, + any, + any, + any, + any, + any, + TResult, + any +>; + +export type SingleStoreSelect< + TTableName extends string | undefined = string | undefined, + TSelection extends ColumnsSelection = Record, + TSelectMode extends SelectMode = SelectMode, + TNullabilityMap extends Record = Record, +> = SingleStoreSelectBase; + +export type AnySingleStoreSelect = SingleStoreSelectBase; + +export type SingleStoreSetOperator< + TTableName extends string | undefined = string | undefined, + TSelection extends ColumnsSelection = Record, + TSelectMode extends SelectMode = SelectMode, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TNullabilityMap extends Record = Record, +> = SingleStoreSelectBase< + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + true, + SingleStoreSetOperatorExcludedMethods +>; + +export type SetOperatorRightSelect< + TValue extends SingleStoreSetOperatorWithResult, + TResult extends any[], +> = TValue extends SingleStoreSetOperatorInterface + ? ValidateShape< + TValueResult[number], + TResult[number], + TypedQueryBuilder + > + : TValue; + +export type SetOperatorRestSelect< + TValue extends readonly SingleStoreSetOperatorWithResult[], + TResult extends any[], +> = TValue extends [infer First, ...infer Rest] + ? First extends SingleStoreSetOperatorInterface + ? Rest extends AnySingleStoreSetOperatorInterface[] ? [ + ValidateShape>, + ...SetOperatorRestSelect, + ] + : ValidateShape[]> + : never + : TValue; + +export type SingleStoreCreateSetOperatorFn = < + TTableName extends string | undefined, + TSelection extends ColumnsSelection, + TSelectMode extends SelectMode, + TValue extends SingleStoreSetOperatorWithResult, + TRest extends SingleStoreSetOperatorWithResult[], + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TNullabilityMap extends Record = TTableName extends string ? Record + : {}, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, + TResult extends any[] = SelectResult[], + TSelectedFields extends ColumnsSelection = BuildSubquerySelection, +>( + leftSelect: SingleStoreSetOperatorInterface< + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields + >, + rightSelect: SetOperatorRightSelect, + ...restSelects: SetOperatorRestSelect +) => SingleStoreSelectWithout< + SingleStoreSelectBase< + TTableName, + TSelection, + TSelectMode, + TPreparedQueryHKT, + TNullabilityMap, + TDynamic, + TExcludedMethods, + TResult, + TSelectedFields + >, + false, + SingleStoreSetOperatorExcludedMethods, + true +>; + +export type GetSingleStoreSetOperators = { + union: SingleStoreCreateSetOperatorFn; + intersect: SingleStoreCreateSetOperatorFn; + except: SingleStoreCreateSetOperatorFn; + unionAll: SingleStoreCreateSetOperatorFn; + minus: SingleStoreCreateSetOperatorFn; +}; diff --git a/drizzle-orm/src/singlestore-core/query-builders/update.ts b/drizzle-orm/src/singlestore-core/query-builders/update.ts new file mode 100644 index 000000000..d26394dfe --- /dev/null +++ b/drizzle-orm/src/singlestore-core/query-builders/update.ts @@ -0,0 +1,215 @@ +import type { GetColumnData } from '~/column.ts'; +import { entityKind } from '~/entity.ts'; +import { QueryPromise } from '~/query-promise.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { + AnySingleStoreQueryResultHKT, + PreparedQueryHKTBase, + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStoreQueryResultHKT, + SingleStoreQueryResultKind, + SingleStoreSession, +} from '~/singlestore-core/session.ts'; +import type { SingleStoreTable } from '~/singlestore-core/table.ts'; +import type { Query, SQL, SQLWrapper } from '~/sql/sql.ts'; +import type { Subquery } from '~/subquery.ts'; +import { mapUpdateSet, type UpdateSet } from '~/utils.ts'; +import type { SelectedFieldsOrdered } from './select.types.ts'; + +export interface SingleStoreUpdateConfig { + where?: SQL | undefined; + set: UpdateSet; + table: SingleStoreTable; + returning?: SelectedFieldsOrdered; + withList?: Subquery[]; +} + +export type SingleStoreUpdateSetSource = + & { + [Key in keyof TTable['$inferInsert']]?: + | GetColumnData + | SQL; + } + & {}; + +export class SingleStoreUpdateBuilder< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, +> { + static readonly [entityKind]: string = 'SingleStoreUpdateBuilder'; + + declare readonly _: { + readonly table: TTable; + }; + + constructor( + private table: TTable, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + private withList?: Subquery[], + ) {} + + set(values: SingleStoreUpdateSetSource): SingleStoreUpdateBase { + return new SingleStoreUpdateBase( + this.table, + mapUpdateSet(this.table, values), + this.session, + this.dialect, + this.withList, + ); + } +} + +export type SingleStoreUpdateWithout< + T extends AnySingleStoreUpdateBase, + TDynamic extends boolean, + K extends keyof T & string, +> = TDynamic extends true ? T : Omit< + SingleStoreUpdateBase< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'], + TDynamic, + T['_']['excludedMethods'] | K + >, + T['_']['excludedMethods'] | K +>; + +export type SingleStoreUpdatePrepare = PreparedQueryKind< + T['_']['preparedQueryHKT'], + SingleStorePreparedQueryConfig & { + execute: SingleStoreQueryResultKind; + iterator: never; + }, + true +>; + +export type SingleStoreUpdateDynamic = SingleStoreUpdate< + T['_']['table'], + T['_']['queryResult'], + T['_']['preparedQueryHKT'] +>; + +export type SingleStoreUpdate< + TTable extends SingleStoreTable = SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT = AnySingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, +> = SingleStoreUpdateBase; + +export type AnySingleStoreUpdateBase = SingleStoreUpdateBase; + +export interface SingleStoreUpdateBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TDynamic extends boolean = false, + TExcludedMethods extends string = never, +> extends QueryPromise>, SQLWrapper { + readonly _: { + readonly table: TTable; + readonly queryResult: TQueryResult; + readonly preparedQueryHKT: TPreparedQueryHKT; + readonly dynamic: TDynamic; + readonly excludedMethods: TExcludedMethods; + }; +} + +export class SingleStoreUpdateBase< + TTable extends SingleStoreTable, + TQueryResult extends SingleStoreQueryResultHKT, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TPreparedQueryHKT extends PreparedQueryHKTBase, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TDynamic extends boolean = false, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + TExcludedMethods extends string = never, +> extends QueryPromise> implements SQLWrapper { + static readonly [entityKind]: string = 'SingleStoreUpdate'; + + private config: SingleStoreUpdateConfig; + + constructor( + table: TTable, + set: UpdateSet, + private session: SingleStoreSession, + private dialect: SingleStoreDialect, + withList?: Subquery[], + ) { + super(); + this.config = { set, table, withList }; + } + + /** + * Adds a 'where' clause to the query. + * + * Calling this method will update only those rows that fulfill a specified condition. + * + * See docs: {@link https://orm.drizzle.team/docs/update} + * + * @param where the 'where' clause. + * + * @example + * You can use conditional operators and `sql function` to filter the rows to be updated. + * + * ```ts + * // Update all cars with green color + * db.update(cars).set({ color: 'red' }) + * .where(eq(cars.color, 'green')); + * // or + * db.update(cars).set({ color: 'red' }) + * .where(sql`${cars.color} = 'green'`) + * ``` + * + * You can logically combine conditional operators with `and()` and `or()` operators: + * + * ```ts + * // Update all BMW cars with a green color + * db.update(cars).set({ color: 'red' }) + * .where(and(eq(cars.color, 'green'), eq(cars.brand, 'BMW'))); + * + * // Update all cars with the green or blue color + * db.update(cars).set({ color: 'red' }) + * .where(or(eq(cars.color, 'green'), eq(cars.color, 'blue'))); + * ``` + */ + where(where: SQL | undefined): SingleStoreUpdateWithout { + this.config.where = where; + return this as any; + } + + /** @internal */ + getSQL(): SQL { + return this.dialect.buildUpdateQuery(this.config); + } + + toSQL(): Query { + const { typings: _typings, ...rest } = this.dialect.sqlToQuery(this.getSQL()); + return rest; + } + + prepare(): SingleStoreUpdatePrepare { + return this.session.prepareQuery( + this.dialect.sqlToQuery(this.getSQL()), + this.config.returning, + ) as SingleStoreUpdatePrepare; + } + + override execute: ReturnType['execute'] = (placeholderValues) => { + return this.prepare().execute(placeholderValues); + }; + + private createIterator = (): ReturnType['iterator'] => { + const self = this; + return async function*(placeholderValues) { + yield* self.prepare().iterator(placeholderValues); + }; + }; + + iterator = this.createIterator(); + + $dynamic(): SingleStoreUpdateDynamic { + return this as any; + } +} diff --git a/drizzle-orm/src/singlestore-core/schema.ts b/drizzle-orm/src/singlestore-core/schema.ts new file mode 100644 index 000000000..82da44a49 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/schema.ts @@ -0,0 +1,41 @@ +import { entityKind, is } from '~/entity.ts'; +import { type SingleStoreTableFn, singlestoreTableWithSchema } from './table.ts'; +import { type singlestoreView, singlestoreViewWithSchema } from './view.ts'; + +export class SingleStoreSchema { + static readonly [entityKind]: string = 'SingleStoreSchema'; + + constructor( + public readonly schemaName: TName, + ) {} + + table: SingleStoreTableFn = (name, columns, extraConfig) => { + return singlestoreTableWithSchema(name, columns, extraConfig, this.schemaName); + }; + + view = ((name, columns) => { + return singlestoreViewWithSchema(name, columns, this.schemaName); + }) as typeof singlestoreView; +} + +/** @deprecated - use `instanceof SingleStoreSchema` */ +export function isSingleStoreSchema(obj: unknown): obj is SingleStoreSchema { + return is(obj, SingleStoreSchema); +} + +/** + * Create a SingleStore schema. + * https://dev.mysql.com/doc/refman/8.0/en/create-database.html + * TODO(singlestore) + * + * @param name singlestore use schema name + * @returns SingleStore schema + */ +export function singlestoreDatabase(name: TName) { + return new SingleStoreSchema(name); +} + +/** + * @see singlestoreDatabase + */ +export const singlestoreSchema = singlestoreDatabase; diff --git a/drizzle-orm/src/singlestore-core/session.ts b/drizzle-orm/src/singlestore-core/session.ts new file mode 100644 index 000000000..b95589a45 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/session.ts @@ -0,0 +1,152 @@ +import { entityKind } from '~/entity.ts'; +import { TransactionRollbackError } from '~/errors.ts'; +import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import { type Query, type SQL, sql } from '~/sql/sql.ts'; +import type { Assume, Equal } from '~/utils.ts'; +import { SingleStoreDatabase } from './db.ts'; +import type { SingleStoreDialect } from './dialect.ts'; +import type { SelectedFieldsOrdered } from './query-builders/select.types.ts'; + +export type Mode = 'default' | 'planetscale'; + +export interface SingleStoreQueryResultHKT { + readonly $brand: 'SingleStoreQueryResultHKT'; + readonly row: unknown; + readonly type: unknown; +} + +export interface AnySingleStoreQueryResultHKT extends SingleStoreQueryResultHKT { + readonly type: any; +} + +export type SingleStoreQueryResultKind = (TKind & { + readonly row: TRow; +})['type']; + +export interface SingleStorePreparedQueryConfig { + execute: unknown; + iterator: unknown; +} + +export interface SingleStorePreparedQueryHKT { + readonly $brand: 'SingleStorePreparedQueryHKT'; + readonly config: unknown; + readonly type: unknown; +} + +export type PreparedQueryKind< + TKind extends SingleStorePreparedQueryHKT, + TConfig extends SingleStorePreparedQueryConfig, + TAssume extends boolean = false, +> = Equal extends true + ? Assume<(TKind & { readonly config: TConfig })['type'], SingleStorePreparedQuery> + : (TKind & { readonly config: TConfig })['type']; + +export abstract class SingleStorePreparedQuery { + static readonly [entityKind]: string = 'SingleStorePreparedQuery'; + + /** @internal */ + joinsNotNullableMap?: Record; + + abstract execute(placeholderValues?: Record): Promise; + + abstract iterator(placeholderValues?: Record): AsyncGenerator; +} + +export interface SingleStoreTransactionConfig { + withConsistentSnapshot?: boolean; + accessMode?: 'read only' | 'read write'; + isolationLevel: 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'; +} + +export abstract class SingleStoreSession< + TQueryResult extends SingleStoreQueryResultHKT = SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase = PreparedQueryHKTBase, + TFullSchema extends Record = Record, + TSchema extends TablesRelationalConfig = Record, +> { + static readonly [entityKind]: string = 'SingleStoreSession'; + + constructor(protected dialect: SingleStoreDialect) {} + + abstract prepareQuery< + T extends SingleStorePreparedQueryConfig, + TPreparedQueryHKT extends SingleStorePreparedQueryHKT, + >( + query: Query, + fields: SelectedFieldsOrdered | undefined, + customResultMapper?: (rows: unknown[][]) => T['execute'], + generatedIds?: Record[], + returningIds?: SelectedFieldsOrdered, + ): PreparedQueryKind; + + execute(query: SQL): Promise { + return this.prepareQuery( + this.dialect.sqlToQuery(query), + undefined, + ).execute(); + } + + abstract all(query: SQL): Promise; + + abstract transaction( + transaction: (tx: SingleStoreTransaction) => Promise, + config?: SingleStoreTransactionConfig, + ): Promise; + + protected getSetTransactionSQL(config: SingleStoreTransactionConfig): SQL | undefined { + const parts: string[] = []; + + if (config.isolationLevel) { + parts.push(`isolation level ${config.isolationLevel}`); + } + + return parts.length ? sql.join(['set transaction ', parts.join(' ')]) : undefined; + } + + protected getStartTransactionSQL(config: SingleStoreTransactionConfig): SQL | undefined { + const parts: string[] = []; + + if (config.withConsistentSnapshot) { + parts.push('with consistent snapshot'); + } + + if (config.accessMode) { + parts.push(config.accessMode); + } + + return parts.length ? sql.join(['start transaction ', parts.join(' ')]) : undefined; + } +} + +export abstract class SingleStoreTransaction< + TQueryResult extends SingleStoreQueryResultHKT, + TPreparedQueryHKT extends PreparedQueryHKTBase, + TFullSchema extends Record = Record, + TSchema extends TablesRelationalConfig = Record, +> extends SingleStoreDatabase { + static readonly [entityKind]: string = 'SingleStoreTransaction'; + + constructor( + dialect: SingleStoreDialect, + session: SingleStoreSession, + protected schema: RelationalSchemaConfig | undefined, + protected readonly nestedIndex: number, + mode: Mode, + ) { + super(dialect, session, schema, mode); + } + + rollback(): never { + throw new TransactionRollbackError(); + } + + /** Nested transactions (aka savepoints) only work with InnoDB engine. */ + abstract override transaction( + transaction: (tx: SingleStoreTransaction) => Promise, + ): Promise; +} + +export interface PreparedQueryHKTBase extends SingleStorePreparedQueryHKT { + type: SingleStorePreparedQuery>; +} diff --git a/drizzle-orm/src/singlestore-core/sql/expressions/conditions.ts b/drizzle-orm/src/singlestore-core/sql/expressions/conditions.ts new file mode 100644 index 000000000..95cffabdd --- /dev/null +++ b/drizzle-orm/src/singlestore-core/sql/expressions/conditions.ts @@ -0,0 +1,22 @@ +import { bindIfParam } from '~/sql/expressions/conditions.ts'; +import { type SQL, sql } from '~/sql/sql.ts'; +import type { Table } from '~/table'; + +/** + * Test that two values match. + * + * ## Examples + * + * ```ts + * // Select cars made by Ford + * db.select().from(cars) + * .where(match(cars.make, 'Ford')) + * ``` + * + * @see isNull for a way to test equality to NULL. + */ +export function match< + TTable extends Table, +>(left: TTable, right: unknown): SQL { + return sql`MATCH (TABLE ${left}) AGAINST (${bindIfParam(right, left)})`; +} diff --git a/drizzle-orm/src/singlestore-core/sql/expressions/index.ts b/drizzle-orm/src/singlestore-core/sql/expressions/index.ts new file mode 100644 index 000000000..81cb13770 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/sql/expressions/index.ts @@ -0,0 +1 @@ +export * from './conditions.ts'; diff --git a/drizzle-orm/src/singlestore-core/sql/index.ts b/drizzle-orm/src/singlestore-core/sql/index.ts new file mode 100644 index 000000000..16ca76679 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/sql/index.ts @@ -0,0 +1 @@ +export * from './expressions/index.ts'; diff --git a/drizzle-orm/src/singlestore-core/subquery.ts b/drizzle-orm/src/singlestore-core/subquery.ts new file mode 100644 index 000000000..a4605c56d --- /dev/null +++ b/drizzle-orm/src/singlestore-core/subquery.ts @@ -0,0 +1,17 @@ +import type { AddAliasToSelection } from '~/query-builders/select.types.ts'; +import type { ColumnsSelection } from '~/sql/sql.ts'; +import type { Subquery, WithSubquery } from '~/subquery.ts'; + +export type SubqueryWithSelection< + TSelection extends ColumnsSelection, + TAlias extends string, +> = + & Subquery> + & AddAliasToSelection; + +export type WithSubqueryWithSelection< + TSelection extends ColumnsSelection, + TAlias extends string, +> = + & WithSubquery> + & AddAliasToSelection; diff --git a/drizzle-orm/src/singlestore-core/table.ts b/drizzle-orm/src/singlestore-core/table.ts new file mode 100644 index 000000000..529a95fe9 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/table.ts @@ -0,0 +1,120 @@ +import type { BuildColumns, BuildExtraConfigColumns } from '~/column-builder.ts'; +import { entityKind } from '~/entity.ts'; +import { Table, type TableConfig as TableConfigBase, type UpdateTableConfig } from '~/table.ts'; +import type { SingleStoreColumn, SingleStoreColumnBuilder, SingleStoreColumnBuilderBase } from './columns/common.ts'; +import type { AnyIndexBuilder } from './indexes.ts'; +import type { PrimaryKeyBuilder } from './primary-keys.ts'; +import type { UniqueConstraintBuilder } from './unique-constraint.ts'; + +export type SingleStoreTableExtraConfig = Record< + string, + | AnyIndexBuilder + | PrimaryKeyBuilder + | UniqueConstraintBuilder +>; + +export type TableConfig = TableConfigBase; + +export class SingleStoreTable extends Table { + static readonly [entityKind]: string = 'SingleStoreTable'; + + declare protected $columns: T['columns']; + + /** @internal */ + static override readonly Symbol = Object.assign({}, Table.Symbol, {}); + + /** @internal */ + override [Table.Symbol.Columns]!: NonNullable; + + /** @internal */ + override [Table.Symbol.ExtraConfigBuilder]: + | ((self: Record) => SingleStoreTableExtraConfig) + | undefined = undefined; +} + +export type AnySingleStoreTable = {}> = SingleStoreTable< + UpdateTableConfig +>; + +export type SingleStoreTableWithColumns = + & SingleStoreTable + & { + [Key in keyof T['columns']]: T['columns'][Key]; + }; + +export function singlestoreTableWithSchema< + TTableName extends string, + TSchemaName extends string | undefined, + TColumnsMap extends Record, +>( + name: TTableName, + columns: TColumnsMap, + extraConfig: + | ((self: BuildColumns) => SingleStoreTableExtraConfig) + | undefined, + schema: TSchemaName, + baseName = name, +): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; +}> { + const rawTable = new SingleStoreTable<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>(name, schema, baseName); + + const builtColumns = Object.fromEntries( + Object.entries(columns).map(([name, colBuilderBase]) => { + const colBuilder = colBuilderBase as SingleStoreColumnBuilder; + const column = colBuilder.build(rawTable); + return [name, column]; + }), + ) as unknown as BuildColumns; + + const table = Object.assign(rawTable, builtColumns); + + table[Table.Symbol.Columns] = builtColumns; + table[Table.Symbol.ExtraConfigColumns] = builtColumns as unknown as BuildExtraConfigColumns< + TTableName, + TColumnsMap, + 'singlestore' + >; + + if (extraConfig) { + table[SingleStoreTable.Symbol.ExtraConfigBuilder] = extraConfig as unknown as ( + self: Record, + ) => SingleStoreTableExtraConfig; + } + + return table; +} + +export interface SingleStoreTableFn { + < + TTableName extends string, + TColumnsMap extends Record, + >( + name: TTableName, + columns: TColumnsMap, + extraConfig?: (self: BuildColumns) => SingleStoreTableExtraConfig, + ): SingleStoreTableWithColumns<{ + name: TTableName; + schema: TSchemaName; + columns: BuildColumns; + dialect: 'singlestore'; + }>; +} + +export const singlestoreTable: SingleStoreTableFn = (name, columns, extraConfig) => { + return singlestoreTableWithSchema(name, columns, extraConfig, undefined, name); +}; + +export function singlestoreTableCreator(customizeTableName: (name: string) => string): SingleStoreTableFn { + return (name, columns, extraConfig) => { + return singlestoreTableWithSchema(customizeTableName(name) as typeof name, columns, extraConfig, undefined, name); + }; +} diff --git a/drizzle-orm/src/singlestore-core/unique-constraint.ts b/drizzle-orm/src/singlestore-core/unique-constraint.ts new file mode 100644 index 000000000..faa4f3216 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/unique-constraint.ts @@ -0,0 +1,64 @@ +import { entityKind } from '~/entity.ts'; +import type { SingleStoreColumn } from './columns/index.ts'; +import { SingleStoreTable } from './table.ts'; + +export function unique(name?: string): UniqueOnConstraintBuilder { + return new UniqueOnConstraintBuilder(name); +} + +export function uniqueKeyName(table: SingleStoreTable, columns: string[]) { + return `${table[SingleStoreTable.Symbol.Name]}_${columns.join('_')}_unique`; +} + +export class UniqueConstraintBuilder { + static readonly [entityKind]: string = 'SingleStoreUniqueConstraintBuilder'; + + /** @internal */ + columns: SingleStoreColumn[]; + + constructor( + columns: SingleStoreColumn[], + private name?: string, + ) { + this.columns = columns; + } + + /** @internal */ + build(table: SingleStoreTable): UniqueConstraint { + return new UniqueConstraint(table, this.columns, this.name); + } +} + +export class UniqueOnConstraintBuilder { + static readonly [entityKind]: string = 'SingleStoreUniqueOnConstraintBuilder'; + + /** @internal */ + name?: string; + + constructor( + name?: string, + ) { + this.name = name; + } + + on(...columns: [SingleStoreColumn, ...SingleStoreColumn[]]) { + return new UniqueConstraintBuilder(columns, this.name); + } +} + +export class UniqueConstraint { + static readonly [entityKind]: string = 'SingleStoreUniqueConstraint'; + + readonly columns: SingleStoreColumn[]; + readonly name?: string; + readonly nullsNotDistinct: boolean = false; + + constructor(readonly table: SingleStoreTable, columns: SingleStoreColumn[], name?: string) { + this.columns = columns; + this.name = name ?? uniqueKeyName(this.table, this.columns.map((column) => column.name)); + } + + getName() { + return this.name; + } +} diff --git a/drizzle-orm/src/singlestore-core/utils.ts b/drizzle-orm/src/singlestore-core/utils.ts new file mode 100644 index 000000000..e6412161d --- /dev/null +++ b/drizzle-orm/src/singlestore-core/utils.ts @@ -0,0 +1,56 @@ +import { is } from '~/entity.ts'; +import { Table } from '~/table.ts'; +import { ViewBaseConfig } from '~/view-common.ts'; +import type { Index } from './indexes.ts'; +import { IndexBuilder } from './indexes.ts'; +import type { PrimaryKey } from './primary-keys.ts'; +import { PrimaryKeyBuilder } from './primary-keys.ts'; +import { SingleStoreTable } from './table.ts'; +import { type UniqueConstraint, UniqueConstraintBuilder } from './unique-constraint.ts'; +import { SingleStoreViewConfig } from './view-common.ts'; +import type { SingleStoreView } from './view.ts'; + +export function getTableConfig(table: SingleStoreTable) { + const columns = Object.values(table[SingleStoreTable.Symbol.Columns]); + const indexes: Index[] = []; + const primaryKeys: PrimaryKey[] = []; + const uniqueConstraints: UniqueConstraint[] = []; + const name = table[Table.Symbol.Name]; + const schema = table[Table.Symbol.Schema]; + const baseName = table[Table.Symbol.BaseName]; + + const extraConfigBuilder = table[SingleStoreTable.Symbol.ExtraConfigBuilder]; + + if (extraConfigBuilder !== undefined) { + const extraConfig = extraConfigBuilder(table[SingleStoreTable.Symbol.Columns]); + for (const builder of Object.values(extraConfig)) { + if (is(builder, IndexBuilder)) { + indexes.push(builder.build(table)); + } else if (is(builder, UniqueConstraintBuilder)) { + uniqueConstraints.push(builder.build(table)); + } else if (is(builder, PrimaryKeyBuilder)) { + primaryKeys.push(builder.build(table)); + } + } + } + + return { + columns, + indexes, + primaryKeys, + uniqueConstraints, + name, + schema, + baseName, + }; +} + +export function getViewConfig< + TName extends string = string, + TExisting extends boolean = boolean, +>(view: SingleStoreView) { + return { + ...view[ViewBaseConfig], + ...view[SingleStoreViewConfig], + }; +} diff --git a/drizzle-orm/src/singlestore-core/view-base.ts b/drizzle-orm/src/singlestore-core/view-base.ts new file mode 100644 index 000000000..f71536e28 --- /dev/null +++ b/drizzle-orm/src/singlestore-core/view-base.ts @@ -0,0 +1,15 @@ +import { entityKind } from '~/entity.ts'; +import type { ColumnsSelection } from '~/sql/sql.ts'; +import { View } from '~/sql/sql.ts'; + +export abstract class SingleStoreViewBase< + TName extends string = string, + TExisting extends boolean = boolean, + TSelectedFields extends ColumnsSelection = ColumnsSelection, +> extends View { + static readonly [entityKind]: string = 'SingleStoreViewBase'; + + declare readonly _: View['_'] & { + readonly viewBrand: 'SingleStoreViewBase'; + }; +} diff --git a/drizzle-orm/src/singlestore-core/view-common.ts b/drizzle-orm/src/singlestore-core/view-common.ts new file mode 100644 index 000000000..d29c3d5ad --- /dev/null +++ b/drizzle-orm/src/singlestore-core/view-common.ts @@ -0,0 +1 @@ +export const SingleStoreViewConfig = Symbol.for('drizzle:SingleStoreViewConfig'); diff --git a/drizzle-orm/src/singlestore-core/view.ts b/drizzle-orm/src/singlestore-core/view.ts new file mode 100644 index 000000000..3c6c8d25c --- /dev/null +++ b/drizzle-orm/src/singlestore-core/view.ts @@ -0,0 +1,208 @@ +import type { BuildColumns } from '~/column-builder.ts'; +import { entityKind } from '~/entity.ts'; +import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; +import type { AddAliasToSelection } from '~/query-builders/select.types.ts'; +import { SelectionProxyHandler } from '~/selection-proxy.ts'; +import type { ColumnsSelection, SQL } from '~/sql/sql.ts'; +import { getTableColumns } from '~/utils.ts'; +import type { SingleStoreColumn, SingleStoreColumnBuilderBase } from './columns/index.ts'; +import { QueryBuilder } from './query-builders/query-builder.ts'; +import type { SelectedFields } from './query-builders/select.types.ts'; +import { singlestoreTable } from './table.ts'; +import { SingleStoreViewBase } from './view-base.ts'; +import { SingleStoreViewConfig } from './view-common.ts'; + +export interface ViewBuilderConfig { + algorithm?: 'undefined' | 'merge' | 'temptable'; + definer?: string; + sqlSecurity?: 'definer' | 'invoker'; + withCheckOption?: 'cascaded' | 'local'; +} + +export class ViewBuilderCore { + static readonly [entityKind]: string = 'SingleStoreViewBuilder'; + + declare readonly _: { + readonly name: TConfig['name']; + readonly columns: TConfig['columns']; + }; + + constructor( + protected name: TConfig['name'], + protected schema: string | undefined, + ) {} + + protected config: ViewBuilderConfig = {}; + + algorithm( + algorithm: Exclude, + ): this { + this.config.algorithm = algorithm; + return this; + } + + definer( + definer: Exclude, + ): this { + this.config.definer = definer; + return this; + } + + sqlSecurity( + sqlSecurity: Exclude, + ): this { + this.config.sqlSecurity = sqlSecurity; + return this; + } + + withCheckOption( + withCheckOption?: Exclude, + ): this { + this.config.withCheckOption = withCheckOption ?? 'cascaded'; + return this; + } +} + +export class ViewBuilder extends ViewBuilderCore<{ name: TName }> { + static readonly [entityKind]: string = 'SingleStoreViewBuilder'; + + as( + qb: TypedQueryBuilder | ((qb: QueryBuilder) => TypedQueryBuilder), + ): SingleStoreViewWithSelection> { + if (typeof qb === 'function') { + qb = qb(new QueryBuilder()); + } + const selectionProxy = new SelectionProxyHandler({ + alias: this.name, + sqlBehavior: 'error', + sqlAliasedBehavior: 'alias', + replaceOriginalName: true, + }); + const aliasedSelection = new Proxy(qb.getSelectedFields(), selectionProxy); + return new Proxy( + new SingleStoreView({ + singlestoreConfig: this.config, + config: { + name: this.name, + schema: this.schema, + selectedFields: aliasedSelection, + query: qb.getSQL().inlineParams(), + }, + }), + selectionProxy as any, + ) as SingleStoreViewWithSelection>; + } +} + +export class ManualViewBuilder< + TName extends string = string, + TColumns extends Record = Record, +> extends ViewBuilderCore<{ name: TName; columns: TColumns }> { + static readonly [entityKind]: string = 'SingleStoreManualViewBuilder'; + + private columns: Record; + + constructor( + name: TName, + columns: TColumns, + schema: string | undefined, + ) { + super(name, schema); + this.columns = getTableColumns(singlestoreTable(name, columns)) as BuildColumns; + } + + existing(): SingleStoreViewWithSelection> { + return new Proxy( + new SingleStoreView({ + singlestoreConfig: undefined, + config: { + name: this.name, + schema: this.schema, + selectedFields: this.columns, + query: undefined, + }, + }), + new SelectionProxyHandler({ + alias: this.name, + sqlBehavior: 'error', + sqlAliasedBehavior: 'alias', + replaceOriginalName: true, + }), + ) as SingleStoreViewWithSelection>; + } + + as(query: SQL): SingleStoreViewWithSelection> { + return new Proxy( + new SingleStoreView({ + singlestoreConfig: this.config, + config: { + name: this.name, + schema: this.schema, + selectedFields: this.columns, + query: query.inlineParams(), + }, + }), + new SelectionProxyHandler({ + alias: this.name, + sqlBehavior: 'error', + sqlAliasedBehavior: 'alias', + replaceOriginalName: true, + }), + ) as SingleStoreViewWithSelection>; + } +} + +export class SingleStoreView< + TName extends string = string, + TExisting extends boolean = boolean, + TSelectedFields extends ColumnsSelection = ColumnsSelection, +> extends SingleStoreViewBase { + static readonly [entityKind]: string = 'SingleStoreView'; + + declare protected $SingleStoreViewBrand: 'SingleStoreView'; + + [SingleStoreViewConfig]: ViewBuilderConfig | undefined; + + constructor({ singlestoreConfig, config }: { + singlestoreConfig: ViewBuilderConfig | undefined; + config: { + name: TName; + schema: string | undefined; + selectedFields: SelectedFields; + query: SQL | undefined; + }; + }) { + super(config); + this[SingleStoreViewConfig] = singlestoreConfig; + } +} + +export type SingleStoreViewWithSelection< + TName extends string, + TExisting extends boolean, + TSelectedFields extends ColumnsSelection, +> = SingleStoreView & TSelectedFields; + +/** @internal */ +export function singlestoreViewWithSchema( + name: string, + selection: Record | undefined, + schema: string | undefined, +): ViewBuilder | ManualViewBuilder { + if (selection) { + return new ManualViewBuilder(name, selection, schema); + } + return new ViewBuilder(name, schema); +} + +export function singlestoreView(name: TName): ViewBuilder; +export function singlestoreView>( + name: TName, + columns: TColumns, +): ManualViewBuilder; +export function singlestoreView( + name: string, + selection?: Record, +): ViewBuilder | ManualViewBuilder { + return singlestoreViewWithSchema(name, selection, undefined); +} diff --git a/drizzle-orm/src/singlestore-proxy/driver.ts b/drizzle-orm/src/singlestore-proxy/driver.ts new file mode 100644 index 000000000..f61040c9f --- /dev/null +++ b/drizzle-orm/src/singlestore-proxy/driver.ts @@ -0,0 +1,54 @@ +import { DefaultLogger } from '~/logger.ts'; +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + type RelationalSchemaConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import { SingleStoreDatabase } from '~/singlestore-core/db.ts'; +import { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { DrizzleConfig } from '~/utils.ts'; +import { + type SingleStoreRemotePreparedQueryHKT, + type SingleStoreRemoteQueryResultHKT, + SingleStoreRemoteSession, +} from './session.ts'; + +export type SingleStoreRemoteDatabase< + TSchema extends Record = Record, +> = SingleStoreDatabase; + +export type RemoteCallback = ( + sql: string, + params: any[], + method: 'all' | 'execute', +) => Promise<{ rows: any[]; insertId?: number; affectedRows?: number }>; + +export function drizzle = Record>( + callback: RemoteCallback, + config: DrizzleConfig = {}, +): SingleStoreRemoteDatabase { + const dialect = new SingleStoreDialect(); + let logger; + if (config.logger === true) { + logger = new DefaultLogger(); + } else if (config.logger !== false) { + logger = config.logger; + } + + let schema: RelationalSchemaConfig | undefined; + if (config.schema) { + const tablesConfig = extractTablesRelationalConfig( + config.schema, + createTableRelationsHelpers, + ); + schema = { + fullSchema: config.schema, + schema: tablesConfig.tables, + tableNamesMap: tablesConfig.tableNamesMap, + }; + } + + const session = new SingleStoreRemoteSession(callback, dialect, schema, { logger }); + return new SingleStoreDatabase(dialect, session, schema, 'default') as SingleStoreRemoteDatabase; +} diff --git a/drizzle-orm/src/singlestore-proxy/index.ts b/drizzle-orm/src/singlestore-proxy/index.ts new file mode 100644 index 000000000..b1b6a52e7 --- /dev/null +++ b/drizzle-orm/src/singlestore-proxy/index.ts @@ -0,0 +1,2 @@ +export * from './driver.ts'; +export * from './session.ts'; diff --git a/drizzle-orm/src/singlestore-proxy/migrator.ts b/drizzle-orm/src/singlestore-proxy/migrator.ts new file mode 100644 index 000000000..2ed0172fb --- /dev/null +++ b/drizzle-orm/src/singlestore-proxy/migrator.ts @@ -0,0 +1,52 @@ +import type { MigrationConfig } from '~/migrator.ts'; +import { readMigrationFiles } from '~/migrator.ts'; +import { sql } from '~/sql/sql.ts'; +import type { SingleStoreRemoteDatabase } from './driver.ts'; + +export type ProxyMigrator = (migrationQueries: string[]) => Promise; + +export async function migrate>( + db: SingleStoreRemoteDatabase, + callback: ProxyMigrator, + config: MigrationConfig, +) { + const migrations = readMigrationFiles(config); + + const migrationsTable = config.migrationsTable ?? '__drizzle_migrations'; + const migrationTableCreate = sql` + create table if not exists ${sql.identifier(migrationsTable)} ( + id serial primary key, + hash text not null, + created_at bigint + ) + `; + await db.execute(migrationTableCreate); + + const dbMigrations = await db.select({ + id: sql.raw('id'), + hash: sql.raw('hash'), + created_at: sql.raw('created_at'), + }).from(sql.identifier(migrationsTable).getSQL()).orderBy( + sql.raw('created_at desc'), + ).limit(1); + + const lastDbMigration = dbMigrations[0]; + + const queriesToRun: string[] = []; + + for (const migration of migrations) { + if ( + !lastDbMigration + || Number(lastDbMigration.created_at) < migration.folderMillis + ) { + queriesToRun.push( + ...migration.sql, + `insert into ${ + sql.identifier(migrationsTable).value + } (\`hash\`, \`created_at\`) values('${migration.hash}', '${migration.folderMillis}')`, + ); + } + } + + await callback(queriesToRun); +} diff --git a/drizzle-orm/src/singlestore-proxy/session.ts b/drizzle-orm/src/singlestore-proxy/session.ts new file mode 100644 index 000000000..cf73f7c71 --- /dev/null +++ b/drizzle-orm/src/singlestore-proxy/session.ts @@ -0,0 +1,178 @@ +import type { FieldPacket, ResultSetHeader } from 'mysql2/promise'; +import { Column } from '~/column.ts'; +import { entityKind, is } from '~/entity.ts'; +import type { Logger } from '~/logger.ts'; +import { NoopLogger } from '~/logger.ts'; +import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import { SingleStoreTransaction } from '~/singlestore-core/index.ts'; +import type { SelectedFieldsOrdered } from '~/singlestore-core/query-builders/select.types.ts'; +import type { + PreparedQueryKind, + SingleStorePreparedQueryConfig, + SingleStorePreparedQueryHKT, + SingleStoreQueryResultHKT, + SingleStoreTransactionConfig, +} from '~/singlestore-core/session.ts'; +import { SingleStorePreparedQuery as PreparedQueryBase, SingleStoreSession } from '~/singlestore-core/session.ts'; +import { fillPlaceholders } from '~/sql/sql.ts'; +import type { Query, SQL } from '~/sql/sql.ts'; +import { type Assume, mapResultRow } from '~/utils.ts'; +import type { RemoteCallback } from './driver.ts'; + +export type SingleStoreRawQueryResult = [ResultSetHeader, FieldPacket[]]; + +export interface SingleStoreRemoteSessionOptions { + logger?: Logger; +} + +export class SingleStoreRemoteSession< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SingleStoreSession { + static readonly [entityKind]: string = 'SingleStoreRemoteSession'; + + private logger: Logger; + + constructor( + private client: RemoteCallback, + dialect: SingleStoreDialect, + private schema: RelationalSchemaConfig | undefined, + options: SingleStoreRemoteSessionOptions, + ) { + super(dialect); + this.logger = options.logger ?? new NoopLogger(); + } + + prepareQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + customResultMapper?: (rows: unknown[][]) => T['execute'], + generatedIds?: Record[], + returningIds?: SelectedFieldsOrdered, + ): PreparedQueryKind { + return new PreparedQuery( + this.client, + query.sql, + query.params, + this.logger, + fields, + customResultMapper, + generatedIds, + returningIds, + ) as PreparedQueryKind; + } + + override all(query: SQL): Promise { + const querySql = this.dialect.sqlToQuery(query); + this.logger.logQuery(querySql.sql, querySql.params); + return this.client(querySql.sql, querySql.params, 'all').then(({ rows }) => rows) as Promise; + } + + override async transaction( + _transaction: (tx: SingleStoreProxyTransaction) => Promise, + _config?: SingleStoreTransactionConfig, + ): Promise { + throw new Error('Transactions are not supported by the SingleStore Proxy driver'); + } +} + +export class SingleStoreProxyTransaction< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SingleStoreTransaction< + SingleStoreRemoteQueryResultHKT, + SingleStoreRemotePreparedQueryHKT, + TFullSchema, + TSchema +> { + static readonly [entityKind]: string = 'SingleStoreProxyTransaction'; + + override async transaction( + _transaction: (tx: SingleStoreProxyTransaction) => Promise, + ): Promise { + throw new Error('Transactions are not supported by the SingleStore Proxy driver'); + } +} + +export class PreparedQuery extends PreparedQueryBase { + static readonly [entityKind]: string = 'SingleStoreProxyPreparedQuery'; + + constructor( + private client: RemoteCallback, + private queryString: string, + private params: unknown[], + private logger: Logger, + private fields: SelectedFieldsOrdered | undefined, + private customResultMapper?: (rows: unknown[][]) => T['execute'], + // Keys that were used in $default and the value that was generated for them + private generatedIds?: Record[], + // Keys that should be returned, it has the column with all properries + key from object + private returningIds?: SelectedFieldsOrdered, + ) { + super(); + } + + async execute(placeholderValues: Record | undefined = {}): Promise { + const params = fillPlaceholders(this.params, placeholderValues); + + const { fields, client, queryString, logger, joinsNotNullableMap, customResultMapper, returningIds, generatedIds } = + this; + + logger.logQuery(queryString, params); + + if (!fields && !customResultMapper) { + const { rows: data } = await client(queryString, params, 'execute'); + + const insertId = data[0].insertId as number; + const affectedRows = data[0].affectedRows; + + if (returningIds) { + const returningResponse = []; + let j = 0; + for (let i = insertId; i < insertId + affectedRows; i++) { + for (const column of returningIds) { + const key = returningIds[0]!.path[0]!; + if (is(column.field, Column)) { + // @ts-ignore + if (column.field.primary && column.field.autoIncrement) { + returningResponse.push({ [key]: i }); + } + if (column.field.defaultFn && generatedIds) { + // generatedIds[rowIdx][key] + returningResponse.push({ [key]: generatedIds[j]![key] }); + } + } + } + j++; + } + + return returningResponse; + } + + return data; + } + + const { rows } = await client(queryString, params, 'all'); + + if (customResultMapper) { + return customResultMapper(rows); + } + + return rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + } + + override iterator( + _placeholderValues: Record = {}, + ): AsyncGenerator { + throw new Error('Streaming is not supported by the SingleStore Proxy driver'); + } +} + +export interface SingleStoreRemoteQueryResultHKT extends SingleStoreQueryResultHKT { + type: SingleStoreRawQueryResult; +} + +export interface SingleStoreRemotePreparedQueryHKT extends SingleStorePreparedQueryHKT { + type: PreparedQuery>; +} diff --git a/drizzle-orm/src/singlestore/driver.ts b/drizzle-orm/src/singlestore/driver.ts new file mode 100644 index 000000000..0cda87593 --- /dev/null +++ b/drizzle-orm/src/singlestore/driver.ts @@ -0,0 +1,99 @@ +import type { Connection as CallbackConnection, Pool as CallbackPool } from 'mysql2'; +import { entityKind } from '~/entity.ts'; +import type { Logger } from '~/logger.ts'; +import { DefaultLogger } from '~/logger.ts'; +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + type RelationalSchemaConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import { SingleStoreDatabase } from '~/singlestore-core/db.ts'; +import { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { Mode } from '~/singlestore-core/session.ts'; +import type { DrizzleConfig } from '~/utils.ts'; +import { DrizzleError } from '../index.ts'; +import type { SingleStore2Client, SingleStore2PreparedQueryHKT, SingleStore2QueryResultHKT } from './session.ts'; +import { SingleStore2Session } from './session.ts'; + +export interface SingleStoreDriverOptions { + logger?: Logger; +} + +export class SingleStore2Driver { + static readonly [entityKind]: string = 'SingleStore2Driver'; + + constructor( + private client: SingleStore2Client, + private dialect: SingleStoreDialect, + private options: SingleStoreDriverOptions = {}, + ) { + } + + createSession( + schema: RelationalSchemaConfig | undefined, + mode: Mode, + ): SingleStore2Session, TablesRelationalConfig> { + return new SingleStore2Session(this.client, this.dialect, schema, { logger: this.options.logger, mode }); + } +} + +export { SingleStoreDatabase } from '~/singlestore-core/db.ts'; + +export type SingleStore2Database< + TSchema extends Record = Record, +> = SingleStoreDatabase; + +export type SingleStore2DrizzleConfig = Record> = + & Omit, 'schema'> + & ({ schema: TSchema; mode: Mode } | { schema?: undefined; mode?: Mode }); + +export function drizzle = Record>( + client: SingleStore2Client | CallbackConnection | CallbackPool, + config: SingleStore2DrizzleConfig = {}, +): SingleStore2Database { + const dialect = new SingleStoreDialect(); + let logger; + if (config.logger === true) { + logger = new DefaultLogger(); + } else if (config.logger !== false) { + logger = config.logger; + } + if (isCallbackClient(client)) { + client = client.promise(); + } + + let schema: RelationalSchemaConfig | undefined; + if (config.schema) { + if (config.mode === undefined) { + throw new DrizzleError({ + message: + 'You need to specify "mode": "planetscale" or "default" when providing a schema. Read more: https://orm.drizzle.team/docs/rqb#modes', + }); + } + + const tablesConfig = extractTablesRelationalConfig( + config.schema, + createTableRelationsHelpers, + ); + schema = { + fullSchema: config.schema, + schema: tablesConfig.tables, + tableNamesMap: tablesConfig.tableNamesMap, + }; + } + + const mode = config.mode ?? 'default'; + + const driver = new SingleStore2Driver(client as SingleStore2Client, dialect, { logger }); + const session = driver.createSession(schema, mode); + return new SingleStoreDatabase(dialect, session, schema, mode) as SingleStore2Database; +} + +interface CallbackClient { + promise(): SingleStore2Client; +} + +function isCallbackClient(client: any): client is CallbackClient { + return typeof client.promise === 'function'; +} diff --git a/drizzle-orm/src/singlestore/index.ts b/drizzle-orm/src/singlestore/index.ts new file mode 100644 index 000000000..b1b6a52e7 --- /dev/null +++ b/drizzle-orm/src/singlestore/index.ts @@ -0,0 +1,2 @@ +export * from './driver.ts'; +export * from './session.ts'; diff --git a/drizzle-orm/src/singlestore/migrator.ts b/drizzle-orm/src/singlestore/migrator.ts new file mode 100644 index 000000000..600b4dd10 --- /dev/null +++ b/drizzle-orm/src/singlestore/migrator.ts @@ -0,0 +1,11 @@ +import type { MigrationConfig } from '~/migrator.ts'; +import { readMigrationFiles } from '~/migrator.ts'; +import type { SingleStore2Database } from './driver.ts'; + +export async function migrate>( + db: SingleStore2Database, + config: MigrationConfig, +) { + const migrations = readMigrationFiles(config); + await db.dialect.migrate(migrations, db.session, config); +} diff --git a/drizzle-orm/src/singlestore/session.ts b/drizzle-orm/src/singlestore/session.ts new file mode 100644 index 000000000..7a4be5257 --- /dev/null +++ b/drizzle-orm/src/singlestore/session.ts @@ -0,0 +1,339 @@ +import type { Connection as CallbackConnection } from 'mysql2'; +import type { + Connection, + FieldPacket, + OkPacket, + Pool, + PoolConnection, + QueryOptions, + ResultSetHeader, + RowDataPacket, +} from 'mysql2/promise'; +import { once } from 'node:events'; +import { Column } from '~/column.ts'; +import { entityKind, is } from '~/entity.ts'; +import type { Logger } from '~/logger.ts'; +import { NoopLogger } from '~/logger.ts'; +import type { RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; +import type { SingleStoreDialect } from '~/singlestore-core/dialect.ts'; +import type { SelectedFieldsOrdered } from '~/singlestore-core/query-builders/select.types.ts'; +import { + type Mode, + type PreparedQueryKind, + SingleStorePreparedQuery, + type SingleStorePreparedQueryConfig, + type SingleStorePreparedQueryHKT, + type SingleStoreQueryResultHKT, + SingleStoreSession, + SingleStoreTransaction, + type SingleStoreTransactionConfig, +} from '~/singlestore-core/session.ts'; +import { fillPlaceholders, sql } from '~/sql/sql.ts'; +import type { Query, SQL } from '~/sql/sql.ts'; +import { type Assume, mapResultRow } from '~/utils.ts'; + +export type SingleStore2Client = Pool | Connection; + +export type SingleStoreRawQueryResult = [ResultSetHeader, FieldPacket[]]; +export type SingleStoreQueryResultType = RowDataPacket[][] | RowDataPacket[] | OkPacket | OkPacket[] | ResultSetHeader; +export type SingleStoreQueryResult< + T = any, +> = [T extends ResultSetHeader ? T : T[], FieldPacket[]]; + +export class SingleStore2PreparedQuery extends SingleStorePreparedQuery { + static readonly [entityKind]: string = 'SingleStore2PreparedQuery'; + + private rawQuery: QueryOptions; + private query: QueryOptions; + + constructor( + private client: SingleStore2Client, + queryString: string, + private params: unknown[], + private logger: Logger, + private fields: SelectedFieldsOrdered | undefined, + private customResultMapper?: (rows: unknown[][]) => T['execute'], + // Keys that were used in $default and the value that was generated for them + private generatedIds?: Record[], + // Keys that should be returned, it has the column with all properries + key from object + private returningIds?: SelectedFieldsOrdered, + ) { + super(); + this.rawQuery = { + sql: queryString, + // rowsAsArray: true, + typeCast: function(field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }; + this.query = { + sql: queryString, + rowsAsArray: true, + typeCast: function(field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }; + } + + async execute(placeholderValues: Record = {}): Promise { + const params = fillPlaceholders(this.params, placeholderValues); + + this.logger.logQuery(this.rawQuery.sql, params); + + const { fields, client, rawQuery, query, joinsNotNullableMap, customResultMapper, returningIds, generatedIds } = + this; + if (!fields && !customResultMapper) { + const res = await client.query(rawQuery, params); + const insertId = res[0].insertId; + const affectedRows = res[0].affectedRows; + // for each row, I need to check keys from + if (returningIds) { + const returningResponse = []; + let j = 0; + for (let i = insertId; i < insertId + affectedRows; i++) { + for (const column of returningIds) { + const key = returningIds[0]!.path[0]!; + if (is(column.field, Column)) { + // @ts-ignore + if (column.field.primary && column.field.autoIncrement) { + returningResponse.push({ [key]: i }); + } + if (column.field.defaultFn && generatedIds) { + // generatedIds[rowIdx][key] + returningResponse.push({ [key]: generatedIds[j]![key] }); + } + } + } + j++; + } + + return returningResponse; + } + return res; + } + + const result = await client.query(query, params); + const rows = result[0]; + + if (customResultMapper) { + return customResultMapper(rows); + } + + return rows.map((row) => mapResultRow(fields!, row, joinsNotNullableMap)); + } + + async *iterator( + placeholderValues: Record = {}, + ): AsyncGenerator { + const params = fillPlaceholders(this.params, placeholderValues); + const conn = ((isPool(this.client) ? await this.client.getConnection() : this.client) as {} as { + connection: CallbackConnection; + }).connection; + + const { fields, query, rawQuery, joinsNotNullableMap, client, customResultMapper } = this; + const hasRowsMapper = Boolean(fields || customResultMapper); + const driverQuery = hasRowsMapper ? conn.query(query, params) : conn.query(rawQuery, params); + + const stream = driverQuery.stream(); + + function dataListener() { + stream.pause(); + } + + stream.on('data', dataListener); + + try { + const onEnd = once(stream, 'end'); + const onError = once(stream, 'error'); + + while (true) { + stream.resume(); + const row = await Promise.race([onEnd, onError, new Promise((resolve) => stream.once('data', resolve))]); + if (row === undefined || (Array.isArray(row) && row.length === 0)) { + break; + } else if (row instanceof Error) { // eslint-disable-line no-instanceof/no-instanceof + throw row; + } else { + if (hasRowsMapper) { + if (customResultMapper) { + const mappedRow = customResultMapper([row as unknown[]]); + yield (Array.isArray(mappedRow) ? mappedRow[0] : mappedRow); + } else { + yield mapResultRow(fields!, row as unknown[], joinsNotNullableMap); + } + } else { + yield row as T['execute']; + } + } + } + } finally { + stream.off('data', dataListener); + if (isPool(client)) { + conn.end(); + } + } + } +} + +export interface SingleStore2SessionOptions { + logger?: Logger; + mode: Mode; +} + +export class SingleStore2Session< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SingleStoreSession { + static readonly [entityKind]: string = 'SingleStore2Session'; + + private logger: Logger; + private mode: Mode; + + constructor( + private client: SingleStore2Client, + dialect: SingleStoreDialect, + private schema: RelationalSchemaConfig | undefined, + private options: SingleStore2SessionOptions, + ) { + super(dialect); + this.logger = options.logger ?? new NoopLogger(); + this.mode = options.mode; + } + + prepareQuery( + query: Query, + fields: SelectedFieldsOrdered | undefined, + customResultMapper?: (rows: unknown[][]) => T['execute'], + generatedIds?: Record[], + returningIds?: SelectedFieldsOrdered, + ): PreparedQueryKind { + // Add returningId fields + // Each driver gets them from response from database + return new SingleStore2PreparedQuery( + this.client, + query.sql, + query.params, + this.logger, + fields, + customResultMapper, + generatedIds, + returningIds, + ) as PreparedQueryKind; + } + + /** + * @internal + * What is its purpose? + */ + async query(query: string, params: unknown[]): Promise { + this.logger.logQuery(query, params); + const result = await this.client.query({ + sql: query, + values: params, + rowsAsArray: true, + typeCast: function(field: any, next: any) { + if (field.type === 'TIMESTAMP' || field.type === 'DATETIME' || field.type === 'DATE') { + return field.string(); + } + return next(); + }, + }); + return result; + } + + override all(query: SQL): Promise { + const querySql = this.dialect.sqlToQuery(query); + this.logger.logQuery(querySql.sql, querySql.params); + return this.client.execute(querySql.sql, querySql.params).then((result) => result[0]) as Promise; + } + + override async transaction( + transaction: (tx: SingleStore2Transaction) => Promise, + config?: SingleStoreTransactionConfig, + ): Promise { + const session = isPool(this.client) + ? new SingleStore2Session( + await this.client.getConnection(), + this.dialect, + this.schema, + this.options, + ) + : this; + const tx = new SingleStore2Transaction( + this.dialect, + session as SingleStoreSession, + this.schema, + 0, + this.mode, + ); + if (config) { + const setTransactionConfigSql = this.getSetTransactionSQL(config); + if (setTransactionConfigSql) { + await tx.execute(setTransactionConfigSql); + } + const startTransactionSql = this.getStartTransactionSQL(config); + await (startTransactionSql ? tx.execute(startTransactionSql) : tx.execute(sql`begin`)); + } else { + await tx.execute(sql`begin`); + } + try { + const result = await transaction(tx); + await tx.execute(sql`commit`); + return result; + } catch (err) { + await tx.execute(sql`rollback`); + throw err; + } finally { + if (isPool(this.client)) { + (session.client as PoolConnection).release(); + } + } + } +} + +export class SingleStore2Transaction< + TFullSchema extends Record, + TSchema extends TablesRelationalConfig, +> extends SingleStoreTransaction { + static readonly [entityKind]: string = 'SingleStore2Transaction'; + + override async transaction( + transaction: (tx: SingleStore2Transaction) => Promise, + ): Promise { + const savepointName = `sp${this.nestedIndex + 1}`; + const tx = new SingleStore2Transaction( + this.dialect, + this.session, + this.schema, + this.nestedIndex + 1, + this.mode, + ); + await tx.execute(sql.raw(`savepoint ${savepointName}`)); + try { + const result = await transaction(tx); + await tx.execute(sql.raw(`release savepoint ${savepointName}`)); + return result; + } catch (err) { + await tx.execute(sql.raw(`rollback to savepoint ${savepointName}`)); + throw err; + } + } +} + +function isPool(client: SingleStore2Client): client is Pool { + return 'getConnection' in client; +} + +export interface SingleStore2QueryResultHKT extends SingleStoreQueryResultHKT { + type: SingleStoreRawQueryResult; +} + +export interface SingleStore2PreparedQueryHKT extends SingleStorePreparedQueryHKT { + type: SingleStore2PreparedQuery>; +} diff --git a/drizzle-orm/src/sqlite-core/db.ts b/drizzle-orm/src/sqlite-core/db.ts index 65f807d08..7ae2736e0 100644 --- a/drizzle-orm/src/sqlite-core/db.ts +++ b/drizzle-orm/src/sqlite-core/db.ts @@ -2,7 +2,7 @@ import { entityKind } from '~/entity.ts'; import type { TypedQueryBuilder } from '~/query-builders/query-builder.ts'; import type { ExtractTablesWithRelations, RelationalSchemaConfig, TablesRelationalConfig } from '~/relations.ts'; import { SelectionProxyHandler } from '~/selection-proxy.ts'; -import type { ColumnsSelection, SQLWrapper } from '~/sql/sql.ts'; +import type { ColumnsSelection, SQL, SQLWrapper } from '~/sql/sql.ts'; import type { SQLiteAsyncDialect, SQLiteSyncDialect } from '~/sqlite-core/dialect.ts'; import { QueryBuilder, @@ -21,10 +21,12 @@ import type { import type { SQLiteTable } from '~/sqlite-core/table.ts'; import { WithSubquery } from '~/subquery.ts'; import type { DrizzleTypeError } from '~/utils.ts'; +import { SQLiteCountBuilder } from './query-builders/count.ts'; import { RelationalQueryBuilder } from './query-builders/query.ts'; import { SQLiteRaw } from './query-builders/raw.ts'; import type { SelectedFields } from './query-builders/select.types.ts'; import type { WithSubqueryWithSelection } from './subquery.ts'; +import type { SQLiteViewBase } from './view-base.ts'; export class BaseSQLiteDatabase< TResultKind extends 'sync' | 'async', @@ -134,6 +136,13 @@ export class BaseSQLiteDatabase< }; } + $count( + source: SQLiteTable | SQLiteViewBase | SQL | SQLWrapper, + filters?: SQL, + ) { + return new SQLiteCountBuilder({ source, filters, session: this.session }); + } + /** * Incorporates a previously defined CTE (using `$with`) into the main query. * diff --git a/drizzle-orm/src/sqlite-core/query-builders/count.ts b/drizzle-orm/src/sqlite-core/query-builders/count.ts new file mode 100644 index 000000000..1b19eed07 --- /dev/null +++ b/drizzle-orm/src/sqlite-core/query-builders/count.ts @@ -0,0 +1,77 @@ +import { entityKind, sql } from '~/index.ts'; +import type { SQLWrapper } from '~/sql/sql.ts'; +import { SQL } from '~/sql/sql.ts'; +import type { SQLiteSession } from '../session.ts'; +import type { SQLiteTable } from '../table.ts'; +import type { SQLiteView } from '../view.ts'; + +export class SQLiteCountBuilder< + TSession extends SQLiteSession, +> extends SQL implements Promise, SQLWrapper { + private sql: SQL; + + static readonly [entityKind] = 'SQLiteCountBuilderAsync'; + [Symbol.toStringTag] = 'SQLiteCountBuilderAsync'; + + private session: TSession; + + private static buildEmbeddedCount( + source: SQLiteTable | SQLiteView | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`(select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters})`; + } + + private static buildCount( + source: SQLiteTable | SQLiteView | SQL | SQLWrapper, + filters?: SQL, + ): SQL { + return sql`select count(*) from ${source}${sql.raw(' where ').if(filters)}${filters}`; + } + + constructor( + readonly params: { + source: SQLiteTable | SQLiteView | SQL | SQLWrapper; + filters?: SQL; + session: TSession; + }, + ) { + super(SQLiteCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks); + + this.session = params.session; + + this.sql = SQLiteCountBuilder.buildCount( + params.source, + params.filters, + ); + } + + then( + onfulfilled?: ((value: number) => TResult1 | PromiseLike) | null | undefined, + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined, + ): Promise { + return Promise.resolve(this.session.count(this.sql)).then( + onfulfilled, + onrejected, + ); + } + + catch( + onRejected?: ((reason: any) => never | PromiseLike) | null | undefined, + ): Promise { + return this.then(undefined, onRejected); + } + + finally(onFinally?: (() => void) | null | undefined): Promise { + return this.then( + (value) => { + onFinally?.(); + return value; + }, + (reason) => { + onFinally?.(); + throw reason; + }, + ); + } +} diff --git a/drizzle-orm/src/sqlite-core/session.ts b/drizzle-orm/src/sqlite-core/session.ts index 4ac987b4a..d291b6fdf 100644 --- a/drizzle-orm/src/sqlite-core/session.ts +++ b/drizzle-orm/src/sqlite-core/session.ts @@ -187,6 +187,12 @@ export abstract class SQLiteSession< >; } + async count(sql: SQL) { + const result = await this.values(sql) as [[number]]; + + return result[0][0]; + } + /** @internal */ extractRawValuesValueFromBatchResult(_result: unknown): unknown { throw new Error('Not implemented'); diff --git a/drizzle-orm/src/tidb-serverless/driver.ts b/drizzle-orm/src/tidb-serverless/driver.ts index bdd5324db..b762bd889 100644 --- a/drizzle-orm/src/tidb-serverless/driver.ts +++ b/drizzle-orm/src/tidb-serverless/driver.ts @@ -1,4 +1,5 @@ import type { Connection } from '@tidbcloud/serverless'; +import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; import { DefaultLogger } from '~/logger.ts'; import { MySqlDatabase } from '~/mysql-core/db.ts'; @@ -17,9 +18,11 @@ export interface TiDBServerlessSDriverOptions { logger?: Logger; } -export type TiDBServerlessDatabase< +export class TiDBServerlessDatabase< TSchema extends Record = Record, -> = MySqlDatabase; +> extends MySqlDatabase { + static readonly [entityKind]: string = 'TiDBServerlessDatabase'; +} export function drizzle = Record>( client: Connection, @@ -47,5 +50,5 @@ export function drizzle = Record; + return new TiDBServerlessDatabase(dialect, session, schema as any, 'default') as TiDBServerlessDatabase; } diff --git a/drizzle-orm/src/tidb-serverless/session.ts b/drizzle-orm/src/tidb-serverless/session.ts index 64a8d61d7..b01b9f948 100644 --- a/drizzle-orm/src/tidb-serverless/session.ts +++ b/drizzle-orm/src/tidb-serverless/session.ts @@ -139,6 +139,14 @@ export class TiDBServerlessSession< return this.client.execute(querySql.sql, querySql.params) as Promise; } + override async count(sql: SQL): Promise { + const res = await this.execute<{ rows: [{ count: string }] }>(sql); + + return Number( + res['rows'][0]['count'], + ); + } + override async transaction( transaction: (tx: TiDBServerlessTransaction) => Promise, ): Promise { diff --git a/drizzle-orm/src/vercel-postgres/driver.ts b/drizzle-orm/src/vercel-postgres/driver.ts index 07e73c732..bc990d0b3 100644 --- a/drizzle-orm/src/vercel-postgres/driver.ts +++ b/drizzle-orm/src/vercel-postgres/driver.ts @@ -42,9 +42,11 @@ export class VercelPgDriver { } } -export type VercelPgDatabase< +export class VercelPgDatabase< TSchema extends Record = Record, -> = PgDatabase; +> extends PgDatabase { + static readonly [entityKind]: string = 'VercelPgDatabase'; +} export function drizzle = Record>( client: VercelPgClient, @@ -73,5 +75,5 @@ export function drizzle = Record; + return new VercelPgDatabase(dialect, session, schema as any) as VercelPgDatabase; } diff --git a/drizzle-orm/src/version.ts b/drizzle-orm/src/version.ts index 0c11937c8..d670a0575 100644 --- a/drizzle-orm/src/version.ts +++ b/drizzle-orm/src/version.ts @@ -1,4 +1,4 @@ // @ts-ignore - imported using Rollup json plugin export { version as npmVersion } from '../package.json'; // In version 7, we changed the PostgreSQL indexes API -export const compatibilityVersion = 7; +export const compatibilityVersion = 8; diff --git a/drizzle-orm/type-tests/mysql/count.ts b/drizzle-orm/type-tests/mysql/count.ts new file mode 100644 index 000000000..d9b9ba9ff --- /dev/null +++ b/drizzle-orm/type-tests/mysql/count.ts @@ -0,0 +1,61 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, gt, ne } from '~/expressions.ts'; +import { int, mysqlTable, serial, text } from '~/mysql-core/index.ts'; +import type { Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = mysqlTable('names', { + id: serial('id').primaryKey(), + name: text('name'), + authorId: int('author_id'), +}); + +const separate = await db.$count(names); + +const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))); + +const embedded = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names).as('count1'), + }) + .from(names); + +const embeddedFilters = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'), + }) + .from(names); + +Expect>; + +Expect>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embedded + > +>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embeddedFilters + > +>; diff --git a/drizzle-orm/type-tests/pg/count.ts b/drizzle-orm/type-tests/pg/count.ts new file mode 100644 index 000000000..9ed5eeaf9 --- /dev/null +++ b/drizzle-orm/type-tests/pg/count.ts @@ -0,0 +1,61 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, gt, ne } from '~/expressions.ts'; +import { integer, pgTable, serial, text } from '~/pg-core/index.ts'; +import type { Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = pgTable('names', { + id: serial('id').primaryKey(), + name: text('name'), + authorId: integer('author_id'), +}); + +const separate = await db.$count(names); + +const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))); + +const embedded = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names).as('count1'), + }) + .from(names); + +const embeddedFilters = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'), + }) + .from(names); + +Expect>; + +Expect>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embedded + > +>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embeddedFilters + > +>; diff --git a/drizzle-orm/type-tests/singlestore/1000columns.ts b/drizzle-orm/type-tests/singlestore/1000columns.ts new file mode 100644 index 000000000..f84640858 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/1000columns.ts @@ -0,0 +1,904 @@ +import { bigint, double, singlestoreTable, varchar } from '~/singlestore-core/index.ts'; + +singlestoreTable('test', { + col0: double('col1').primaryKey().autoincrement().default(0), + col1: double('col1').primaryKey().autoincrement().default(0), + col2: double('col1').primaryKey().autoincrement().default(0), + col3: double('col1').primaryKey().autoincrement().default(0), + col4: double('col1').primaryKey().autoincrement().default(0), + col5: double('col1').primaryKey().autoincrement().default(0), + col6: double('col1').primaryKey().autoincrement().default(0), + col8: double('col1').primaryKey().autoincrement().default(0), + col9: double('col1').primaryKey().autoincrement().default(0), + col10: double('col1').primaryKey().autoincrement().default(0), + col11: double('col1').primaryKey().autoincrement().default(0), + col12: double('col1').primaryKey().autoincrement().default(0), + col13: double('col1').primaryKey().autoincrement().default(0), + col14: double('col1').primaryKey().autoincrement().default(0), + col15: double('col1').primaryKey().autoincrement().default(0), + col16: double('col1').primaryKey().autoincrement().default(0), + col18: double('col1').primaryKey().autoincrement().default(0), + col19: double('col1').primaryKey().autoincrement().default(0), + col20: double('col1').primaryKey().autoincrement().default(0), + col21: double('col1').primaryKey().autoincrement().default(0), + col22: double('col1').primaryKey().autoincrement().default(0), + col23: double('col1').primaryKey().autoincrement().default(0), + col24: double('col1').primaryKey().autoincrement().default(0), + col25: double('col1').primaryKey().autoincrement().default(0), + col26: double('col1').primaryKey().autoincrement().default(0), + col28: double('col1').primaryKey().autoincrement().default(0), + col29: double('col1').primaryKey().autoincrement().default(0), + col30: double('col1').primaryKey().autoincrement().default(0), + col31: double('col1').primaryKey().autoincrement().default(0), + col32: double('col1').primaryKey().autoincrement().default(0), + col33: double('col1').primaryKey().autoincrement().default(0), + col34: double('col1').primaryKey().autoincrement().default(0), + col35: double('col1').primaryKey().autoincrement().default(0), + col36: double('col1').primaryKey().autoincrement().default(0), + col38: double('col1').primaryKey().autoincrement().default(0), + col39: double('col1').primaryKey().autoincrement().default(0), + col40: double('col1').primaryKey().autoincrement().default(0), + col41: double('col1').primaryKey().autoincrement().default(0), + col42: double('col1').primaryKey().autoincrement().default(0), + col43: double('col1').primaryKey().autoincrement().default(0), + col44: double('col1').primaryKey().autoincrement().default(0), + col45: double('col1').primaryKey().autoincrement().default(0), + col46: double('col1').primaryKey().autoincrement().default(0), + col48: double('col1').primaryKey().autoincrement().default(0), + col49: double('col1').primaryKey().autoincrement().default(0), + col50: double('col1').primaryKey().autoincrement().default(0), + col51: double('col1').primaryKey().autoincrement().default(0), + col52: double('col1').primaryKey().autoincrement().default(0), + col53: double('col1').primaryKey().autoincrement().default(0), + col54: double('col1').primaryKey().autoincrement().default(0), + col55: double('col1').primaryKey().autoincrement().default(0), + col56: double('col1').primaryKey().autoincrement().default(0), + col58: double('col1').primaryKey().autoincrement().default(0), + col59: double('col1').primaryKey().autoincrement().default(0), + col60: double('col1').primaryKey().autoincrement().default(0), + col61: double('col1').primaryKey().autoincrement().default(0), + col62: double('col1').primaryKey().autoincrement().default(0), + col63: double('col1').primaryKey().autoincrement().default(0), + col64: double('col1').primaryKey().autoincrement().default(0), + col65: double('col1').primaryKey().autoincrement().default(0), + col66: double('col1').primaryKey().autoincrement().default(0), + col68: double('col1').primaryKey().autoincrement().default(0), + col69: double('col1').primaryKey().autoincrement().default(0), + col70: double('col1').primaryKey().autoincrement().default(0), + col71: double('col1').primaryKey().autoincrement().default(0), + col72: double('col1').primaryKey().autoincrement().default(0), + col73: double('col1').primaryKey().autoincrement().default(0), + col74: double('col1').primaryKey().autoincrement().default(0), + col75: double('col1').primaryKey().autoincrement().default(0), + col76: double('col1').primaryKey().autoincrement().default(0), + col78: double('col1').primaryKey().autoincrement().default(0), + col79: double('col1').primaryKey().autoincrement().default(0), + col80: double('col1').primaryKey().autoincrement().default(0), + col81: double('col1').primaryKey().autoincrement().default(0), + col82: double('col1').primaryKey().autoincrement().default(0), + col83: double('col1').primaryKey().autoincrement().default(0), + col84: double('col1').primaryKey().autoincrement().default(0), + col85: double('col1').primaryKey().autoincrement().default(0), + col86: double('col1').primaryKey().autoincrement().default(0), + col88: double('col1').primaryKey().autoincrement().default(0), + col89: double('col1').primaryKey().autoincrement().default(0), + col90: double('col1').primaryKey().autoincrement().default(0), + col91: double('col1').primaryKey().autoincrement().default(0), + col92: double('col1').primaryKey().autoincrement().default(0), + col93: double('col1').primaryKey().autoincrement().default(0), + col94: double('col1').primaryKey().autoincrement().default(0), + col95: double('col1').primaryKey().autoincrement().default(0), + col96: double('col1').primaryKey().autoincrement().default(0), + col98: double('col1').primaryKey().autoincrement().default(0), + col99: double('col1').primaryKey().autoincrement().default(0), + col100: double('col1').primaryKey().autoincrement().default(0), + col101: double('col1').primaryKey().autoincrement().default(0), + col102: double('col1').primaryKey().autoincrement().default(0), + col103: double('col1').primaryKey().autoincrement().default(0), + col104: double('col1').primaryKey().autoincrement().default(0), + col105: double('col1').primaryKey().autoincrement().default(0), + col106: double('col1').primaryKey().autoincrement().default(0), + col108: double('col1').primaryKey().autoincrement().default(0), + col109: double('col1').primaryKey().autoincrement().default(0), + col110: double('col11').primaryKey().autoincrement().default(0), + col111: double('col11').primaryKey().autoincrement().default(0), + col112: double('col11').primaryKey().autoincrement().default(0), + col113: double('col11').primaryKey().autoincrement().default(0), + col114: double('col11').primaryKey().autoincrement().default(0), + col115: double('col11').primaryKey().autoincrement().default(0), + col116: double('col11').primaryKey().autoincrement().default(0), + col118: double('col11').primaryKey().autoincrement().default(0), + col119: double('col11').primaryKey().autoincrement().default(0), + col120: double('col11').primaryKey().autoincrement().default(0), + col121: double('col11').primaryKey().autoincrement().default(0), + col122: double('col11').primaryKey().autoincrement().default(0), + col123: double('col11').primaryKey().autoincrement().default(0), + col124: double('col11').primaryKey().autoincrement().default(0), + col125: double('col11').primaryKey().autoincrement().default(0), + col126: double('col11').primaryKey().autoincrement().default(0), + col128: double('col11').primaryKey().autoincrement().default(0), + col129: double('col11').primaryKey().autoincrement().default(0), + col130: double('col11').primaryKey().autoincrement().default(0), + col131: double('col11').primaryKey().autoincrement().default(0), + col132: double('col11').primaryKey().autoincrement().default(0), + col133: double('col11').primaryKey().autoincrement().default(0), + col134: double('col11').primaryKey().autoincrement().default(0), + col135: double('col11').primaryKey().autoincrement().default(0), + col136: double('col11').primaryKey().autoincrement().default(0), + col138: double('col11').primaryKey().autoincrement().default(0), + col139: double('col11').primaryKey().autoincrement().default(0), + col140: double('col11').primaryKey().autoincrement().default(0), + col141: double('col11').primaryKey().autoincrement().default(0), + col142: double('col11').primaryKey().autoincrement().default(0), + col143: double('col11').primaryKey().autoincrement().default(0), + col144: double('col11').primaryKey().autoincrement().default(0), + col145: double('col11').primaryKey().autoincrement().default(0), + col146: double('col11').primaryKey().autoincrement().default(0), + col148: double('col11').primaryKey().autoincrement().default(0), + col149: double('col11').primaryKey().autoincrement().default(0), + col150: double('col11').primaryKey().autoincrement().default(0), + col151: double('col11').primaryKey().autoincrement().default(0), + col152: double('col11').primaryKey().autoincrement().default(0), + col153: double('col11').primaryKey().autoincrement().default(0), + col154: double('col11').primaryKey().autoincrement().default(0), + col155: double('col11').primaryKey().autoincrement().default(0), + col156: double('col11').primaryKey().autoincrement().default(0), + col158: double('col11').primaryKey().autoincrement().default(0), + col159: double('col11').primaryKey().autoincrement().default(0), + col160: double('col11').primaryKey().autoincrement().default(0), + col161: double('col11').primaryKey().autoincrement().default(0), + col162: double('col11').primaryKey().autoincrement().default(0), + col163: double('col11').primaryKey().autoincrement().default(0), + col164: double('col11').primaryKey().autoincrement().default(0), + col165: double('col11').primaryKey().autoincrement().default(0), + col166: double('col11').primaryKey().autoincrement().default(0), + col168: double('col11').primaryKey().autoincrement().default(0), + col169: double('col11').primaryKey().autoincrement().default(0), + col170: double('col11').primaryKey().autoincrement().default(0), + col171: double('col11').primaryKey().autoincrement().default(0), + col172: double('col11').primaryKey().autoincrement().default(0), + col173: double('col11').primaryKey().autoincrement().default(0), + col174: double('col11').primaryKey().autoincrement().default(0), + col175: double('col11').primaryKey().autoincrement().default(0), + col176: double('col11').primaryKey().autoincrement().default(0), + col178: double('col11').primaryKey().autoincrement().default(0), + col179: double('col11').primaryKey().autoincrement().default(0), + col180: double('col11').primaryKey().autoincrement().default(0), + col181: double('col11').primaryKey().autoincrement().default(0), + col182: double('col11').primaryKey().autoincrement().default(0), + col183: double('col11').primaryKey().autoincrement().default(0), + col184: double('col11').primaryKey().autoincrement().default(0), + col185: double('col11').primaryKey().autoincrement().default(0), + col186: double('col11').primaryKey().autoincrement().default(0), + col188: double('col11').primaryKey().autoincrement().default(0), + col189: double('col11').primaryKey().autoincrement().default(0), + col190: double('col11').primaryKey().autoincrement().default(0), + col191: double('col11').primaryKey().autoincrement().default(0), + col192: double('col11').primaryKey().autoincrement().default(0), + col193: double('col11').primaryKey().autoincrement().default(0), + col194: double('col11').primaryKey().autoincrement().default(0), + col195: double('col11').primaryKey().autoincrement().default(0), + col196: double('col11').primaryKey().autoincrement().default(0), + col198: double('col11').primaryKey().autoincrement().default(0), + col199: double('col11').primaryKey().autoincrement().default(0), + col200: double('col2').primaryKey().autoincrement().default(0), + col201: double('col2').primaryKey().autoincrement().default(0), + col202: double('col2').primaryKey().autoincrement().default(0), + col203: double('col2').primaryKey().autoincrement().default(0), + col204: double('col2').primaryKey().autoincrement().default(0), + col205: double('col2').primaryKey().autoincrement().default(0), + col206: double('col2').primaryKey().autoincrement().default(0), + col208: double('col2').primaryKey().autoincrement().default(0), + col209: double('col2').primaryKey().autoincrement().default(0), + col210: double('col21').primaryKey().autoincrement().default(0), + col211: double('col21').primaryKey().autoincrement().default(0), + col212: double('col21').primaryKey().autoincrement().default(0), + col213: double('col21').primaryKey().autoincrement().default(0), + col214: double('col21').primaryKey().autoincrement().default(0), + col215: double('col21').primaryKey().autoincrement().default(0), + col216: double('col21').primaryKey().autoincrement().default(0), + col218: double('col21').primaryKey().autoincrement().default(0), + col219: double('col21').primaryKey().autoincrement().default(0), + col220: double('col21').primaryKey().autoincrement().default(0), + col221: double('col21').primaryKey().autoincrement().default(0), + col222: double('col21').primaryKey().autoincrement().default(0), + col223: double('col21').primaryKey().autoincrement().default(0), + col224: double('col21').primaryKey().autoincrement().default(0), + col225: double('col21').primaryKey().autoincrement().default(0), + col226: double('col21').primaryKey().autoincrement().default(0), + col228: double('col21').primaryKey().autoincrement().default(0), + col229: double('col21').primaryKey().autoincrement().default(0), + col230: double('col21').primaryKey().autoincrement().default(0), + col231: double('col21').primaryKey().autoincrement().default(0), + col232: double('col21').primaryKey().autoincrement().default(0), + col233: double('col21').primaryKey().autoincrement().default(0), + col234: double('col21').primaryKey().autoincrement().default(0), + col235: double('col21').primaryKey().autoincrement().default(0), + col236: double('col21').primaryKey().autoincrement().default(0), + col238: double('col21').primaryKey().autoincrement().default(0), + col239: double('col21').primaryKey().autoincrement().default(0), + col240: double('col21').primaryKey().autoincrement().default(0), + col241: double('col21').primaryKey().autoincrement().default(0), + col242: double('col21').primaryKey().autoincrement().default(0), + col243: double('col21').primaryKey().autoincrement().default(0), + col244: double('col21').primaryKey().autoincrement().default(0), + col245: double('col21').primaryKey().autoincrement().default(0), + col246: double('col21').primaryKey().autoincrement().default(0), + col248: double('col21').primaryKey().autoincrement().default(0), + col249: double('col21').primaryKey().autoincrement().default(0), + col250: double('col21').primaryKey().autoincrement().default(0), + col251: double('col21').primaryKey().autoincrement().default(0), + col252: double('col21').primaryKey().autoincrement().default(0), + col253: double('col21').primaryKey().autoincrement().default(0), + col254: double('col21').primaryKey().autoincrement().default(0), + col255: double('col21').primaryKey().autoincrement().default(0), + col256: double('col21').primaryKey().autoincrement().default(0), + col258: double('col21').primaryKey().autoincrement().default(0), + col259: double('col21').primaryKey().autoincrement().default(0), + col260: double('col21').primaryKey().autoincrement().default(0), + col261: double('col21').primaryKey().autoincrement().default(0), + col262: double('col21').primaryKey().autoincrement().default(0), + col263: double('col21').primaryKey().autoincrement().default(0), + col264: double('col21').primaryKey().autoincrement().default(0), + col265: double('col21').primaryKey().autoincrement().default(0), + col266: double('col21').primaryKey().autoincrement().default(0), + col268: double('col21').primaryKey().autoincrement().default(0), + col269: double('col21').primaryKey().autoincrement().default(0), + col270: double('col21').primaryKey().autoincrement().default(0), + col271: double('col21').primaryKey().autoincrement().default(0), + col272: double('col21').primaryKey().autoincrement().default(0), + col273: double('col21').primaryKey().autoincrement().default(0), + col274: double('col21').primaryKey().autoincrement().default(0), + col275: double('col21').primaryKey().autoincrement().default(0), + col276: double('col21').primaryKey().autoincrement().default(0), + col278: double('col21').primaryKey().autoincrement().default(0), + col279: double('col21').primaryKey().autoincrement().default(0), + col280: double('col21').primaryKey().autoincrement().default(0), + col281: double('col21').primaryKey().autoincrement().default(0), + col282: double('col21').primaryKey().autoincrement().default(0), + col283: double('col21').primaryKey().autoincrement().default(0), + col284: double('col21').primaryKey().autoincrement().default(0), + col285: double('col21').primaryKey().autoincrement().default(0), + col286: double('col21').primaryKey().autoincrement().default(0), + col288: double('col21').primaryKey().autoincrement().default(0), + col289: double('col21').primaryKey().autoincrement().default(0), + col290: double('col21').primaryKey().autoincrement().default(0), + col291: double('col21').primaryKey().autoincrement().default(0), + col292: double('col21').primaryKey().autoincrement().default(0), + col293: double('col21').primaryKey().autoincrement().default(0), + col294: double('col21').primaryKey().autoincrement().default(0), + col295: double('col21').primaryKey().autoincrement().default(0), + col296: double('col21').primaryKey().autoincrement().default(0), + col298: double('col21').primaryKey().autoincrement().default(0), + col299: double('col21').primaryKey().autoincrement().default(0), + col300: double('col3').primaryKey().autoincrement().default(0), + col301: double('col3').primaryKey().autoincrement().default(0), + col302: double('col3').primaryKey().autoincrement().default(0), + col303: double('col3').primaryKey().autoincrement().default(0), + col304: double('col3').primaryKey().autoincrement().default(0), + col305: double('col3').primaryKey().autoincrement().default(0), + col306: double('col3').primaryKey().autoincrement().default(0), + col308: double('col3').primaryKey().autoincrement().default(0), + col309: double('col3').primaryKey().autoincrement().default(0), + col310: double('col31').primaryKey().autoincrement().default(0), + col311: double('col31').primaryKey().autoincrement().default(0), + col312: double('col31').primaryKey().autoincrement().default(0), + col313: double('col31').primaryKey().autoincrement().default(0), + col314: double('col31').primaryKey().autoincrement().default(0), + col315: double('col31').primaryKey().autoincrement().default(0), + col316: double('col31').primaryKey().autoincrement().default(0), + col318: double('col31').primaryKey().autoincrement().default(0), + col319: double('col31').primaryKey().autoincrement().default(0), + col320: double('col31').primaryKey().autoincrement().default(0), + col321: double('col31').primaryKey().autoincrement().default(0), + col322: double('col31').primaryKey().autoincrement().default(0), + col323: double('col31').primaryKey().autoincrement().default(0), + col324: double('col31').primaryKey().autoincrement().default(0), + col325: double('col31').primaryKey().autoincrement().default(0), + col326: double('col31').primaryKey().autoincrement().default(0), + col328: double('col31').primaryKey().autoincrement().default(0), + col329: double('col31').primaryKey().autoincrement().default(0), + col330: double('col31').primaryKey().autoincrement().default(0), + col331: double('col31').primaryKey().autoincrement().default(0), + col332: double('col31').primaryKey().autoincrement().default(0), + col333: double('col31').primaryKey().autoincrement().default(0), + col334: double('col31').primaryKey().autoincrement().default(0), + col335: double('col31').primaryKey().autoincrement().default(0), + col336: double('col31').primaryKey().autoincrement().default(0), + col338: double('col31').primaryKey().autoincrement().default(0), + col339: double('col31').primaryKey().autoincrement().default(0), + col340: double('col31').primaryKey().autoincrement().default(0), + col341: double('col31').primaryKey().autoincrement().default(0), + col342: double('col31').primaryKey().autoincrement().default(0), + col343: double('col31').primaryKey().autoincrement().default(0), + col344: double('col31').primaryKey().autoincrement().default(0), + col345: double('col31').primaryKey().autoincrement().default(0), + col346: double('col31').primaryKey().autoincrement().default(0), + col348: double('col31').primaryKey().autoincrement().default(0), + col349: double('col31').primaryKey().autoincrement().default(0), + col350: double('col31').primaryKey().autoincrement().default(0), + col351: double('col31').primaryKey().autoincrement().default(0), + col352: double('col31').primaryKey().autoincrement().default(0), + col353: double('col31').primaryKey().autoincrement().default(0), + col354: double('col31').primaryKey().autoincrement().default(0), + col355: double('col31').primaryKey().autoincrement().default(0), + col356: double('col31').primaryKey().autoincrement().default(0), + col358: double('col31').primaryKey().autoincrement().default(0), + col359: double('col31').primaryKey().autoincrement().default(0), + col360: double('col31').primaryKey().autoincrement().default(0), + col361: double('col31').primaryKey().autoincrement().default(0), + col362: double('col31').primaryKey().autoincrement().default(0), + col363: double('col31').primaryKey().autoincrement().default(0), + col364: double('col31').primaryKey().autoincrement().default(0), + col365: double('col31').primaryKey().autoincrement().default(0), + col366: double('col31').primaryKey().autoincrement().default(0), + col368: double('col31').primaryKey().autoincrement().default(0), + col369: double('col31').primaryKey().autoincrement().default(0), + col370: double('col31').primaryKey().autoincrement().default(0), + col371: double('col31').primaryKey().autoincrement().default(0), + col372: double('col31').primaryKey().autoincrement().default(0), + col373: double('col31').primaryKey().autoincrement().default(0), + col374: double('col31').primaryKey().autoincrement().default(0), + col375: double('col31').primaryKey().autoincrement().default(0), + col376: double('col31').primaryKey().autoincrement().default(0), + col378: double('col31').primaryKey().autoincrement().default(0), + col379: double('col31').primaryKey().autoincrement().default(0), + col380: double('col31').primaryKey().autoincrement().default(0), + col381: double('col31').primaryKey().autoincrement().default(0), + col382: double('col31').primaryKey().autoincrement().default(0), + col383: double('col31').primaryKey().autoincrement().default(0), + col384: double('col31').primaryKey().autoincrement().default(0), + col385: double('col31').primaryKey().autoincrement().default(0), + col386: double('col31').primaryKey().autoincrement().default(0), + col388: double('col31').primaryKey().autoincrement().default(0), + col389: double('col31').primaryKey().autoincrement().default(0), + col390: double('col31').primaryKey().autoincrement().default(0), + col391: double('col31').primaryKey().autoincrement().default(0), + col392: double('col31').primaryKey().autoincrement().default(0), + col393: double('col31').primaryKey().autoincrement().default(0), + col394: double('col31').primaryKey().autoincrement().default(0), + col395: double('col31').primaryKey().autoincrement().default(0), + col396: double('col31').primaryKey().autoincrement().default(0), + col398: double('col31').primaryKey().autoincrement().default(0), + col399: double('col31').primaryKey().autoincrement().default(0), + col400: double('col4').primaryKey().autoincrement().default(0), + col401: double('col4').primaryKey().autoincrement().default(0), + col402: double('col4').primaryKey().autoincrement().default(0), + col403: double('col4').primaryKey().autoincrement().default(0), + col404: double('col4').primaryKey().autoincrement().default(0), + col405: double('col4').primaryKey().autoincrement().default(0), + col406: double('col4').primaryKey().autoincrement().default(0), + col408: double('col4').primaryKey().autoincrement().default(0), + col409: double('col4').primaryKey().autoincrement().default(0), + col410: double('col41').primaryKey().autoincrement().default(0), + col411: double('col41').primaryKey().autoincrement().default(0), + col412: double('col41').primaryKey().autoincrement().default(0), + col413: double('col41').primaryKey().autoincrement().default(0), + col414: double('col41').primaryKey().autoincrement().default(0), + col415: double('col41').primaryKey().autoincrement().default(0), + col416: double('col41').primaryKey().autoincrement().default(0), + col418: double('col41').primaryKey().autoincrement().default(0), + col419: double('col41').primaryKey().autoincrement().default(0), + col420: double('col41').primaryKey().autoincrement().default(0), + col421: double('col41').primaryKey().autoincrement().default(0), + col422: double('col41').primaryKey().autoincrement().default(0), + col423: double('col41').primaryKey().autoincrement().default(0), + col424: double('col41').primaryKey().autoincrement().default(0), + col425: double('col41').primaryKey().autoincrement().default(0), + col426: double('col41').primaryKey().autoincrement().default(0), + col428: double('col41').primaryKey().autoincrement().default(0), + col429: double('col41').primaryKey().autoincrement().default(0), + col430: double('col41').primaryKey().autoincrement().default(0), + col431: double('col41').primaryKey().autoincrement().default(0), + col432: double('col41').primaryKey().autoincrement().default(0), + col433: double('col41').primaryKey().autoincrement().default(0), + col434: double('col41').primaryKey().autoincrement().default(0), + col435: double('col41').primaryKey().autoincrement().default(0), + col436: double('col41').primaryKey().autoincrement().default(0), + col438: double('col41').primaryKey().autoincrement().default(0), + col439: double('col41').primaryKey().autoincrement().default(0), + col440: double('col41').primaryKey().autoincrement().default(0), + col441: double('col41').primaryKey().autoincrement().default(0), + col442: double('col41').primaryKey().autoincrement().default(0), + col443: double('col41').primaryKey().autoincrement().default(0), + col444: double('col41').primaryKey().autoincrement().default(0), + col445: double('col41').primaryKey().autoincrement().default(0), + col446: double('col41').primaryKey().autoincrement().default(0), + col448: double('col41').primaryKey().autoincrement().default(0), + col449: double('col41').primaryKey().autoincrement().default(0), + col450: double('col41').primaryKey().autoincrement().default(0), + col451: double('col41').primaryKey().autoincrement().default(0), + col452: double('col41').primaryKey().autoincrement().default(0), + col453: double('col41').primaryKey().autoincrement().default(0), + col454: double('col41').primaryKey().autoincrement().default(0), + col455: double('col41').primaryKey().autoincrement().default(0), + col456: double('col41').primaryKey().autoincrement().default(0), + col458: double('col41').primaryKey().autoincrement().default(0), + col459: double('col41').primaryKey().autoincrement().default(0), + col460: double('col41').primaryKey().autoincrement().default(0), + col461: double('col41').primaryKey().autoincrement().default(0), + col462: double('col41').primaryKey().autoincrement().default(0), + col463: double('col41').primaryKey().autoincrement().default(0), + col464: double('col41').primaryKey().autoincrement().default(0), + col465: double('col41').primaryKey().autoincrement().default(0), + col466: double('col41').primaryKey().autoincrement().default(0), + col468: double('col41').primaryKey().autoincrement().default(0), + col469: double('col41').primaryKey().autoincrement().default(0), + col470: double('col41').primaryKey().autoincrement().default(0), + col471: double('col41').primaryKey().autoincrement().default(0), + col472: double('col41').primaryKey().autoincrement().default(0), + col473: double('col41').primaryKey().autoincrement().default(0), + col474: double('col41').primaryKey().autoincrement().default(0), + col475: double('col41').primaryKey().autoincrement().default(0), + col476: double('col41').primaryKey().autoincrement().default(0), + col478: double('col41').primaryKey().autoincrement().default(0), + col479: double('col41').primaryKey().autoincrement().default(0), + col480: double('col41').primaryKey().autoincrement().default(0), + col481: double('col41').primaryKey().autoincrement().default(0), + col482: double('col41').primaryKey().autoincrement().default(0), + col483: double('col41').primaryKey().autoincrement().default(0), + col484: double('col41').primaryKey().autoincrement().default(0), + col485: double('col41').primaryKey().autoincrement().default(0), + col486: double('col41').primaryKey().autoincrement().default(0), + col488: double('col41').primaryKey().autoincrement().default(0), + col489: double('col41').primaryKey().autoincrement().default(0), + col490: double('col41').primaryKey().autoincrement().default(0), + col491: double('col41').primaryKey().autoincrement().default(0), + col492: double('col41').primaryKey().autoincrement().default(0), + col493: double('col41').primaryKey().autoincrement().default(0), + col494: double('col41').primaryKey().autoincrement().default(0), + col495: double('col41').primaryKey().autoincrement().default(0), + col496: double('col41').primaryKey().autoincrement().default(0), + col498: double('col41').primaryKey().autoincrement().default(0), + col499: double('col41').primaryKey().autoincrement().default(0), + col500: double('col5').primaryKey().autoincrement().default(0), + col501: double('col5').primaryKey().autoincrement().default(0), + col502: double('col5').primaryKey().autoincrement().default(0), + col503: double('col5').primaryKey().autoincrement().default(0), + col504: double('col5').primaryKey().autoincrement().default(0), + col505: double('col5').primaryKey().autoincrement().default(0), + col506: double('col5').primaryKey().autoincrement().default(0), + col508: double('col5').primaryKey().autoincrement().default(0), + col509: double('col5').primaryKey().autoincrement().default(0), + col510: double('col51').primaryKey().autoincrement().default(0), + col511: double('col51').primaryKey().autoincrement().default(0), + col512: double('col51').primaryKey().autoincrement().default(0), + col513: double('col51').primaryKey().autoincrement().default(0), + col514: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col515: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col516: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col518: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col519: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col520: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col521: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col522: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col523: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col524: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col525: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col526: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col528: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col529: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col530: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col531: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col532: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col533: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col534: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col535: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col536: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col538: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col539: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col540: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col541: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col542: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col543: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col544: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col545: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col546: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col548: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col549: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col550: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col551: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col552: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col553: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col554: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col555: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col556: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col558: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col559: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col560: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col561: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col562: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col563: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col564: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col565: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col566: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col568: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col569: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col570: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col571: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col572: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col573: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col574: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col575: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col576: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col578: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col579: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col580: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col581: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col582: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col583: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col584: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col585: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col586: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col588: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col589: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col590: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col591: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col592: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col593: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col594: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col595: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col596: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col598: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col599: bigint('col51', { mode: 'number' }).primaryKey().autoincrement().default(0), + col600: bigint('col6', { mode: 'number' }).primaryKey().autoincrement().default(0), + col601: double('col6').primaryKey().autoincrement().default(0), + col602: double('col6').primaryKey().autoincrement().default(0), + col603: double('col6').primaryKey().autoincrement().default(0), + col604: double('col6').primaryKey().autoincrement().default(0), + col605: double('col6').primaryKey().autoincrement().default(0), + col606: double('col6').primaryKey().autoincrement().default(0), + col608: double('col6').primaryKey().autoincrement().default(0), + col609: double('col6').primaryKey().autoincrement().default(0), + col610: double('col61').primaryKey().autoincrement().default(0), + col611: double('col61').primaryKey().autoincrement().default(0), + col612: double('col61').primaryKey().autoincrement().default(0), + col613: double('col61').primaryKey().autoincrement().default(0), + col614: double('col61').primaryKey().autoincrement().default(0), + col615: double('col61').primaryKey().autoincrement().default(0), + col616: double('col61').primaryKey().autoincrement().default(0), + col618: double('col61').primaryKey().autoincrement().default(0), + col619: double('col61').primaryKey().autoincrement().default(0), + col620: double('col61').primaryKey().autoincrement().default(0), + col621: double('col61').primaryKey().autoincrement().default(0), + col622: double('col61').primaryKey().autoincrement().default(0), + col623: double('col61').primaryKey().autoincrement().default(0), + col624: double('col61').primaryKey().autoincrement().default(0), + col625: double('col61').primaryKey().autoincrement().default(0), + col626: double('col61').primaryKey().autoincrement().default(0), + col628: double('col61').primaryKey().autoincrement().default(0), + col629: double('col61').primaryKey().autoincrement().default(0), + col630: double('col61').primaryKey().autoincrement().default(0), + col631: double('col61').primaryKey().autoincrement().default(0), + col632: double('col61').primaryKey().autoincrement().default(0), + col633: double('col61').primaryKey().autoincrement().default(0), + col634: double('col61').primaryKey().autoincrement().default(0), + col635: double('col61').primaryKey().autoincrement().default(0), + col636: double('col61').primaryKey().autoincrement().default(0), + col638: double('col61').primaryKey().autoincrement().default(0), + col639: double('col61').primaryKey().autoincrement().default(0), + col640: double('col61').primaryKey().autoincrement().default(0), + col641: double('col61').primaryKey().autoincrement().default(0), + col642: double('col61').primaryKey().autoincrement().default(0), + col643: double('col61').primaryKey().autoincrement().default(0), + col644: double('col61').primaryKey().autoincrement().default(0), + col645: double('col61').primaryKey().autoincrement().default(0), + col646: double('col61').primaryKey().autoincrement().default(0), + col648: double('col61').primaryKey().autoincrement().default(0), + col649: double('col61').primaryKey().autoincrement().default(0), + col650: double('col61').primaryKey().autoincrement().default(0), + col651: double('col61').primaryKey().autoincrement().default(0), + col652: double('col61').primaryKey().autoincrement().default(0), + col653: double('col61').primaryKey().autoincrement().default(0), + col654: double('col61').primaryKey().autoincrement().default(0), + col655: double('col61').primaryKey().autoincrement().default(0), + col656: double('col61').primaryKey().autoincrement().default(0), + col658: double('col61').primaryKey().autoincrement().default(0), + col659: double('col61').primaryKey().autoincrement().default(0), + col660: double('col61').primaryKey().autoincrement().default(0), + col661: double('col61').primaryKey().autoincrement().default(0), + col662: double('col61').primaryKey().autoincrement().default(0), + col663: double('col61').primaryKey().autoincrement().default(0), + col664: double('col61').primaryKey().autoincrement().default(0), + col665: double('col61').primaryKey().autoincrement().default(0), + col666: double('col61').primaryKey().autoincrement().default(0), + col668: double('col61').primaryKey().autoincrement().default(0), + col669: double('col61').primaryKey().autoincrement().default(0), + col670: double('col61').primaryKey().autoincrement().default(0), + col671: double('col61').primaryKey().autoincrement().default(0), + col672: double('col61').primaryKey().autoincrement().default(0), + col673: double('col61').primaryKey().autoincrement().default(0), + col674: double('col61').primaryKey().autoincrement().default(0), + col675: double('col61').primaryKey().autoincrement().default(0), + col676: double('col61').primaryKey().autoincrement().default(0), + col678: double('col61').primaryKey().autoincrement().default(0), + col679: double('col61').primaryKey().autoincrement().default(0), + col680: double('col61').primaryKey().autoincrement().default(0), + col681: double('col61').primaryKey().autoincrement().default(0), + col682: double('col61').primaryKey().autoincrement().default(0), + col683: double('col61').primaryKey().autoincrement().default(0), + col684: double('col61').primaryKey().autoincrement().default(0), + col685: double('col61').primaryKey().autoincrement().default(0), + col686: double('col61').primaryKey().autoincrement().default(0), + col688: double('col61').primaryKey().autoincrement().default(0), + col689: double('col61').primaryKey().autoincrement().default(0), + col690: double('col61').primaryKey().autoincrement().default(0), + col691: double('col61').primaryKey().autoincrement().default(0), + col692: double('col61').primaryKey().autoincrement().default(0), + col693: double('col61').primaryKey().autoincrement().default(0), + col694: double('col61').primaryKey().autoincrement().default(0), + col695: double('col61').primaryKey().autoincrement().default(0), + col696: double('col61').primaryKey().autoincrement().default(0), + col698: double('col61').primaryKey().autoincrement().default(0), + col699: double('col61').primaryKey().autoincrement().default(0), + col700: double('col7').primaryKey().autoincrement().default(0), + col701: double('col7').primaryKey().autoincrement().default(0), + col702: double('col7').primaryKey().autoincrement().default(0), + col703: double('col7').primaryKey().autoincrement().default(0), + col704: double('col7').primaryKey().autoincrement().default(0), + col705: double('col7').primaryKey().autoincrement().default(0), + col706: double('col7').primaryKey().autoincrement().default(0), + col708: double('col7').primaryKey().autoincrement().default(0), + col709: double('col7').primaryKey().autoincrement().default(0), + col710: double('col71').primaryKey().autoincrement().default(0), + col711: double('col71').primaryKey().autoincrement().default(0), + col712: double('col71').primaryKey().autoincrement().default(0), + col713: double('col71').primaryKey().autoincrement().default(0), + col714: double('col71').primaryKey().autoincrement().default(0), + col715: double('col71').primaryKey().autoincrement().default(0), + col716: double('col71').primaryKey().autoincrement().default(0), + col718: double('col71').primaryKey().autoincrement().default(0), + col719: double('col71').primaryKey().autoincrement().default(0), + col720: double('col71').primaryKey().autoincrement().default(0), + col721: double('col71').primaryKey().autoincrement().default(0), + col722: double('col71').primaryKey().autoincrement().default(0), + col723: double('col71').primaryKey().autoincrement().default(0), + col724: double('col71').primaryKey().autoincrement().default(0), + col725: double('col71').primaryKey().autoincrement().default(0), + col726: double('col71').primaryKey().autoincrement().default(0), + col728: double('col71').primaryKey().autoincrement().default(0), + col729: double('col71').primaryKey().autoincrement().default(0), + col730: double('col71').primaryKey().autoincrement().default(0), + col731: double('col71').primaryKey().autoincrement().default(0), + col732: double('col71').primaryKey().autoincrement().default(0), + col733: double('col71').primaryKey().autoincrement().default(0), + col734: double('col71').primaryKey().autoincrement().default(0), + col735: double('col71').primaryKey().autoincrement().default(0), + col736: double('col71').primaryKey().autoincrement().default(0), + col738: double('col71').primaryKey().autoincrement().default(0), + col739: double('col71').primaryKey().autoincrement().default(0), + col740: double('col71').primaryKey().autoincrement().default(0), + col741: double('col71').primaryKey().autoincrement().default(0), + col742: double('col71').primaryKey().autoincrement().default(0), + col743: double('col71').primaryKey().autoincrement().default(0), + col744: double('col71').primaryKey().autoincrement().default(0), + col745: double('col71').primaryKey().autoincrement().default(0), + col746: double('col71').primaryKey().autoincrement().default(0), + col748: double('col71').primaryKey().autoincrement().default(0), + col749: double('col71').primaryKey().autoincrement().default(0), + col750: double('col71').primaryKey().autoincrement().default(0), + col751: double('col71').primaryKey().autoincrement().default(0), + col752: double('col71').primaryKey().autoincrement().default(0), + col753: double('col71').primaryKey().autoincrement().default(0), + col754: double('col71').primaryKey().autoincrement().default(0), + col755: double('col71').primaryKey().autoincrement().default(0), + col756: double('col71').primaryKey().autoincrement().default(0), + col758: double('col71').primaryKey().autoincrement().default(0), + col759: double('col71').primaryKey().autoincrement().default(0), + col760: double('col71').primaryKey().autoincrement().default(0), + col761: double('col71').primaryKey().autoincrement().default(0), + col762: double('col71').primaryKey().autoincrement().default(0), + col763: double('col71').primaryKey().autoincrement().default(0), + col764: double('col71').primaryKey().autoincrement().default(0), + col765: double('col71').primaryKey().autoincrement().default(0), + col766: double('col71').primaryKey().autoincrement().default(0), + col768: double('col71').primaryKey().autoincrement().default(0), + col769: double('col71').primaryKey().autoincrement().default(0), + col770: double('col71').primaryKey().autoincrement().default(0), + col771: double('col71').primaryKey().autoincrement().default(0), + col772: double('col71').primaryKey().autoincrement().default(0), + col773: double('col71').primaryKey().autoincrement().default(0), + col774: double('col71').primaryKey().autoincrement().default(0), + col775: double('col71').primaryKey().autoincrement().default(0), + col776: double('col71').primaryKey().autoincrement().default(0), + col778: double('col71').primaryKey().autoincrement().default(0), + col779: double('col71').primaryKey().autoincrement().default(0), + col780: double('col71').primaryKey().autoincrement().default(0), + col781: double('col71').primaryKey().autoincrement().default(0), + col782: double('col71').primaryKey().autoincrement().default(0), + col783: double('col71').primaryKey().autoincrement().default(0), + col784: double('col71').primaryKey().autoincrement().default(0), + col785: double('col71').primaryKey().autoincrement().default(0), + col786: double('col71').primaryKey().autoincrement().default(0), + col788: double('col71').primaryKey().autoincrement().default(0), + col789: double('col71').primaryKey().autoincrement().default(0), + col790: double('col71').primaryKey().autoincrement().default(0), + col791: double('col71').primaryKey().autoincrement().default(0), + col792: double('col71').primaryKey().autoincrement().default(0), + col793: double('col71').primaryKey().autoincrement().default(0), + col794: double('col71').primaryKey().autoincrement().default(0), + col795: double('col71').primaryKey().autoincrement().default(0), + col796: double('col71').primaryKey().autoincrement().default(0), + col798: double('col71').primaryKey().autoincrement().default(0), + col799: double('col71').primaryKey().autoincrement().default(0), + col800: double('col8').primaryKey().autoincrement().default(0), + col801: double('col8').primaryKey().autoincrement().default(0), + col802: double('col8').primaryKey().autoincrement().default(0), + col803: double('col8').primaryKey().autoincrement().default(0), + col804: double('col8').primaryKey().autoincrement().default(0), + col805: double('col8').primaryKey().autoincrement().default(0), + col806: double('col8').primaryKey().autoincrement().default(0), + col808: double('col8').primaryKey().autoincrement().default(0), + col809: double('col8').primaryKey().autoincrement().default(0), + col810: double('col81').primaryKey().autoincrement().default(0), + col811: double('col81').primaryKey().autoincrement().default(0), + col812: double('col81').primaryKey().autoincrement().default(0), + col813: double('col81').primaryKey().autoincrement().default(0), + col814: double('col81').primaryKey().autoincrement().default(0), + col815: double('col81').primaryKey().autoincrement().default(0), + col816: double('col81').primaryKey().autoincrement().default(0), + col818: double('col81').primaryKey().autoincrement().default(0), + col819: double('col81').primaryKey().autoincrement().default(0), + col820: double('col81').primaryKey().autoincrement().default(0), + col821: double('col81').primaryKey().autoincrement().default(0), + col822: double('col81').primaryKey().autoincrement().default(0), + col823: double('col81').primaryKey().autoincrement().default(0), + col824: double('col81').primaryKey().autoincrement().default(0), + col825: double('col81').primaryKey().autoincrement().default(0), + col826: double('col81').primaryKey().autoincrement().default(0), + col828: double('col81').primaryKey().autoincrement().default(0), + col829: double('col81').primaryKey().autoincrement().default(0), + col830: double('col81').primaryKey().autoincrement().default(0), + col831: double('col81').primaryKey().autoincrement().default(0), + col832: double('col81').primaryKey().autoincrement().default(0), + col833: double('col81').primaryKey().autoincrement().default(0), + col834: double('col81').primaryKey().autoincrement().default(0), + col835: double('col81').primaryKey().autoincrement().default(0), + col836: double('col81').primaryKey().autoincrement().default(0), + col838: double('col81').primaryKey().autoincrement().default(0), + col839: double('col81').primaryKey().autoincrement().default(0), + col840: double('col81').primaryKey().autoincrement().default(0), + col841: double('col81').primaryKey().autoincrement().default(0), + col842: double('col81').primaryKey().autoincrement().default(0), + col843: double('col81').primaryKey().autoincrement().default(0), + col844: double('col81').primaryKey().autoincrement().default(0), + col845: double('col81').primaryKey().autoincrement().default(0), + col846: double('col81').primaryKey().autoincrement().default(0), + col848: double('col81').primaryKey().autoincrement().default(0), + col849: double('col81').primaryKey().autoincrement().default(0), + col850: double('col81').primaryKey().autoincrement().default(0), + col851: double('col81').primaryKey().autoincrement().default(0), + col852: double('col81').primaryKey().autoincrement().default(0), + col853: double('col81').primaryKey().autoincrement().default(0), + col854: double('col81').primaryKey().autoincrement().default(0), + col855: double('col81').primaryKey().autoincrement().default(0), + col856: double('col81').primaryKey().autoincrement().default(0), + col858: double('col81').primaryKey().autoincrement().default(0), + col859: double('col81').primaryKey().autoincrement().default(0), + col860: double('col81').primaryKey().autoincrement().default(0), + col861: double('col81').primaryKey().autoincrement().default(0), + col862: double('col81').primaryKey().autoincrement().default(0), + col863: double('col81').primaryKey().autoincrement().default(0), + col864: double('col81').primaryKey().autoincrement().default(0), + col865: double('col81').primaryKey().autoincrement().default(0), + col866: double('col81').primaryKey().autoincrement().default(0), + col868: double('col81').primaryKey().autoincrement().default(0), + col869: double('col81').primaryKey().autoincrement().default(0), + col870: double('col81').primaryKey().autoincrement().default(0), + col871: double('col81').primaryKey().autoincrement().default(0), + col872: double('col81').primaryKey().autoincrement().default(0), + col873: double('col81').primaryKey().autoincrement().default(0), + col874: double('col81').primaryKey().autoincrement().default(0), + col875: double('col81').primaryKey().autoincrement().default(0), + col876: double('col81').primaryKey().autoincrement().default(0), + col878: double('col81').primaryKey().autoincrement().default(0), + col879: double('col81').primaryKey().autoincrement().default(0), + col880: double('col81').primaryKey().autoincrement().default(0), + col881: double('col81').primaryKey().autoincrement().default(0), + col882: double('col81').primaryKey().autoincrement().default(0), + col883: double('col81').primaryKey().autoincrement().default(0), + col884: double('col81').primaryKey().autoincrement().default(0), + col885: double('col81').primaryKey().autoincrement().default(0), + col886: double('col81').primaryKey().autoincrement().default(0), + col888: double('col81').primaryKey().autoincrement().default(0), + col889: double('col81').primaryKey().autoincrement().default(0), + col890: double('col81').primaryKey().autoincrement().default(0), + col891: double('col81').primaryKey().autoincrement().default(0), + col892: double('col81').primaryKey().autoincrement().default(0), + col893: double('col81').primaryKey().autoincrement().default(0), + col894: double('col81').primaryKey().autoincrement().default(0), + col895: double('col81').primaryKey().autoincrement().default(0), + col896: double('col81').primaryKey().autoincrement().default(0), + col898: double('col81').primaryKey().autoincrement().default(0), + col899: double('col81').primaryKey().autoincrement().default(0), + col900: double('col9').primaryKey().autoincrement().default(0), + col901: double('col9').primaryKey().autoincrement().default(0), + col902: double('col9').primaryKey().autoincrement().default(0), + col903: double('col9').primaryKey().autoincrement().default(0), + col904: double('col9').primaryKey().autoincrement().default(0), + col905: double('col9').primaryKey().autoincrement().default(0), + col906: double('col9').primaryKey().autoincrement().default(0), + col908: double('col9').primaryKey().autoincrement().default(0), + col909: double('col9').primaryKey().autoincrement().default(0), + col910: double('col91').primaryKey().autoincrement().default(0), + col911: double('col91').primaryKey().autoincrement().default(0), + col912: double('col91').primaryKey().autoincrement().default(0), + col913: double('col91').primaryKey().autoincrement().default(0), + col914: double('col91').primaryKey().autoincrement().default(0), + col915: double('col91').primaryKey().autoincrement().default(0), + col916: double('col91').primaryKey().autoincrement().default(0), + col918: double('col91').primaryKey().autoincrement().default(0), + col919: double('col91').primaryKey().autoincrement().default(0), + col920: double('col91').primaryKey().autoincrement().default(0), + col921: double('col91').primaryKey().autoincrement().default(0), + col922: double('col91').primaryKey().autoincrement().default(0), + col923: double('col91').primaryKey().autoincrement().default(0), + col924: double('col91').primaryKey().autoincrement().default(0), + col925: double('col91').primaryKey().autoincrement().default(0), + col926: double('col91').primaryKey().autoincrement().default(0), + col928: double('col91').primaryKey().autoincrement().default(0), + col929: double('col91').primaryKey().autoincrement().default(0), + col930: double('col91').primaryKey().autoincrement().default(0), + col931: double('col91').primaryKey().autoincrement().default(0), + col932: double('col91').primaryKey().autoincrement().default(0), + col933: double('col91').primaryKey().autoincrement().default(0), + col934: double('col91').primaryKey().autoincrement().default(0), + col935: double('col91').primaryKey().autoincrement().default(0), + col936: double('col91').primaryKey().autoincrement().default(0), + col938: double('col91').primaryKey().autoincrement().default(0), + col939: double('col91').primaryKey().autoincrement().default(0), + col940: double('col91').primaryKey().autoincrement().default(0), + col941: double('col91').primaryKey().autoincrement().default(0), + col942: double('col91').primaryKey().autoincrement().default(0), + col943: double('col91').primaryKey().autoincrement().default(0), + col944: varchar('col91', { length: 200 }).primaryKey().default('0'), + col945: varchar('col91', { length: 200 }).primaryKey().default('0'), + col946: varchar('col91', { length: 200 }).primaryKey().default('0'), + col948: varchar('col91', { length: 200 }).primaryKey().default('0'), + col949: varchar('col91', { length: 200 }).primaryKey().default('0'), + col950: varchar('col91', { length: 200 }).primaryKey().default('0'), + col951: varchar('col91', { length: 200 }).primaryKey().default('0'), + col952: varchar('col91', { length: 200 }).primaryKey().default('0'), + col953: varchar('col91', { length: 200 }).primaryKey().default('0'), + col954: varchar('col91', { length: 200 }).primaryKey().default('0'), + col955: varchar('col91', { length: 200 }).primaryKey().default('0'), + col956: varchar('col91', { length: 200 }).primaryKey().default('0'), + col958: varchar('col91', { length: 200 }).primaryKey().default('0'), + col959: varchar('col91', { length: 200 }).primaryKey().default('0'), + col960: varchar('col91', { length: 200 }).primaryKey().default('0'), + col961: varchar('col91', { length: 200 }).primaryKey().default('0'), + col962: varchar('col91', { length: 200 }).primaryKey().default('0'), + col963: varchar('col91', { length: 200 }).primaryKey().default('0'), + col964: varchar('col91', { length: 200 }).primaryKey().default('0'), + col965: varchar('col91', { length: 200 }).primaryKey().default('0'), + col966: varchar('col91', { length: 200 }).primaryKey().default('0'), + col968: varchar('col91', { length: 200 }).primaryKey().default('0'), + col969: varchar('col91', { length: 200 }).primaryKey().default('0'), + col970: varchar('col91', { length: 200 }).primaryKey().default('0'), + col971: varchar('col91', { length: 200 }).primaryKey().default('0'), + col972: varchar('col91', { length: 200 }).primaryKey().default('0'), + col973: varchar('col91', { length: 200 }).primaryKey().default('0'), + col974: varchar('col91', { length: 200 }).primaryKey().default('0'), + col975: varchar('col91', { length: 200 }).primaryKey().default('0'), + col976: varchar('col91', { length: 200 }).primaryKey().default('0'), + col978: varchar('col91', { length: 200 }).primaryKey().default('0'), + col979: varchar('col91', { length: 200 }).primaryKey().default('0'), + col980: varchar('col91', { length: 200 }).primaryKey().default('0'), + col981: varchar('col91', { length: 200 }).primaryKey().default('0'), + col982: varchar('col91', { length: 200 }).primaryKey().default('0'), + col983: varchar('col91', { length: 200 }).primaryKey().default('0'), + col984: varchar('col91', { length: 200 }).primaryKey().default('0'), + col985: varchar('col91', { length: 200 }).primaryKey().default('0'), + col986: varchar('col91', { length: 200 }).primaryKey().default('0'), + col988: varchar('col91', { length: 200 }).primaryKey().default('0'), + col989: varchar('col91', { length: 200 }).primaryKey().default('0'), + col990: varchar('col91', { length: 200 }).primaryKey().default('0'), + col991: varchar('col91', { length: 200 }).primaryKey().default('0'), + col992: varchar('col91', { length: 200 }).primaryKey().default('0'), + col993: varchar('col91', { length: 200 }).primaryKey().default('0'), + col994: varchar('col91', { length: 200 }).primaryKey().default('0'), + col995: varchar('col91', { length: 200 }).primaryKey().default('0'), + col996: varchar('col91', { length: 200 }).primaryKey().default('0'), + col998: varchar('col91', { length: 200 }).primaryKey().default('0'), + col999: varchar('col91', { length: 200 }).primaryKey().default('0'), +}); diff --git a/drizzle-orm/type-tests/singlestore/db.ts b/drizzle-orm/type-tests/singlestore/db.ts new file mode 100644 index 000000000..bde149b08 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/db.ts @@ -0,0 +1,14 @@ +import { createPool } from 'mysql2/promise'; +import { drizzle } from '~/singlestore/index.ts'; + +const pool = createPool({}); + +export const db = drizzle(pool); + +{ + drizzle(pool); + // @ts-expect-error - missing mode + drizzle(pool, { schema: {} }); + drizzle(pool, { schema: {}, mode: 'default' }); + drizzle(pool, { mode: 'default' }); +} diff --git a/drizzle-orm/type-tests/singlestore/delete.ts b/drizzle-orm/type-tests/singlestore/delete.ts new file mode 100644 index 000000000..0fce8882e --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/delete.ts @@ -0,0 +1,61 @@ +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { eq } from '~/expressions.ts'; +import type { SingleStoreDelete } from '~/singlestore-core/index.ts'; +import type { SingleStoreRawQueryResult } from '~/singlestore/index.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; +import { users } from './tables.ts'; + +const deleteAll = await db.delete(users); +Expect>; + +const deleteAllStmt = db.delete(users).prepare(); +const deleteAllPrepared = await deleteAllStmt.execute(); +Expect>; + +const deleteWhere = await db.delete(users).where(eq(users.id, 1)); +Expect>; + +const deleteWhereStmt = db.delete(users).where(eq(users.id, 1)).prepare(); +const deleteWherePrepared = await deleteWhereStmt.execute(); +Expect>; + +const deleteReturningAll = await db.delete(users); +Expect>; + +const deleteReturningAllStmt = db.delete(users).prepare(); +const deleteReturningAllPrepared = await deleteReturningAllStmt.execute(); +Expect>; + +const deleteReturningPartial = await db.delete(users); +Expect>; + +const deleteReturningPartialStmt = db.delete(users).prepare(); +const deleteReturningPartialPrepared = await deleteReturningPartialStmt.execute(); +Expect>; + +{ + function dynamic(qb: T) { + return qb.where(sql``); + } + + const qbBase = db.delete(users).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + Expect>; +} + +{ + db + .delete(users) + .where(sql``) + // @ts-expect-error method was already called + .where(sql``); + + db + .delete(users) + .$dynamic() + .where(sql``) + .where(sql``); +} diff --git a/drizzle-orm/type-tests/singlestore/generated-columns.ts b/drizzle-orm/type-tests/singlestore/generated-columns.ts new file mode 100644 index 000000000..e33a9d61b --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/generated-columns.ts @@ -0,0 +1,158 @@ +import { type Equal, Expect } from 'type-tests/utils'; +import { type InferInsertModel, type InferSelectModel, sql } from '~/index'; +import { drizzle } from '~/singlestore'; +import { serial, singlestoreTable, text, varchar } from '~/singlestore-core'; +import { db } from './db'; + +const users = singlestoreTable( + 'users', + { + id: serial('id').primaryKey(), + firstName: varchar('first_name', { length: 255 }), + lastName: varchar('last_name', { length: 255 }), + email: text('email').notNull(), + fullName: text('full_name').generatedAlwaysAs(sql`concat_ws(first_name, ' ', last_name)`), + upperName: text('upper_name').generatedAlwaysAs( + sql` case when first_name is null then null else upper(first_name) end `, + ).$type(), // There is no way for drizzle to detect nullability in these cases. This is how the user can work around it + }, +); +{ + type User = typeof users.$inferSelect; + type NewUser = typeof users.$inferInsert; + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + }, + User + > + >(); + + Expect< + Equal< + { + email: string; + id?: number | undefined; + firstName?: string | null | undefined; + lastName?: string | null | undefined; + }, + NewUser + > + >(); +} + +{ + type User = InferSelectModel; + type NewUser = InferInsertModel; + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + }, + User + > + >(); + + Expect< + Equal< + { + email: string; + id?: number | undefined; + firstName?: string | null | undefined; + lastName?: string | null | undefined; + }, + NewUser + > + >(); +} + +{ + const dbUsers = await db.select().from(users); + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + }[], + typeof dbUsers + > + >(); +} + +{ + const db = drizzle({} as any, { schema: { users }, mode: 'default' }); + + const dbUser = await db.query.users.findFirst(); + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + } | undefined, + typeof dbUser + > + >(); +} + +{ + const db = drizzle({} as any, { schema: { users }, mode: 'default' }); + + const dbUser = await db.query.users.findMany(); + + Expect< + Equal< + { + id: number; + firstName: string | null; + lastName: string | null; + email: string; + fullName: string | null; + upperName: string | null; + }[], + typeof dbUser + > + >(); +} + +{ + // @ts-expect-error - Can't use the fullName because it's a generated column + await db.insert(users).values({ + firstName: 'test', + lastName: 'test', + email: 'test', + fullName: 'test', + }); +} + +{ + await db.update(users).set({ + firstName: 'test', + lastName: 'test', + email: 'test', + // @ts-expect-error - Can't use the fullName because it's a generated column + fullName: 'test', + }); +} diff --git a/drizzle-orm/type-tests/singlestore/insert.ts b/drizzle-orm/type-tests/singlestore/insert.ts new file mode 100644 index 000000000..738bf669d --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/insert.ts @@ -0,0 +1,135 @@ +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { int, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import type { SingleStoreInsert } from '~/singlestore-core/index.ts'; +import type { SingleStoreRawQueryResult } from '~/singlestore/index.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; +import { users } from './tables.ts'; + +const singlestoreInsertReturning = await db.insert(users).values({ + // ^? + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).$returningId(); + +Expect>; + +const insert = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertPrepared = await insertStmt.execute(); +Expect>; + +const insertSql = await db.insert(users).values({ + homeCity: sql`123`, + class: 'A', + age1: 1, + enumCol: sql`foobar`, +}); +Expect>; + +const insertSqlStmt = db.insert(users).values({ + homeCity: sql`123`, + class: 'A', + age1: 1, + enumCol: sql`foobar`, +}).prepare(); +const insertSqlPrepared = await insertSqlStmt.execute(); +Expect>; + +const insertReturning = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertReturningStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertReturningPrepared = await insertReturningStmt.execute(); +Expect>; + +const insertReturningPartial = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}); +Expect>; + +const insertReturningPartialStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: 1, + enumCol: 'a', +}).prepare(); +const insertReturningPartialPrepared = await insertReturningPartialStmt.execute(); +Expect>; + +const insertReturningSql = await db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: sql`2 + 2`, + enumCol: 'a', +}); +Expect>; + +const insertReturningSqlStmt = db.insert(users).values({ + homeCity: 1, + class: 'A', + age1: sql`2 + 2`, + enumCol: 'a', +}).prepare(); +const insertReturningSqlPrepared = await insertReturningSqlStmt.execute(); +Expect>; + +{ + const users = singlestoreTable('users', { + id: int('id').autoincrement().primaryKey(), + name: text('name').notNull(), + age: int('age'), + occupation: text('occupation'), + }); + + await db.insert(users).values({ name: 'John Wick', age: 58, occupation: 'housekeeper' }); +} + +{ + function dynamic(qb: T) { + return qb.onDuplicateKeyUpdate({ set: {} }); + } + + const qbBase = db.insert(users).values({ age1: 0, class: 'A', enumCol: 'a', homeCity: 0 }).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + + Expect>; +} + +{ + db + .insert(users) + .values({ age1: 0, class: 'A', enumCol: 'a', homeCity: 0 }) + .onDuplicateKeyUpdate({ set: {} }) + // @ts-expect-error method was already called + .onDuplicateKeyUpdate({ set: {} }); +} diff --git a/drizzle-orm/type-tests/singlestore/select.ts b/drizzle-orm/type-tests/singlestore/select.ts new file mode 100644 index 000000000..10a7551a7 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/select.ts @@ -0,0 +1,606 @@ +import { + and, + between, + eq, + exists, + gt, + gte, + ilike, + inArray, + isNotNull, + isNull, + like, + lt, + lte, + ne, + not, + notBetween, + notExists, + notIlike, + notInArray, + notLike, + or, +} from '~/expressions.ts'; +import { alias } from '~/singlestore-core/alias.ts'; +import { param, sql } from '~/sql/sql.ts'; + +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { QueryBuilder, type SingleStoreSelect, type SingleStoreSelectQueryBuilder } from '~/singlestore-core/index.ts'; +import { db } from './db.ts'; +import { cities, classes, newYorkers, users } from './tables.ts'; + +const city = alias(cities, 'city'); +const city1 = alias(cities, 'city1'); + +const join = await db + .select({ + users, + cities, + city, + city1: { + id: city1.id, + }, + }) + .from(users) + .leftJoin(cities, eq(users.id, cities.id)) + .rightJoin(city, eq(city.id, users.id)) + .rightJoin(city1, eq(city1.id, users.id)); + +Expect< + Equal< + { + users: { + id: number; + text: string | null; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + } | null; + cities: { + id: number; + name: string; + population: number | null; + } | null; + city: { + id: number; + name: string; + population: number | null; + } | null; + city1: { + id: number; + }; + }[], + typeof join + > +>; + +const join2 = await db + .select({ + userId: users.id, + cityId: cities.id, + }) + .from(users) + .fullJoin(cities, eq(users.id, cities.id)); + +Expect< + Equal< + { + userId: number | null; + cityId: number | null; + }[], + typeof join2 + > +>; + +const join3 = await db + .select({ + userId: users.id, + cityId: cities.id, + classId: classes.id, + }) + .from(users) + .fullJoin(cities, eq(users.id, cities.id)) + .rightJoin(classes, eq(users.id, classes.id)); + +Expect< + Equal< + { + userId: number | null; + cityId: number | null; + classId: number; + }[], + typeof join3 + > +>; + +db + .select() + .from(users) + .where(exists(db.select().from(cities).where(eq(users.homeCity, cities.id)))); + +function mapFunkyFuncResult(valueFromDriver: unknown) { + return { + foo: (valueFromDriver as Record)['foo'], + }; +} + +const age = 1; + +const allOperators = await db + .select({ + col2: sql`5 - ${users.id} + 1`, // unknown + col3: sql`${users.id} + 1`, // number + col33: sql`${users.id} + 1`.mapWith(users.id), // number + col34: sql`${users.id} + 1`.mapWith(mapFunkyFuncResult), // number + col4: sql`one_or_another(${users.id}, ${users.class})`, // string | number + col5: sql`true`, // unknown + col6: sql`true`, // boolean + col7: sql`random()`, // number + col8: sql`some_funky_func(${users.id})`.mapWith(mapFunkyFuncResult), // { foo: string } + col9: sql`greatest(${users.createdAt}, ${param(new Date(), users.createdAt)})`, // unknown + col10: sql`date_or_false(${users.createdAt}, ${param(new Date(), users.createdAt)})`, // Date | boolean + col11: sql`${users.age1} + ${age}`, // unknown + col12: sql`${users.age1} + ${param(age, users.age1)}`, // unknown + col13: sql`lower(${users.class})`, // unknown + col14: sql`length(${users.class})`, // number + count: sql`count(*)::int`, // number + }) + .from(users) + .where(and( + eq(users.id, 1), + ne(users.id, 1), + or(eq(users.id, 1), ne(users.id, 1)), + not(eq(users.id, 1)), + gt(users.id, 1), + gte(users.id, 1), + lt(users.id, 1), + lte(users.id, 1), + inArray(users.id, [1, 2, 3]), + inArray(users.id, db.select({ id: users.id }).from(users)), + inArray(users.id, sql`select id from ${users}`), + notInArray(users.id, [1, 2, 3]), + notInArray(users.id, db.select({ id: users.id }).from(users)), + notInArray(users.id, sql`select id from ${users}`), + isNull(users.subClass), + isNotNull(users.id), + exists(db.select({ id: users.id }).from(users)), + exists(sql`select id from ${users}`), + notExists(db.select({ id: users.id }).from(users)), + notExists(sql`select id from ${users}`), + between(users.id, 1, 2), + notBetween(users.id, 1, 2), + like(users.id, '%1%'), + notLike(users.id, '%1%'), + ilike(users.id, '%1%'), + notIlike(users.id, '%1%'), + )); + +Expect< + Equal<{ + col2: unknown; + col3: number; + col33: number; + col34: { foo: any }; + col4: string | number; + col5: unknown; + col6: boolean; + col7: number; + col8: { + foo: any; + }; + col9: unknown; + col10: boolean | Date; + col11: unknown; + col12: unknown; + col13: unknown; + col14: number; + count: number; + }[], typeof allOperators> +>; + +const textSelect = await db + .select({ + t: users.text, + }) + .from(users); + +Expect>; + +const homeCity = alias(cities, 'homeCity'); +const c = alias(classes, 'c'); +const otherClass = alias(classes, 'otherClass'); +const anotherClass = alias(classes, 'anotherClass'); +const friend = alias(users, 'friend'); +const currentCity = alias(cities, 'currentCity'); +const subscriber = alias(users, 'subscriber'); +const closestCity = alias(cities, 'closestCity'); + +const megaJoin = await db + .select({ + user: { + id: users.id, + maxAge: sql`max(${users.age1})`, + }, + city: { + id: cities.id, + }, + homeCity, + c, + otherClass, + anotherClass, + friend, + currentCity, + subscriber, + closestCity, + }) + .from(users) + .innerJoin(cities, sql`${users.id} = ${cities.id}`) + .innerJoin(homeCity, sql`${users.homeCity} = ${homeCity.id}`) + .innerJoin(c, eq(c.id, users.class)) + .innerJoin(otherClass, sql`${c.id} = ${otherClass.id}`) + .innerJoin(anotherClass, sql`${users.class} = ${anotherClass.id}`) + .innerJoin(friend, sql`${users.id} = ${friend.id}`) + .innerJoin(currentCity, sql`${homeCity.id} = ${currentCity.id}`) + .innerJoin(subscriber, sql`${users.class} = ${subscriber.id}`) + .innerJoin(closestCity, sql`${users.currentCity} = ${closestCity.id}`) + .where(and(sql`${users.age1} > 0`, eq(cities.id, 1))) + .limit(1) + .offset(1); + +Expect< + Equal< + { + user: { + id: number; + maxAge: unknown; + }; + city: { + id: number; + }; + homeCity: { + id: number; + name: string; + population: number | null; + }; + c: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + otherClass: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + anotherClass: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + friend: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + currentCity: { + id: number; + name: string; + population: number | null; + }; + subscriber: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + closestCity: { + id: number; + name: string; + population: number | null; + }; + }[], + typeof megaJoin + > +>; + +const friends = alias(users, 'friends'); + +const join4 = await db + .select({ + user: { + id: users.id, + }, + city: { + id: cities.id, + }, + class: classes, + friend: friends, + }) + .from(users) + .innerJoin(cities, sql`${users.id} = ${cities.id}`) + .innerJoin(classes, sql`${cities.id} = ${classes.id}`) + .innerJoin(friends, sql`${friends.id} = ${users.id}`) + .where(sql`${users.age1} > 0`); + +Expect< + Equal<{ + user: { + id: number; + }; + city: { + id: number; + }; + class: { + id: number; + class: 'A' | 'C' | null; + subClass: 'B' | 'D'; + }; + friend: { + id: number; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + text: string | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }; + }[], typeof join4> +>; + +{ + const authenticated = false as boolean; + + const result = await db + .select({ + id: users.id, + ...(authenticated ? { city: users.homeCity } : {}), + }) + .from(users); + + Expect< + Equal< + { + id: number; + city?: number; + }[], + typeof result + > + >; +} + +await db.select().from(users).for('update'); +await db.select().from(users).for('share', { skipLocked: true }); +await db.select().from(users).for('update', { noWait: true }); +await db + .select() + .from(users) + // @ts-expect-error - can't use both skipLocked and noWait + .for('share', { noWait: true, skipLocked: true }); + +{ + const result = await db.select().from(newYorkers); + Expect< + Equal< + { + userId: number; + cityId: number | null; + }[], + typeof result + > + >; +} + +{ + const result = await db.select({ userId: newYorkers.userId }).from(newYorkers); + Expect< + Equal< + { + userId: number; + }[], + typeof result + > + >; +} + +{ + const query = db.select().from(users).prepare().iterator(); + for await (const row of query) { + Expect>(); + } +} + +{ + db + .select() + .from(users) + .where(eq(users.id, 1)); + + db + .select() + .from(users) + .where(eq(users.id, 1)) + // @ts-expect-error - can't use where twice + .where(eq(users.id, 1)); + + db + .select() + .from(users) + .where(eq(users.id, 1)) + .limit(10) + // @ts-expect-error - can't use where twice + .where(eq(users.id, 1)); +} + +{ + function withFriends(qb: T) { + const friends = alias(users, 'friends'); + const friends2 = alias(users, 'friends2'); + const friends3 = alias(users, 'friends3'); + const friends4 = alias(users, 'friends4'); + const friends5 = alias(users, 'friends5'); + return qb + .leftJoin(friends, sql`true`) + .leftJoin(friends2, sql`true`) + .leftJoin(friends3, sql`true`) + .leftJoin(friends4, sql`true`) + .leftJoin(friends5, sql`true`); + } + + const qb = db.select().from(users).$dynamic(); + const result = await withFriends(qb); + Expect< + Equal + >; +} + +{ + function withFriends(qb: T) { + const friends = alias(users, 'friends'); + const friends2 = alias(users, 'friends2'); + const friends3 = alias(users, 'friends3'); + const friends4 = alias(users, 'friends4'); + const friends5 = alias(users, 'friends5'); + return qb + .leftJoin(friends, sql`true`) + .leftJoin(friends2, sql`true`) + .leftJoin(friends3, sql`true`) + .leftJoin(friends4, sql`true`) + .leftJoin(friends5, sql`true`); + } + + const qb = db.select().from(users).$dynamic(); + const result = await withFriends(qb); + Expect< + Equal + >; +} + +{ + function dynamic(qb: T) { + return qb.where(sql``).having(sql``).groupBy(sql``).orderBy(sql``).limit(1).offset(1).for('update'); + } + + const qb = db.select().from(users).$dynamic(); + const result = await dynamic(qb); + Expect>; +} + +{ + // TODO: add to docs + function dynamic(qb: T) { + return qb.where(sql``).having(sql``).groupBy(sql``).orderBy(sql``).limit(1).offset(1).for('update'); + } + + const query = new QueryBuilder().select().from(users).$dynamic(); + dynamic(query); +} + +{ + // TODO: add to docs + function paginated(qb: T, page: number) { + return qb.limit(10).offset((page - 1) * 10); + } + + const qb = db.select().from(users).$dynamic(); + const result = await paginated(qb, 1); + + Expect>; +} + +{ + db + .select() + .from(users) + .where(sql``) + .limit(10) + // @ts-expect-error method was already called + .where(sql``); + + db + .select() + .from(users) + .having(sql``) + .limit(10) + // @ts-expect-error method was already called + .having(sql``); + + db + .select() + .from(users) + .groupBy(sql``) + .limit(10) + // @ts-expect-error method was already called + .groupBy(sql``); + + db + .select() + .from(users) + .orderBy(sql``) + .limit(10) + // @ts-expect-error method was already called + .orderBy(sql``); + + db + .select() + .from(users) + .limit(10) + .where(sql``) + // @ts-expect-error method was already called + .limit(10); + + db + .select() + .from(users) + .offset(10) + .limit(10) + // @ts-expect-error method was already called + .offset(10); + + db + .select() + .from(users) + .for('update') + .limit(10) + // @ts-expect-error method was already called + .for('update'); +} diff --git a/drizzle-orm/type-tests/singlestore/set-operators.ts b/drizzle-orm/type-tests/singlestore/set-operators.ts new file mode 100644 index 000000000..8bd434262 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/set-operators.ts @@ -0,0 +1,286 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import { eq } from '~/expressions.ts'; +import { + except, + exceptAll, + intersect, + intersectAll, + type SingleStoreSetOperator, + union, + unionAll, +} from '~/singlestore-core/index.ts'; +import { desc, sql } from '~/sql/index.ts'; +import { db } from './db.ts'; +import { cities, classes, newYorkers, users } from './tables.ts'; + +const unionTest = await db + .select({ id: users.id }) + .from(users) + .union( + db + .select({ id: users.id }) + .from(users), + ); + +Expect>; + +const unionAllTest = await db + .select({ id: users.id, age: users.age1 }) + .from(users) + .unionAll( + db.select({ id: users.id, age: users.age1 }) + .from(users) + .leftJoin(cities, eq(users.id, cities.id)), + ); + +Expect>; + +const intersectTest = await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + .intersect(({ intersect }) => + intersect( + db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users), + db + .select({ id: users.id, homeCity: sql`${users.homeCity}`.mapWith(Number) }) + .from(users), + ) + ); + +Expect>; + +const intersectAllTest = await db + .select({ id: users.id, homeCity: users.class }) + .from(users) + .intersect( + db + .select({ id: users.id, homeCity: users.class }) + .from(users) + .leftJoin(cities, eq(users.id, cities.id)), + ); + +Expect>; + +const exceptTest = await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + .except( + db + .select({ id: users.id, homeCity: sql`${users.homeCity}`.mapWith(Number) }) + .from(users), + ); + +Expect>; + +const exceptAllTest = await db + .select({ id: users.id, homeCity: users.class }) + .from(users) + .except( + db + .select({ id: users.id, homeCity: sql<'A' | 'C'>`${users.class}` }) + .from(users), + ); + +Expect>; + +const union2Test = await union(db.select().from(cities), db.select().from(cities), db.select().from(cities)); + +Expect>; + +const unionAll2Test = await unionAll( + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select().from(cities), +); + +Expect>; + +const intersect2Test = await intersect( + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), + db.select({ + id: cities.id, + name: cities.name, + population: cities.population, + }).from(cities), +); + +Expect>; + +const intersectAll2Test = await intersectAll( + union( + db.select({ + id: cities.id, + }).from(cities), + db.select({ + id: cities.id, + }) + .from(cities).where(sql``), + ), + db.select({ + id: cities.id, + }) + .from(cities), +).orderBy(desc(cities.id)).limit(23); + +Expect>; + +const except2Test = await except( + db.select({ + userId: newYorkers.userId, + }) + .from(newYorkers), + db.select({ + userId: newYorkers.userId, + }).from(newYorkers), +); + +Expect>; + +const exceptAll2Test = await exceptAll( + db.select({ + userId: newYorkers.userId, + cityId: newYorkers.cityId, + }) + .from(newYorkers).where(sql``), + db.select({ + userId: newYorkers.userId, + cityId: newYorkers.cityId, + }).from(newYorkers).leftJoin(users, sql``), +); + +Expect>; + +const unionfull = await union(db.select().from(users), db.select().from(users)).orderBy(sql``).limit(1).offset(2); + +Expect< + Equal<{ + id: number; + text: string | null; + homeCity: number; + currentCity: number | null; + serialNullable: number; + serialNotNull: number; + class: 'A' | 'C'; + subClass: 'B' | 'D' | null; + age1: number; + createdAt: Date; + enumCol: 'a' | 'b' | 'c'; + }[], typeof unionfull> +>; + +union(db.select().from(users), db.select().from(users)) + .orderBy(sql``) + // @ts-expect-error - method was already called + .orderBy(sql``); + +union(db.select().from(users), db.select().from(users)) + .offset(1) + // @ts-expect-error - method was already called + .offset(2); + +union(db.select().from(users), db.select().from(users)) + .orderBy(sql``) + // @ts-expect-error - method was already called + .orderBy(sql``); + +{ + function dynamic(qb: T) { + return qb.orderBy(sql``).limit(1).offset(2); + } + + const qb = union(db.select().from(users), db.select().from(users)).$dynamic(); + const result = await dynamic(qb); + Expect>; +} + +await db + .select({ id: users.id, homeCity: users.homeCity }) + .from(users) + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + .intersect(({ intersect }) => intersect(db.select().from(users), db.select().from(users))); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select().from(classes).union(db.select({ id: classes.id }).from(classes)); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select({ id: classes.id }).from(classes).union(db.select().from(classes).where(sql``)); + +// All queries in combining statements should return the same number of columns +// and the corresponding columns should have compatible data type +// @ts-expect-error +db.select({ id: classes.id }).from(classes).union(db.select().from(classes)); + +union( + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``), + db.select({ id: cities.id, name: cities.name }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select().from(cities), +); + +union( + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name, population: cities.population }).from(cities), + db.select({ id: cities.id, name: cities.name }).from(cities).where(sql``).limit(3).$dynamic(), + db.select({ id: cities.id, name: cities.name }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: newYorkers.userId }).from(newYorkers), + db.select({ id: cities.id }).from(cities), +); + +union( + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities), + db.select({ id: cities.id }).from(cities).where(sql``), + db.select({ id: sql`${cities.id}` }).from(cities), + db.select({ id: cities.id }).from(cities), + // All queries in combining statements should return the same number of columns + // and the corresponding columns should have compatible data type + // @ts-expect-error + db.select({ id: cities.id, name: cities.name, population: cities.population }).from(cities).where(sql``), +); diff --git a/drizzle-orm/type-tests/singlestore/subquery.ts b/drizzle-orm/type-tests/singlestore/subquery.ts new file mode 100644 index 000000000..e8ee4e80b --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/subquery.ts @@ -0,0 +1,97 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, eq } from '~/expressions.ts'; +import { alias, int, serial, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import { sql } from '~/sql/sql.ts'; +import type { DrizzleTypeError, Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = singlestoreTable('names', { + id: serial('id').primaryKey(), + name: text('name'), + authorId: int('author_id'), +}); + +const n1 = db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: sql`count(1)::int`.as('count1'), + }) + .from(names) + .groupBy(names.id, names.name, names.authorId) + .as('n1'); + +const n2 = db + .select({ + id: names.id, + authorId: names.authorId, + totalCount: sql`count(1)::int`.as('totalCount'), + }) + .from(names) + .groupBy(names.id, names.authorId) + .as('n2'); + +const result = await db + .select({ + name: n1.name, + authorId: n1.authorId, + count1: n1.count1, + totalCount: n2.totalCount, + }) + .from(n1) + .innerJoin(n2, and(eq(n2.id, n1.id), eq(n2.authorId, n1.authorId))); + +Expect< + Equal< + { + name: string | null; + authorId: number | null; + count1: number; + totalCount: number; + }[], + typeof result + > +>; + +const names2 = alias(names, 'names2'); + +const sq1 = db + .select({ + id: names.id, + name: names.name, + id2: names2.id, + }) + .from(names) + .leftJoin(names2, eq(names.name, names2.name)) + .as('sq1'); + +const res = await db.select().from(sq1); + +Expect< + Equal< + { + id: number; + name: string | null; + id2: number | null; + }[], + typeof res + > +>; + +{ + const sq = db.select({ count: sql`count(1)::int` }).from(names).as('sq'); + Expect ? true : false>; +} + +const sqUnion = db.select().from(names).union(db.select().from(names2)).as('sqUnion'); + +const resUnion = await db.select().from(sqUnion); + +Expect< + Equal<{ + id: number; + name: string | null; + authorId: number | null; + }[], typeof resUnion> +>; diff --git a/drizzle-orm/type-tests/singlestore/tables.ts b/drizzle-orm/type-tests/singlestore/tables.ts new file mode 100644 index 000000000..18ed96a30 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/tables.ts @@ -0,0 +1,751 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import { eq, gt } from '~/expressions.ts'; +import type { BuildColumn, InferSelectModel, Simplify } from '~/index.ts'; +import { + bigint, + char, + customType, + date, + datetime, + decimal, + index, + int, + json, + longtext, + mediumtext, + primaryKey, + serial, + type SingleStoreColumn, + singlestoreEnum, + singlestoreSchema, + singlestoreTable, + text, + timestamp, + tinytext, + unique, + uniqueIndex, + varchar, +} from '~/singlestore-core/index.ts'; + +import { singlestoreView, type SingleStoreViewWithSelection } from '~/singlestore-core/view.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; + +export const users = singlestoreTable( + 'users_table', + { + id: serial('id').primaryKey(), + homeCity: int('home_city') + .notNull(), + currentCity: int('current_city'), + serialNullable: serial('serial1'), + serialNotNull: serial('serial2').notNull(), + class: text('class', { enum: ['A', 'C'] }).notNull(), + subClass: text('sub_class', { enum: ['B', 'D'] }), + text: text('text'), + age1: int('age1').notNull(), + createdAt: timestamp('created_at', { mode: 'date' }).notNull().defaultNow(), + enumCol: singlestoreEnum('enum_col', ['a', 'b', 'c']).notNull(), + }, + (users) => ({ + usersAge1Idx: uniqueIndex('usersAge1Idx').on(users.class), + usersAge2Idx: index('usersAge2Idx').on(users.class), + uniqueClass: uniqueIndex('uniqueClass') + .on(users.class, users.subClass) + .lock('default') + .algorythm('copy') + .using(`btree`), + pk: primaryKey(users.age1, users.class), + }), +); + +export const cities = singlestoreTable('cities_table', { + id: serial('id').primaryKey(), + name: text('name_db').notNull(), + population: int('population').default(0), +}, (cities) => ({ + citiesNameIdx: index('citiesNameIdx').on(cities.id), +})); + +Expect< + Equal< + { + id: SingleStoreColumn<{ + name: 'id'; + tableName: 'cities_table'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + isPrimaryKey: true; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isAutoincrement: true; + hasRuntimeDefault: false; + }, object>; + name: SingleStoreColumn<{ + name: 'name_db'; + tableName: 'cities_table'; + dataType: 'string'; + columnType: 'SingleStoreText'; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + isAutoincrement: false; + hasRuntimeDefault: false; + }, object>; + population: SingleStoreColumn<{ + name: 'population'; + tableName: 'cities_table'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + notNull: false; + hasDefault: true; + isPrimaryKey: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isAutoincrement: false; + hasRuntimeDefault: false; + }, object>; + }, + typeof cities._.columns + > +>; + +Expect< + Equal<{ + id: number; + name_db: string; + population: number | null; + }, InferSelectModel> +>; + +Expect< + Equal<{ + id?: number; + name: string; + population?: number | null; + }, typeof cities.$inferInsert> +>; + +export const customSchema = singlestoreSchema('custom_schema'); + +export const citiesCustom = customSchema.table('cities_table', { + id: serial('id').primaryKey(), + name: text('name_db').notNull(), + population: int('population').default(0), +}, (cities) => ({ + citiesNameIdx: index('citiesNameIdx').on(cities.id), +})); + +Expect>; + +export const classes = singlestoreTable('classes_table', { + id: serial('id').primaryKey(), + class: text('class', { enum: ['A', 'C'] }), + subClass: text('sub_class', { enum: ['B', 'D'] }).notNull(), +}); + +/* export const classes2 = singlestoreTable('classes_table', { + id: serial().primaryKey(), + class: text({ enum: ['A', 'C'] }).$dbName('class_db'), + subClass: text({ enum: ['B', 'D'] }).notNull(), +}); */ + +export const newYorkers = singlestoreView('new_yorkers') + .algorithm('merge') + .definer('root@localhost') + .sqlSecurity('definer') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + +Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: false; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > +>; + +{ + const newYorkers = customSchema.view('new_yorkers') + .algorithm('merge') + .definer('root@localhost') + .sqlSecurity('definer') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: true; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'id'; + dataType: 'number'; + columnType: 'SingleStoreSerial'; + data: number; + driverParam: number; + notNull: false; + hasDefault: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: true; + isAutoincrement: true; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = singlestoreView('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }) + .algorithm('merge') + .definer('root@localhost') + .sqlSecurity('definer') + .as( + sql`select ${users.id} as user_id, ${cities.id} as city_id from ${users} left join ${cities} on ${ + eq(cities.id, users.homeCity) + } where ${gt(users.age1, 18)}`, + ); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = customSchema.view('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }) + .algorithm('merge') + .definer('root@localhost') + .sqlSecurity('definer') + .as( + sql`select ${users.id} as user_id, ${cities.id} as city_id from ${users} left join ${cities} on ${ + eq(cities.id, users.homeCity) + } where ${gt(users.age1, 18)}`, + ); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', false, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = singlestoreView('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }).existing(); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', true, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const newYorkers = customSchema.view('new_yorkers', { + userId: int('user_id').notNull(), + cityId: int('city_id'), + }).existing(); + + Expect< + Equal< + SingleStoreViewWithSelection<'new_yorkers', true, { + userId: SingleStoreColumn<{ + name: 'user_id'; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + hasDefault: false; + notNull: true; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + cityId: SingleStoreColumn<{ + name: 'city_id'; + notNull: false; + hasDefault: false; + dataType: 'number'; + columnType: 'SingleStoreInt'; + data: number; + driverParam: string | number; + tableName: 'new_yorkers'; + enumValues: undefined; + baseColumn: never; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }>; + }>, + typeof newYorkers + > + >; +} + +{ + const customText = customType<{ data: string }>({ + dataType() { + return 'text'; + }, + }); + + const t = customText('name').notNull(); + Expect< + Equal< + { + brand: 'Column'; + name: 'name'; + tableName: 'table'; + dataType: 'custom'; + columnType: 'SingleStoreCustomColumn'; + data: string; + driverParam: unknown; + notNull: true; + hasDefault: false; + enumValues: undefined; + baseColumn: never; + dialect: 'singlestore'; + generated: undefined; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + }, + Simplify['_']> + > + >; +} + +{ + singlestoreTable('test', { + bigint: bigint('bigint', { mode: 'bigint' }), + number: bigint('number', { mode: 'number' }), + date: date('date').default(new Date()), + date2: date('date2', { mode: 'date' }).default(new Date()), + date3: date('date3', { mode: 'string' }).default('2020-01-01'), + date4: date('date4', { mode: undefined }).default(new Date()), + datetime: datetime('datetime').default(new Date()), + datetime2: datetime('datetime2', { mode: 'date' }).default(new Date()), + datetime3: datetime('datetime3', { mode: 'string' }).default('2020-01-01'), + datetime4: datetime('datetime4', { mode: undefined }).default(new Date()), + timestamp: timestamp('timestamp').default(new Date()), + timestamp2: timestamp('timestamp2', { mode: 'date' }).default(new Date()), + timestamp3: timestamp('timestamp3', { mode: 'string' }).default('2020-01-01'), + timestamp4: timestamp('timestamp4', { mode: undefined }).default(new Date()), + }); +} + +{ + singlestoreTable('test', { + col1: decimal('col1').default('1'), + }); +} + +{ + const test = singlestoreTable('test', { + test1: singlestoreEnum('test', ['a', 'b', 'c'] as const).notNull(), + test2: singlestoreEnum('test', ['a', 'b', 'c']).notNull(), + test3: varchar('test', { length: 255, enum: ['a', 'b', 'c'] as const }).notNull(), + test4: varchar('test', { length: 255, enum: ['a', 'b', 'c'] }).notNull(), + test5: text('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test6: text('test', { enum: ['a', 'b', 'c'] }).notNull(), + test7: tinytext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test8: tinytext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test9: mediumtext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test10: mediumtext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test11: longtext('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test12: longtext('test', { enum: ['a', 'b', 'c'] }).notNull(), + test13: char('test', { enum: ['a', 'b', 'c'] as const }).notNull(), + test14: char('test', { enum: ['a', 'b', 'c'] }).notNull(), + test15: text('test').notNull(), + }); + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; +} + +{ // All types with generated columns + const test = singlestoreTable('test', { + test1: singlestoreEnum('test', ['a', 'b', 'c'] as const).generatedAlwaysAs(sql``), + test2: singlestoreEnum('test', ['a', 'b', 'c']).generatedAlwaysAs(sql``), + test3: varchar('test', { length: 255, enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test4: varchar('test', { length: 255, enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test5: text('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test6: text('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test7: tinytext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test8: tinytext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test9: mediumtext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test10: mediumtext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test11: longtext('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test12: longtext('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test13: char('test', { enum: ['a', 'b', 'c'] as const }).generatedAlwaysAs(sql``), + test14: char('test', { enum: ['a', 'b', 'c'] }).generatedAlwaysAs(sql``), + test15: text('test').generatedAlwaysAs(sql``), + }); + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; + Expect>; +} + +{ + const getUsersTable = (schemaName: TSchema) => { + return singlestoreSchema(schemaName).table('users', { + id: int('id').primaryKey(), + name: text('name').notNull(), + }); + }; + + const users1 = getUsersTable('id1'); + Expect>; + + const users2 = getUsersTable('id2'); + Expect>; +} + +{ + const internalStaff = singlestoreTable('internal_staff', { + userId: int('user_id').notNull(), + }); + + const customUser = singlestoreTable('custom_user', { + id: int('id').notNull(), + }); + + const ticket = singlestoreTable('ticket', { + staffId: int('staff_id').notNull(), + }); + + const subq = db + .select() + .from(internalStaff) + .leftJoin( + customUser, + eq(internalStaff.userId, customUser.id), + ).as('internal_staff'); + + const mainQuery = await db + .select() + .from(ticket) + .leftJoin(subq, eq(subq.internal_staff.userId, ticket.staffId)); + + Expect< + Equal<{ + internal_staff: { + internal_staff: { + userId: number; + }; + custom_user: { + id: number | null; + }; + } | null; + ticket: { + staffId: number; + }; + }[], typeof mainQuery> + >; +} + +{ + const newYorkers = singlestoreView('new_yorkers') + .as((qb) => { + const sq = qb + .$with('sq') + .as( + qb.select({ userId: users.id, cityId: cities.id }) + .from(users) + .leftJoin(cities, eq(cities.id, users.homeCity)) + .where(sql`${users.age1} > 18`), + ); + return qb.with(sq).select().from(sq).where(sql`${users.homeCity} = 1`); + }); + + await db.select().from(newYorkers).leftJoin(newYorkers, eq(newYorkers.userId, newYorkers.userId)); +} + +{ + const test = singlestoreTable('test', { + id: text('id').$defaultFn(() => crypto.randomUUID()).primaryKey(), + }); + + Expect< + Equal<{ + id?: string; + }, typeof test.$inferInsert> + >; +} + +{ + singlestoreTable('test', { + id: int('id').$default(() => 1), + id2: int('id').$defaultFn(() => 1), + // @ts-expect-error - should be number + id3: int('id').$default(() => '1'), + // @ts-expect-error - should be number + id4: int('id').$defaultFn(() => '1'), + }); +} +{ + const emailLog = singlestoreTable( + 'email_log', + { + id: int('id', { unsigned: true }).autoincrement().notNull(), + clientId: int('id_client', { unsigned: true }), + receiverEmail: varchar('receiver_email', { length: 255 }).notNull(), + messageId: varchar('message_id', { length: 255 }), + contextId: int('context_id', { unsigned: true }), + contextType: singlestoreEnum('context_type', ['test']).$type<['test']>(), + action: varchar('action', { length: 80 }).$type<['test']>(), + events: json('events').$type<{ t: 'test' }[]>(), + createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp('updated_at', { mode: 'string' }).defaultNow().onUpdateNow(), + }, + (table) => { + return { + emailLogId: primaryKey({ columns: [table.id], name: 'email_log_id' }), + emailLogMessageIdUnique: unique('email_log_message_id_unique').on(table.messageId), + }; + }, + ); + + Expect< + Equal<{ + receiverEmail: string; + id?: number | undefined; + createdAt?: string | undefined; + clientId?: number | null | undefined; + messageId?: string | null | undefined; + contextId?: number | null | undefined; + contextType?: ['test'] | null | undefined; + action?: ['test'] | null | undefined; + events?: + | { + t: 'test'; + }[] + | null + | undefined; + updatedAt?: string | null | undefined; + }, typeof emailLog.$inferInsert> + >; +} diff --git a/drizzle-orm/type-tests/singlestore/update.ts b/drizzle-orm/type-tests/singlestore/update.ts new file mode 100644 index 000000000..3f10ae2e4 --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/update.ts @@ -0,0 +1,26 @@ +import { type Equal, Expect } from 'type-tests/utils.ts'; +import type { SingleStoreUpdate } from '~/singlestore-core/index.ts'; +import type { SingleStoreRawQueryResult } from '~/singlestore/session.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; +import { users } from './tables.ts'; + +{ + function dynamic(qb: T) { + return qb.where(sql``); + } + + const qbBase = db.update(users).set({}).$dynamic(); + const qb = dynamic(qbBase); + const result = await qb; + Expect>; +} + +{ + db + .update(users) + .set({}) + .where(sql``) + // @ts-expect-error method was already called + .where(sql``); +} diff --git a/drizzle-orm/type-tests/singlestore/with.ts b/drizzle-orm/type-tests/singlestore/with.ts new file mode 100644 index 000000000..77309e32a --- /dev/null +++ b/drizzle-orm/type-tests/singlestore/with.ts @@ -0,0 +1,80 @@ +import type { Equal } from 'type-tests/utils.ts'; +import { Expect } from 'type-tests/utils.ts'; +import { gt, inArray } from '~/expressions.ts'; +import { int, serial, singlestoreTable, text } from '~/singlestore-core/index.ts'; +import { sql } from '~/sql/sql.ts'; +import { db } from './db.ts'; + +const orders = singlestoreTable('orders', { + id: serial('id').primaryKey(), + region: text('region').notNull(), + product: text('product').notNull(), + amount: int('amount').notNull(), + quantity: int('quantity').notNull(), + generated: text('generatedText').generatedAlwaysAs(sql``), +}); + +{ + const regionalSales = db + .$with('regional_sales') + .as( + db + .select({ + region: orders.region, + totalSales: sql`sum(${orders.amount})`.as('total_sales'), + }) + .from(orders) + .groupBy(orders.region), + ); + + const topRegions = db + .$with('top_regions') + .as( + db + .select({ + region: orders.region, + totalSales: orders.amount, + }) + .from(regionalSales) + .where( + gt( + regionalSales.totalSales, + db.select({ sales: sql`sum(${regionalSales.totalSales})/10` }).from(regionalSales), + ), + ), + ); + + const result = await db + .with(regionalSales, topRegions) + .select({ + region: orders.region, + product: orders.product, + productUnits: sql`sum(${orders.quantity})`, + productSales: sql`sum(${orders.amount})`, + }) + .from(orders) + .where(inArray(orders.region, db.select({ region: topRegions.region }).from(topRegions))); + + Expect< + Equal<{ + region: string; + product: string; + productUnits: number; + productSales: number; + }[], typeof result> + >; + + const allOrdersWith = db.$with('all_orders_with').as(db.select().from(orders)); + const allFromWith = await db.with(allOrdersWith).select().from(allOrdersWith); + + Expect< + Equal<{ + id: number; + region: string; + product: string; + amount: number; + quantity: number; + generated: string | null; + }[], typeof allFromWith> + >; +} diff --git a/drizzle-orm/type-tests/sqlite/count.ts b/drizzle-orm/type-tests/sqlite/count.ts new file mode 100644 index 000000000..04350f000 --- /dev/null +++ b/drizzle-orm/type-tests/sqlite/count.ts @@ -0,0 +1,61 @@ +import { Expect } from 'type-tests/utils.ts'; +import { and, gt, ne } from '~/expressions.ts'; +import { integer, sqliteTable, text } from '~/sqlite-core/index.ts'; +import type { Equal } from '~/utils.ts'; +import { db } from './db.ts'; + +const names = sqliteTable('names', { + id: integer('id').primaryKey(), + name: text('name'), + authorId: integer('author_id'), +}); + +const separate = await db.$count(names); + +const separateFilters = await db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))); + +const embedded = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names).as('count1'), + }) + .from(names); + +const embeddedFilters = await db + .select({ + id: names.id, + name: names.name, + authorId: names.authorId, + count1: db.$count(names, and(gt(names.id, 1), ne(names.name, 'forbidden'))).as('count1'), + }) + .from(names); + +Expect>; + +Expect>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embedded + > +>; + +Expect< + Equal< + { + id: number; + name: string | null; + authorId: number | null; + count1: number; + }[], + typeof embeddedFilters + > +>; diff --git a/examples/pg-proxy/package-lock.json b/examples/pg-proxy/package-lock.json index 0dcfbee7b..4f0fa429f 100644 --- a/examples/pg-proxy/package-lock.json +++ b/examples/pg-proxy/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "axios": "^1.5.1", - "express": "^4.18.2", + "express": "^4.21.0", "express-rate-limit": "^7.0.2", "pg": "^8.11.3" }, @@ -601,20 +601,20 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", + "qs": "6.13.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -655,12 +655,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -746,9 +752,9 @@ } }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -776,6 +782,22 @@ "ms": "2.0.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -854,13 +876,32 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es5-ext": { "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", @@ -1005,36 +1046,36 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1072,12 +1113,12 @@ "dev": true }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -1157,19 +1198,26 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1218,6 +1266,17 @@ "node": ">=10" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hanji": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/hanji/-/hanji-0.0.5.tgz", @@ -1228,21 +1287,21 @@ "sisteransi": "^1.0.5" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "function-bind": "^1.1.1" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">= 0.4.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -1261,6 +1320,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/heap": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", @@ -1379,9 +1449,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -1456,9 +1529,12 @@ "dev": true }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1503,9 +1579,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/pg": { "version": "8.11.3", @@ -1658,11 +1734,11 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -1680,9 +1756,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -1727,9 +1803,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -1749,38 +1825,66 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" diff --git a/examples/pg-proxy/package.json b/examples/pg-proxy/package.json index 1440f4047..7804fd9c1 100644 --- a/examples/pg-proxy/package.json +++ b/examples/pg-proxy/package.json @@ -13,7 +13,7 @@ "license": "ISC", "dependencies": { "axios": "^1.5.1", - "express": "^4.18.2", + "express": "^4.21.0", "express-rate-limit": "^7.0.2", "pg": "^8.11.3" }, diff --git a/integration-tests/package.json b/integration-tests/package.json index a4fcab0b2..78f36fe30 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -15,6 +15,7 @@ "license": "Apache-2.0", "private": true, "devDependencies": { + "@libsql/client": "^0.10.0", "@neondatabase/serverless": "0.9.0", "@originjs/vite-plugin-commonjs": "^1.0.3", "@paralleldrive/cuid2": "^2.2.2", @@ -41,7 +42,6 @@ "@aws-sdk/client-rds-data": "^3.549.0", "@aws-sdk/credential-providers": "^3.549.0", "@electric-sql/pglite": "^0.1.1", - "@libsql/client": "^0.5.6", "@miniflare/d1": "^2.14.2", "@miniflare/shared": "^2.14.2", "@planetscale/database": "^1.16.0", diff --git a/integration-tests/tests/mysql/mysql-common.ts b/integration-tests/tests/mysql/mysql-common.ts index 58f7a1e2c..8a2fb768b 100644 --- a/integration-tests/tests/mysql/mysql-common.ts +++ b/integration-tests/tests/mysql/mysql-common.ts @@ -3577,29 +3577,237 @@ export function tests(driver?: string) { await db.execute(sql`drop view ${newYorkers1}`); }); - }); - test('limit 0', async (ctx) => { - const { db } = ctx.mysql; + test('$count separate', async (ctx) => { + const { db } = ctx.mysql; - await db.insert(usersTable).values({ name: 'John' }); - const users = await db - .select() - .from(usersTable) - .limit(0); + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); - expect(users).toEqual([]); - }); + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(4); + }); + + test('$count embedded', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + }); + + test('$count separate reuse', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.$count(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual(4); + expect(count2).toStrictEqual(5); + expect(count3).toStrictEqual(6); + }); + + test('$count embedded reuse', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); - test('limit -1', async (ctx) => { - const { db } = ctx.mysql; + const count = db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); - await db.insert(usersTable).values({ name: 'John' }); - const users = await db - .select() - .from(usersTable) - .limit(-1); + const count1 = await count; - expect(users.length).toBeGreaterThan(0); + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + expect(count2).toStrictEqual([ + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + ]); + expect(count3).toStrictEqual([ + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + ]); + }); + + test('$count separate with filters', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable, gt(countTestTable.id, 1)); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(3); + }); + + test('$count embedded with filters', async (ctx) => { + const { db } = ctx.mysql; + + const countTestTable = mysqlTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable, gt(countTestTable.id, 1)), + }).from(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 3 }, + { count: 3 }, + { count: 3 }, + { count: 3 }, + ]); + }); + + test('limit 0', async (ctx) => { + const { db } = ctx.mysql; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db + .select() + .from(usersTable) + .limit(0); + + expect(users).toEqual([]); + }); + + test('limit -1', async (ctx) => { + const { db } = ctx.mysql; + + await db.insert(usersTable).values({ name: 'John' }); + const users = await db + .select() + .from(usersTable) + .limit(-1); + + expect(users.length).toBeGreaterThan(0); + }); }); } diff --git a/integration-tests/tests/pg/awsdatapi.test.ts b/integration-tests/tests/pg/awsdatapi.test.ts index 8ee39cf12..3bb884c0c 100644 --- a/integration-tests/tests/pg/awsdatapi.test.ts +++ b/integration-tests/tests/pg/awsdatapi.test.ts @@ -799,19 +799,18 @@ test('migrator : default migration strategy', async () => { }); test('migrator : migrate with custom schema', async () => { - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); await migrate(db, { migrationsFolder: './drizzle2/pg', - migrationsSchema: customSchema, + migrationsSchema: 'custom_migrations', }); // test if the custom migrations table was created const { rows } = await db.execute( - sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`, + sql`select * from custom_migrations."__drizzle_migrations";`, ); expect(rows).toBeTruthy(); expect(rows!.length).toBeGreaterThan(0); @@ -824,7 +823,7 @@ test('migrator : migrate with custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); await db.execute( - sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`, + sql`drop table custom_migrations."__drizzle_migrations"`, ); }); @@ -858,7 +857,6 @@ test('migrator : migrate with custom table', async () => { test('migrator : migrate with custom table and custom schema', async () => { const customTable = randomString(); - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); @@ -866,12 +864,12 @@ test('migrator : migrate with custom table and custom schema', async () => { await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable, - migrationsSchema: customSchema, + migrationsSchema: 'custom_migrations', }); // test if the custom migrations table was created const { rows } = await db.execute( - sql`select * from ${sql.identifier(customSchema)}.${ + sql`select * from custom_migrations.${ sql.identifier( customTable, ) @@ -888,7 +886,7 @@ test('migrator : migrate with custom table and custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); await db.execute( - sql`drop table ${sql.identifier(customSchema)}.${ + sql`drop table custom_migrations.${ sql.identifier( customTable, ) diff --git a/integration-tests/tests/pg/neon-http.test.ts b/integration-tests/tests/pg/neon-http.test.ts index 1476e9628..319c84f40 100644 --- a/integration-tests/tests/pg/neon-http.test.ts +++ b/integration-tests/tests/pg/neon-http.test.ts @@ -68,15 +68,14 @@ test('migrator : default migration strategy', async () => { }); test('migrator : migrate with custom schema', async () => { - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); - await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema }); + await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' }); // test if the custom migrations table was created - const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`); + const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`); expect(rowCount && rowCount > 0).toBeTruthy(); // test if the migrated table are working as expected @@ -86,7 +85,7 @@ test('migrator : migrate with custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`); + await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`); }); test('migrator : migrate with custom table', async () => { @@ -113,7 +112,6 @@ test('migrator : migrate with custom table', async () => { test('migrator : migrate with custom table and custom schema', async () => { const customTable = randomString(); - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); @@ -121,12 +119,12 @@ test('migrator : migrate with custom table and custom schema', async () => { await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable, - migrationsSchema: customSchema, + migrationsSchema: 'custom_migrations', }); // test if the custom migrations table was created const { rowCount } = await db.execute( - sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`, + sql`select * from custom_migrations.${sql.identifier(customTable)};`, ); expect(rowCount && rowCount > 0).toBeTruthy(); @@ -137,7 +135,7 @@ test('migrator : migrate with custom table and custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`); + await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`); }); test('all date and time columns without timezone first case mode string', async () => { diff --git a/integration-tests/tests/pg/pg-common.ts b/integration-tests/tests/pg/pg-common.ts index c48a533f9..8550f5ae4 100644 --- a/integration-tests/tests/pg/pg-common.ts +++ b/integration-tests/tests/pg/pg-common.ts @@ -74,7 +74,7 @@ import { } from 'drizzle-orm/pg-core'; import getPort from 'get-port'; import { v4 as uuidV4 } from 'uuid'; -import { afterAll, beforeEach, describe, expect, test } from 'vitest'; +import { afterAll, afterEach, beforeEach, describe, expect, test } from 'vitest'; import { Expect } from '~/utils'; import type { schema } from './neon-http-batch.test'; // eslint-disable-next-line @typescript-eslint/no-import-type-side-effects @@ -246,6 +246,7 @@ export function tests() { await db.execute(sql`drop schema if exists public cascade`); await db.execute(sql`drop schema if exists ${mySchema} cascade`); await db.execute(sql`create schema public`); + await db.execute(sql`create schema if not exists custom_migrations`); await db.execute(sql`create schema ${mySchema}`); // public users await db.execute( @@ -377,6 +378,11 @@ export function tests() { ); }); + afterEach(async (ctx) => { + const { db } = ctx.pg; + await db.execute(sql`drop schema if exists custom_migrations cascade`); + }); + async function setupSetOperationTest(db: PgDatabase) { await db.execute(sql`drop table if exists users2`); await db.execute(sql`drop table if exists cities`); @@ -4660,5 +4666,213 @@ export function tests() { jsonbNumberField: testNumber, }]); }); + + test('$count separate', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(4); + }); + + test('$count embedded', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + }); + + test('$count separate reuse', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.$count(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual(4); + expect(count2).toStrictEqual(5); + expect(count3).toStrictEqual(6); + }); + + test('$count embedded reuse', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + expect(count2).toStrictEqual([ + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + ]); + expect(count3).toStrictEqual([ + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + ]); + }); + + test('$count separate with filters', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable, gt(countTestTable.id, 1)); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(3); + }); + + test('$count embedded with filters', async (ctx) => { + const { db } = ctx.pg; + + const countTestTable = pgTable('count_test', { + id: integer('id').notNull(), + name: text('name').notNull(), + }); + + await db.execute(sql`drop table if exists ${countTestTable}`); + await db.execute(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable, gt(countTestTable.id, 1)), + }).from(countTestTable); + + await db.execute(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 3 }, + { count: 3 }, + { count: 3 }, + { count: 3 }, + ]); + }); }); } diff --git a/integration-tests/tests/pg/vercel-pg.test.ts b/integration-tests/tests/pg/vercel-pg.test.ts index 3f1248d9b..ecf1d22ac 100644 --- a/integration-tests/tests/pg/vercel-pg.test.ts +++ b/integration-tests/tests/pg/vercel-pg.test.ts @@ -77,15 +77,14 @@ test('migrator : default migration strategy', async () => { }); test('migrator : migrate with custom schema', async () => { - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); - await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: customSchema }); + await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsSchema: 'custom_migrations' }); // test if the custom migrations table was created - const { rowCount } = await db.execute(sql`select * from ${sql.identifier(customSchema)}."__drizzle_migrations";`); + const { rowCount } = await db.execute(sql`select * from custom_migrations."__drizzle_migrations";`); expect(rowCount && rowCount > 0).toBeTruthy(); // test if the migrated table are working as expected @@ -95,7 +94,7 @@ test('migrator : migrate with custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}."__drizzle_migrations"`); + await db.execute(sql`drop table custom_migrations."__drizzle_migrations"`); }); test('migrator : migrate with custom table', async () => { @@ -122,7 +121,6 @@ test('migrator : migrate with custom table', async () => { test('migrator : migrate with custom table and custom schema', async () => { const customTable = randomString(); - const customSchema = randomString(); await db.execute(sql`drop table if exists all_columns`); await db.execute(sql`drop table if exists users12`); await db.execute(sql`drop table if exists "drizzle"."__drizzle_migrations"`); @@ -130,12 +128,12 @@ test('migrator : migrate with custom table and custom schema', async () => { await migrate(db, { migrationsFolder: './drizzle2/pg', migrationsTable: customTable, - migrationsSchema: customSchema, + migrationsSchema: 'custom_migrations', }); // test if the custom migrations table was created const { rowCount } = await db.execute( - sql`select * from ${sql.identifier(customSchema)}.${sql.identifier(customTable)};`, + sql`select * from custom_migrations.${sql.identifier(customTable)};`, ); expect(rowCount && rowCount > 0).toBeTruthy(); @@ -146,7 +144,7 @@ test('migrator : migrate with custom table and custom schema', async () => { await db.execute(sql`drop table all_columns`); await db.execute(sql`drop table users12`); - await db.execute(sql`drop table ${sql.identifier(customSchema)}.${sql.identifier(customTable)}`); + await db.execute(sql`drop table custom_migrations.${sql.identifier(customTable)}`); }); test('all date and time columns without timezone first case mode string', async () => { diff --git a/integration-tests/tests/sqlite/sqlite-common.ts b/integration-tests/tests/sqlite/sqlite-common.ts index be452bcf1..e8ddb86e6 100644 --- a/integration-tests/tests/sqlite/sqlite-common.ts +++ b/integration-tests/tests/sqlite/sqlite-common.ts @@ -2679,6 +2679,214 @@ export function tests() { expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); } }); + + test('$count separate', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable); + + await db.run(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(4); + }); + + test('$count embedded', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + await db.run(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + }); + + test('$count separate reuse', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.$count(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.run(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual(4); + expect(count2).toStrictEqual(5); + expect(count3).toStrictEqual(6); + }); + + test('$count embedded reuse', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = db.select({ + count: db.$count(countTestTable), + }).from(countTestTable); + + const count1 = await count; + + await db.insert(countTestTable).values({ id: 5, name: 'fifth' }); + + const count2 = await count; + + await db.insert(countTestTable).values({ id: 6, name: 'sixth' }); + + const count3 = await count; + + await db.run(sql`drop table ${countTestTable}`); + + expect(count1).toStrictEqual([ + { count: 4 }, + { count: 4 }, + { count: 4 }, + { count: 4 }, + ]); + expect(count2).toStrictEqual([ + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + { count: 5 }, + ]); + expect(count3).toStrictEqual([ + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + { count: 6 }, + ]); + }); + + test('$count separate with filters', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.$count(countTestTable, gt(countTestTable.id, 1)); + + await db.run(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual(3); + }); + + test('$count embedded with filters', async (ctx) => { + const { db } = ctx.sqlite; + + const countTestTable = sqliteTable('count_test', { + id: int('id').notNull(), + name: text('name').notNull(), + }); + + await db.run(sql`drop table if exists ${countTestTable}`); + await db.run(sql`create table ${countTestTable} (id int, name text)`); + + await db.insert(countTestTable).values([ + { id: 1, name: 'First' }, + { id: 2, name: 'Second' }, + { id: 3, name: 'Third' }, + { id: 4, name: 'Fourth' }, + ]); + + const count = await db.select({ + count: db.$count(countTestTable, gt(countTestTable.id, 1)), + }).from(countTestTable); + + await db.run(sql`drop table ${countTestTable}`); + + expect(count).toStrictEqual([ + { count: 3 }, + { count: 3 }, + { count: 3 }, + { count: 3 }, + ]); + }); }); test('table configs: unique third param', () => { diff --git a/package.json b/package.json index 4e7bd4e91..9b5c6739f 100755 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test": "turbo run test --color", "t": "pnpm test", "test:types": "turbo run test:types --color", - "lint": "concurrently -n eslint,dprint \"eslint --ext ts .\" \"dprint check --list-different\"" + "lint": "concurrently -n eslint,dprint \"eslint --ext ts .\" \"dprint check --list-different\"", + "lint:fix": "npx dprint fmt" }, "devDependencies": { "@arethetypeswrong/cli": "^0.15.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2d091ad6..2870df804 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,7 +45,7 @@ importers: version: link:drizzle-orm/dist drizzle-orm-old: specifier: npm:drizzle-orm@^0.27.2 - version: drizzle-orm@0.27.2(@aws-sdk/client-rds-data@3.583.0)(@cloudflare/workers-types@4.20240524.0)(@libsql/client@0.5.6)(@neondatabase/serverless@0.9.3)(@opentelemetry/api@1.8.0)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(@vercel/postgres@0.8.0)(better-sqlite3@9.6.0)(bun-types@1.0.3)(knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.11.0)(pg@8.11.5)(sqlite3@5.1.7))(kysely@0.25.0)(mysql2@3.11.0)(pg@8.11.5)(postgres@3.4.4)(sql.js@1.10.3)(sqlite3@5.1.7) + version: drizzle-orm@0.27.2(@aws-sdk/client-rds-data@3.583.0)(@cloudflare/workers-types@4.20240524.0)(@libsql/client@0.10.0)(@neondatabase/serverless@0.9.3)(@opentelemetry/api@1.8.0)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(@vercel/postgres@0.8.0)(better-sqlite3@9.6.0)(bun-types@1.0.3)(knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7))(kysely@0.25.0)(mysql2@3.3.3)(pg@8.11.5)(postgres@3.4.4)(sql.js@1.10.3)(sqlite3@5.1.7) eslint: specifier: ^8.50.0 version: 8.50.0 @@ -123,8 +123,8 @@ importers: specifier: ^0.2.1 version: 0.2.2(hono@4.5.0)(zod@3.23.7) '@libsql/client': - specifier: ^0.4.2 - version: 0.4.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + specifier: ^0.10.0 + version: 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) '@neondatabase/serverless': specifier: ^0.9.1 version: 0.9.3 @@ -182,6 +182,9 @@ importers: better-sqlite3: specifier: ^9.4.3 version: 9.6.0 + bun-types: + specifier: ^0.6.6 + version: 0.6.14 camelcase: specifier: ^7.0.1 version: 7.0.1 @@ -303,14 +306,14 @@ importers: specifier: ^0.1.1 version: 0.1.5 '@libsql/client': - specifier: ^0.5.6 - version: 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + specifier: ^0.10.0 + version: 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) '@neondatabase/serverless': specifier: ^0.9.0 version: 0.9.0 '@op-engineering/op-sqlite': specifier: ^2.0.16 - version: 2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + version: 2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(react@18.3.1) '@opentelemetry/api': specifier: ^1.4.1 version: 1.8.0 @@ -358,7 +361,7 @@ importers: version: 10.1.0 expo-sqlite: specifier: ^13.2.0 - version: 13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) + version: 13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) knex: specifier: ^2.4.2 version: 2.5.1(better-sqlite3@8.7.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7) @@ -551,9 +554,6 @@ importers: '@electric-sql/pglite': specifier: ^0.1.1 version: 0.1.5 - '@libsql/client': - specifier: ^0.5.6 - version: 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@miniflare/d1': specifier: ^2.14.2 version: 2.14.2 @@ -645,6 +645,9 @@ importers: specifier: ^3.20.2 version: 3.23.7 devDependencies: + '@libsql/client': + specifier: ^0.10.0 + version: 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) '@neondatabase/serverless': specifier: 0.9.0 version: 0.9.0 @@ -2974,10 +2977,12 @@ packages: '@humanwhocodes/config-array@0.11.11': resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/config-array@0.11.13': resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} @@ -2990,9 +2995,11 @@ packages: '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead '@humanwhocodes/object-schema@2.0.1': resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + deprecated: Use @eslint/object-schema instead '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} @@ -3078,94 +3085,89 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@libsql/client@0.4.3': - resolution: {integrity: sha512-AUYKnSPqAsFBVWBvmtrb4dG3pQlvTKT92eztAest9wQU2iJkabH8WzHLDb3dKFWKql7/kiCqvBQUVpozDwhekQ==} + '@libsql/client@0.10.0': + resolution: {integrity: sha512-2ERn08T4XOVx34yBtUPq0RDjAdd9TJ5qNH/izugr208ml2F94mk92qC64kXyDVQINodWJvp3kAdq6P4zTtCZ7g==} - '@libsql/client@0.5.6': - resolution: {integrity: sha512-UBjmDoxz75Z2sHdP+ETCROpeLA/77VMesiff8R4UWK1rnaWbh6/YoCLDILMJL3Rh0udQeKxjL8MjXthqohax+g==} + '@libsql/core@0.10.0': + resolution: {integrity: sha512-rqynAXGaiSpTsykOZdBtI1N4z4O+KZ6mt33K/aHeXAY0gSIfK/ctxuWa0Y1Bjo4FMz1idBTCXz4Ps5kITOvZZw==} - '@libsql/core@0.4.3': - resolution: {integrity: sha512-r28iYBtaLBW9RRgXPFh6cGCsVI/rwRlOzSOpAu/1PVTm6EJ3t233pUf97jETVHU0vjdr1d8VvV6fKAvJkokqCw==} - - '@libsql/core@0.5.6': - resolution: {integrity: sha512-3vicUAydq6jPth410n4AsHHm1n2psTwvkSf94nfJlSXutGSZsl0updn2N/mJBgqUHkbuFoWZtlMifF0SwBj1xQ==} - - '@libsql/darwin-arm64@0.2.0': - resolution: {integrity: sha512-+qyT2W/n5CFH1YZWv2mxW4Fsoo4dX9Z9M/nvbQqZ7H84J8hVegvVAsIGYzcK8xAeMEcpU5yGKB1Y9NoDY4hOSQ==} + '@libsql/darwin-arm64@0.3.19': + resolution: {integrity: sha512-rmOqsLcDI65zzxlUOoEiPJLhqmbFsZF6p4UJQ2kMqB+Kc0Rt5/A1OAdOZ/Wo8fQfJWjR1IbkbpEINFioyKf+nQ==} cpu: [arm64] os: [darwin] - '@libsql/darwin-arm64@0.3.18': - resolution: {integrity: sha512-Zt49dt+cwhPCkuoWgvjbQd4ckNfCJR5xzIAyhgHl3CBZqZaEuaXTOGKLNQT7bnFRPuQcdLt5PBT1cenKu2N6pA==} + '@libsql/darwin-arm64@0.4.1': + resolution: {integrity: sha512-XICT9/OyU8Aa9Iv1xZIHgvM09n/1OQUk3VC+s5uavzdiGHrDMkOWzN47JN7/FiMa/NWrcgoEiDMk3+e7mE53Ig==} cpu: [arm64] os: [darwin] - '@libsql/darwin-x64@0.2.0': - resolution: {integrity: sha512-hwmO2mF1n8oDHKFrUju6Jv+n9iFtTf5JUK+xlnIE3Td0ZwGC/O1R/Z/btZTd9nD+vsvakC8SJT7/Q6YlWIkhEw==} + '@libsql/darwin-x64@0.3.19': + resolution: {integrity: sha512-q9O55B646zU+644SMmOQL3FIfpmEvdWpRpzubwFc2trsa+zoBlSkHuzU9v/C+UNoPHQVRMP7KQctJ455I/h/xw==} cpu: [x64] os: [darwin] - '@libsql/darwin-x64@0.3.18': - resolution: {integrity: sha512-faq6HUGDaNaueeqPei5cypHaD/hhazUyfHo094CXiEeRZq6ZKtNl5PHdlr8jE/Uw8USNpVVQaLdnvSgKcpRPHw==} + '@libsql/darwin-x64@0.4.1': + resolution: {integrity: sha512-pSKxhRrhu4SsTD+IBRZXcs1SkwMdeAG1tv6Z/Ctp/sOEYrgkU8MDKLqkOr9NsmwpK4S0+JdwjkLMyhTkct/5TQ==} cpu: [x64] os: [darwin] - '@libsql/hrana-client@0.5.6': - resolution: {integrity: sha512-mjQoAmejZ1atG+M3YR2ZW+rg6ceBByH/S/h17ZoYZkqbWrvohFhXyz2LFxj++ARMoY9m6w3RJJIRdJdmnEUlFg==} + '@libsql/hrana-client@0.6.2': + resolution: {integrity: sha512-MWxgD7mXLNf9FXXiM0bc90wCjZSpErWKr5mGza7ERy2FJNNMXd7JIOv+DepBA1FQTIfI8TFO4/QDYgaQC0goNw==} - '@libsql/isomorphic-fetch@0.1.12': - resolution: {integrity: sha512-MRo4UcmjAGAa3ac56LoD5OE13m2p0lu0VEtZC2NZMcogM/jc5fU9YtMQ3qbPjFJ+u2BBjFZgMPkQaLS1dlMhpg==} + '@libsql/isomorphic-fetch@0.2.5': + resolution: {integrity: sha512-8s/B2TClEHms2yb+JGpsVRTPBfy1ih/Pq6h6gvyaNcYnMVJvgQRY7wAa8U2nD0dppbCuDU5evTNMEhrQ17ZKKg==} + engines: {node: '>=18.0.0'} '@libsql/isomorphic-ws@0.1.5': resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} - '@libsql/linux-arm64-gnu@0.2.0': - resolution: {integrity: sha512-1w2lPXIYtnBaK5t/Ej5E8x7lPiE+jP3KATI/W4yei5Z/ONJh7jQW5PJ7sYU95vTME3hWEM1FXN6kvzcpFAte7w==} + '@libsql/linux-arm64-gnu@0.3.19': + resolution: {integrity: sha512-mgeAUU1oqqh57k7I3cQyU6Trpdsdt607eFyEmH5QO7dv303ti+LjUvh1pp21QWV6WX7wZyjeJV1/VzEImB+jRg==} cpu: [arm64] os: [linux] - '@libsql/linux-arm64-gnu@0.3.18': - resolution: {integrity: sha512-5m9xtDAhoyLSV54tho9uQ2ZIDeJWc0vU3Xpe/VK4+6bpURISs23qNhXiCrZnnq3oV0hFlBfcIgQUIATmb6jD2A==} + '@libsql/linux-arm64-gnu@0.4.1': + resolution: {integrity: sha512-9lpvb24tO2qZd9nq5dlq3ESA3hSKYWBIK7lJjfiCM6f7a70AUwBY9QoPJV9q4gILIyVnR1YBGrlm50nnb+dYgw==} cpu: [arm64] os: [linux] - '@libsql/linux-arm64-musl@0.2.0': - resolution: {integrity: sha512-lkblBEJ7xuNiWNjP8DDq0rqoWccszfkUS7Efh5EjJ+GDWdCBVfh08mPofIZg0fZVLWQCY3j+VZCG1qZfATBizg==} + '@libsql/linux-arm64-musl@0.3.19': + resolution: {integrity: sha512-VEZtxghyK6zwGzU9PHohvNxthruSxBEnRrX7BSL5jQ62tN4n2JNepJ6SdzXp70pdzTfwroOj/eMwiPt94gkVRg==} cpu: [arm64] os: [linux] - '@libsql/linux-arm64-musl@0.3.18': - resolution: {integrity: sha512-oYD5+oM2gPEalp+EoR5DVQBRtdGjLsocjsRbQs5O2m4WOBJKER7VUfDYZHsifLGZoBSc11Yo6s9IR9rjGWy20w==} + '@libsql/linux-arm64-musl@0.4.1': + resolution: {integrity: sha512-lyxi+lFxE+NcBRDMQCxCtDg3c4WcKAbc9u63d5+B23Vm+UgphD9XY4seu+tGrBy1MU2tuNVix7r9S7ECpAaVrA==} cpu: [arm64] os: [linux] - '@libsql/linux-x64-gnu@0.2.0': - resolution: {integrity: sha512-+x/d289KeJydwOhhqSxKT+6MSQTCfLltzOpTzPccsvdt5fxg8CBi+gfvEJ4/XW23Sa+9bc7zodFP0i6MOlxX7w==} + '@libsql/linux-x64-gnu@0.3.19': + resolution: {integrity: sha512-2t/J7LD5w2f63wGihEO+0GxfTyYIyLGEvTFEsMO16XI5o7IS9vcSHrxsvAJs4w2Pf907uDjmc7fUfMg6L82BrQ==} cpu: [x64] os: [linux] - '@libsql/linux-x64-gnu@0.3.18': - resolution: {integrity: sha512-QDSSP60nS8KIldGE7H3bpEflQHiL1erwED6huoVJdmDFxsyDJX2CYdWUWW8Za0ZUOvUbnEWAOyMhp6j1dBbZqw==} + '@libsql/linux-x64-gnu@0.4.1': + resolution: {integrity: sha512-psvuQ3UFBEmDFV8ZHG+WkUHIJiWv+elZ+zIPvOVedlIKdxG1O+8WthWUAhFHOGnbiyzc4sAZ4c3de1oCvyHxyQ==} cpu: [x64] os: [linux] - '@libsql/linux-x64-musl@0.2.0': - resolution: {integrity: sha512-5Xn0c5A6vKf9D1ASpgk7mef//FuY7t5Lktj/eiU4n3ryxG+6WTpqstTittJUgepVjcleLPYxIhQAYeYwTYH1IQ==} + '@libsql/linux-x64-musl@0.3.19': + resolution: {integrity: sha512-BLsXyJaL8gZD8+3W2LU08lDEd9MIgGds0yPy5iNPp8tfhXx3pV/Fge2GErN0FC+nzt4DYQtjL+A9GUMglQefXQ==} cpu: [x64] os: [linux] - '@libsql/linux-x64-musl@0.3.18': - resolution: {integrity: sha512-5SXwTlaLCUPzxYyq+P0c7Ko7tcEjpd1X6RZKe1DuRFmJPg6f7j2+LrPEhMSIbqKcrl5ACUUAyoKmGZqNYwz23w==} + '@libsql/linux-x64-musl@0.4.1': + resolution: {integrity: sha512-PDidJ3AhGDqosGg3OAZzGxMFIbnuOALya4BoezJKl667AFv3x7BBQ30H81Mngsq3Fh8RkJkXSdWfL91+Txb1iA==} cpu: [x64] os: [linux] - '@libsql/win32-x64-msvc@0.2.0': - resolution: {integrity: sha512-rpK+trBIpRST15m3cMYg5aPaX7kvCIottxY7jZPINkKAaScvfbn9yulU/iZUM9YtuK96Y1ZmvwyVIK/Y5DzoMQ==} + '@libsql/win32-x64-msvc@0.3.19': + resolution: {integrity: sha512-ay1X9AobE4BpzG0XPw1gplyLZPGHIgJOovvW23gUrukRegiUP62uzhpRbKNogLlUOynyXeq//prHgPXiebUfWg==} cpu: [x64] os: [win32] - '@libsql/win32-x64-msvc@0.3.18': - resolution: {integrity: sha512-9EEIHz+e8tTbx9TMkb8ByZnzxc0pYFirK1nSbqC6cFEST95fiY0NCfQ/zAzJxe90KckbjifX6BbO69eWIi3TAg==} + '@libsql/win32-x64-msvc@0.4.1': + resolution: {integrity: sha512-IdODVqV/PrdOnHA/004uWyorZQuRsB7U7bCRCE3vXgABj3eJLJGc6cv2C6ksEaEoVxJbD8k53H4VVAGrtYwXzQ==} cpu: [x64] os: [win32] @@ -4056,9 +4058,6 @@ packages: '@types/minimist@1.2.2': resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} - '@types/node-fetch@2.6.11': - resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} @@ -4509,6 +4508,7 @@ packages: are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -4630,10 +4630,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - aws-ssl-profiles@1.1.1: - resolution: {integrity: sha512-+H+kuK34PfMaI9PNU/NSjBKL5hh/KDM9J72kwYeYEm0A8B1AC4fuCy3qsjnA7lxklgyXsB68yn8Z2xoZEjgwCQ==} - engines: {node: '>= 6.0.0'} - axios@1.6.8: resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} @@ -6275,6 +6271,7 @@ packages: gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -7092,13 +7089,12 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - libsql@0.2.0: - resolution: {integrity: sha512-ELBRqhpJx5Dap0187zKQnntZyk4EjlDHSrjIVL8t+fQ5e8IxbQTeYgZgigMjB1EvrETdkm0Y0VxBGhzPQ+t0Jg==} - cpu: [x64, arm64] + libsql@0.3.19: + resolution: {integrity: sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==} os: [darwin, linux, win32] - libsql@0.3.18: - resolution: {integrity: sha512-lvhKr7WV3NLWRbXkjn/MeKqXOAqWKU0PX9QYrvDh7fneukapj+iUQ4qgJASrQyxcCrEsClXCQiiK5W6OoYPAlA==} + libsql@0.4.1: + resolution: {integrity: sha512-qZlR9Yu1zMBeLChzkE/cKfoKV3Esp9cn9Vx5Zirn4AVhDWPcjYhKwbtJcMuHehgk3mH+fJr9qW+3vesBWbQpBg==} os: [darwin, linux, win32] lighthouse-logger@1.4.2: @@ -7309,10 +7305,6 @@ packages: resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} engines: {node: '>=16.14'} - lru-cache@9.1.2: - resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==} - engines: {node: 14 || >=16.14} - lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} @@ -7626,10 +7618,6 @@ packages: resolution: {integrity: sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==} engines: {node: '>=0.8.0'} - mysql2@3.11.0: - resolution: {integrity: sha512-J9phbsXGvTOcRVPR95YedzVSxJecpW5A5+cQ57rhHIFXteTP10HCs+VBjS7DHIKfEaI1zQ5tlVrquCd64A6YvA==} - engines: {node: '>= 8.0'} - mysql2@3.3.3: resolution: {integrity: sha512-MxDQJztArk4JFX1PKVjDhIXRzAmVJfuqZrVU+my6NeYBAA/XZRaDw5q7vga8TNvgyy3Lv3rivBFBBuJFbsdjaw==} engines: {node: '>= 8.0'} @@ -7785,6 +7773,7 @@ packages: npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. npx-import@1.1.4: resolution: {integrity: sha512-3ShymTWOgqGyNlh5lMJAejLuIv3W1K3fbI5Ewc6YErZU3Sp0PqsNs8UIU1O8z5+KVl/Du5ag56Gza9vdorGEoA==} @@ -8302,6 +8291,9 @@ packages: bluebird: optional: true + promise-limit@2.7.0: + resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} @@ -8598,6 +8590,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@5.0.0: @@ -10210,7 +10203,7 @@ snapshots: '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) '@aws-sdk/client-sts': 3.583.0 '@aws-sdk/core': 3.582.0 - '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 '@aws-sdk/middleware-logger': 3.577.0 '@aws-sdk/middleware-recursion-detection': 3.577.0 @@ -10300,7 +10293,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sts': 3.583.0 '@aws-sdk/core': 3.582.0 - '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 '@aws-sdk/middleware-logger': 3.577.0 '@aws-sdk/middleware-recursion-detection': 3.577.0 @@ -10610,7 +10603,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) '@aws-sdk/core': 3.582.0 - '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/credential-provider-node': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/middleware-host-header': 3.577.0 '@aws-sdk/middleware-logger': 3.577.0 '@aws-sdk/middleware-recursion-detection': 3.577.0 @@ -10799,12 +10792,12 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-ini@3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0)': + '@aws-sdk/credential-provider-ini@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)': dependencies: '@aws-sdk/client-sts': 3.583.0 '@aws-sdk/credential-provider-env': 3.577.0 '@aws-sdk/credential-provider-process': 3.577.0 - '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)) + '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.583.0) '@aws-sdk/types': 3.577.0 '@smithy/credential-provider-imds': 3.0.0 @@ -10889,13 +10882,13 @@ snapshots: - '@aws-sdk/client-sts' - aws-crt - '@aws-sdk/credential-provider-node@3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0)': + '@aws-sdk/credential-provider-node@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0)': dependencies: '@aws-sdk/credential-provider-env': 3.577.0 '@aws-sdk/credential-provider-http': 3.582.0 - '@aws-sdk/credential-provider-ini': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.583.0) + '@aws-sdk/credential-provider-ini': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.583.0) '@aws-sdk/credential-provider-process': 3.577.0 - '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)) + '@aws-sdk/credential-provider-sso': 3.583.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.583.0) '@aws-sdk/types': 3.577.0 '@smithy/credential-provider-imds': 3.0.0 @@ -10970,10 +10963,10 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-sso@3.583.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))': + '@aws-sdk/credential-provider-sso@3.583.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: '@aws-sdk/client-sso': 3.583.0 - '@aws-sdk/token-providers': 3.577.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)) + '@aws-sdk/token-providers': 3.577.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 @@ -11216,7 +11209,7 @@ snapshots: '@smithy/types': 2.12.0 tslib: 2.6.2 - '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))': + '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: '@aws-sdk/client-sso-oidc': 3.583.0(@aws-sdk/client-sts@3.583.0) '@aws-sdk/types': 3.577.0 @@ -12890,7 +12883,7 @@ snapshots: mv: 2.1.1 safe-json-stringify: 1.2.0 - '@expo/cli@0.18.13(bufferutil@4.0.8)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)': + '@expo/cli@0.18.13(bufferutil@4.0.8)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(utf-8-validate@6.0.3)': dependencies: '@babel/runtime': 7.24.6 '@expo/code-signing-certificates': 0.0.5 @@ -12908,7 +12901,7 @@ snapshots: '@expo/rudder-sdk-node': 1.1.1(encoding@0.1.13) '@expo/spawn-async': 1.7.2 '@expo/xcpretty': 4.3.1 - '@react-native/dev-middleware': 0.74.83(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native/dev-middleware': 0.74.83(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@urql/core': 2.3.6(graphql@15.8.0) '@urql/exchange-retry': 0.3.0(graphql@15.8.0) accepts: 1.3.8 @@ -13328,103 +13321,81 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@libsql/client@0.4.3(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': - dependencies: - '@libsql/core': 0.4.3 - '@libsql/hrana-client': 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) - js-base64: 3.7.7 - optionalDependencies: - libsql: 0.2.0 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@libsql/client@0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': + '@libsql/client@0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3)': dependencies: - '@libsql/core': 0.5.6 - '@libsql/hrana-client': 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + '@libsql/core': 0.10.0 + '@libsql/hrana-client': 0.6.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) js-base64: 3.7.7 - libsql: 0.3.18 + libsql: 0.4.1 + promise-limit: 2.7.0 transitivePeerDependencies: - bufferutil - - encoding - utf-8-validate - '@libsql/core@0.4.3': - dependencies: - js-base64: 3.7.7 - - '@libsql/core@0.5.6': + '@libsql/core@0.10.0': dependencies: js-base64: 3.7.7 - '@libsql/darwin-arm64@0.2.0': + '@libsql/darwin-arm64@0.3.19': optional: true - '@libsql/darwin-arm64@0.3.18': + '@libsql/darwin-arm64@0.4.1': optional: true - '@libsql/darwin-x64@0.2.0': + '@libsql/darwin-x64@0.3.19': optional: true - '@libsql/darwin-x64@0.3.18': + '@libsql/darwin-x64@0.4.1': optional: true - '@libsql/hrana-client@0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': + '@libsql/hrana-client@0.6.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)': dependencies: - '@libsql/isomorphic-fetch': 0.1.12(encoding@0.1.13) + '@libsql/isomorphic-fetch': 0.2.5 '@libsql/isomorphic-ws': 0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3) js-base64: 3.7.7 node-fetch: 3.3.2 transitivePeerDependencies: - bufferutil - - encoding - utf-8-validate - '@libsql/isomorphic-fetch@0.1.12(encoding@0.1.13)': - dependencies: - '@types/node-fetch': 2.6.11 - node-fetch: 2.7.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding + '@libsql/isomorphic-fetch@0.2.5': {} '@libsql/isomorphic-ws@0.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.3)': dependencies: '@types/ws': 8.5.11 - ws: 8.17.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - utf-8-validate - '@libsql/linux-arm64-gnu@0.2.0': + '@libsql/linux-arm64-gnu@0.3.19': optional: true - '@libsql/linux-arm64-gnu@0.3.18': + '@libsql/linux-arm64-gnu@0.4.1': optional: true - '@libsql/linux-arm64-musl@0.2.0': + '@libsql/linux-arm64-musl@0.3.19': optional: true - '@libsql/linux-arm64-musl@0.3.18': + '@libsql/linux-arm64-musl@0.4.1': optional: true - '@libsql/linux-x64-gnu@0.2.0': + '@libsql/linux-x64-gnu@0.3.19': optional: true - '@libsql/linux-x64-gnu@0.3.18': + '@libsql/linux-x64-gnu@0.4.1': optional: true - '@libsql/linux-x64-musl@0.2.0': + '@libsql/linux-x64-musl@0.3.19': optional: true - '@libsql/linux-x64-musl@0.3.18': + '@libsql/linux-x64-musl@0.4.1': optional: true - '@libsql/win32-x64-msvc@0.2.0': + '@libsql/win32-x64-msvc@0.3.19': optional: true - '@libsql/win32-x64-msvc@0.3.18': + '@libsql/win32-x64-msvc@0.4.1': optional: true '@miniflare/core@2.14.2': @@ -13504,10 +13475,10 @@ snapshots: rimraf: 3.0.2 optional: true - '@op-engineering/op-sqlite@2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)': + '@op-engineering/op-sqlite@2.0.22(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(react@18.3.1)': dependencies: react: 18.3.1 - react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1) + react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3) '@opentelemetry/api@1.8.0': {} @@ -13644,7 +13615,7 @@ snapshots: transitivePeerDependencies: - encoding - '@react-native-community/cli-server-api@13.6.6(bufferutil@4.0.8)(encoding@0.1.13)': + '@react-native-community/cli-server-api@13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': dependencies: '@react-native-community/cli-debugger-ui': 13.6.6 '@react-native-community/cli-tools': 13.6.6(encoding@0.1.13) @@ -13654,7 +13625,7 @@ snapshots: nocache: 3.0.4 pretty-format: 26.6.2 serve-static: 1.15.0 - ws: 6.2.2(bufferutil@4.0.8) + ws: 6.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - encoding @@ -13681,14 +13652,14 @@ snapshots: dependencies: joi: 17.13.1 - '@react-native-community/cli@13.6.6(bufferutil@4.0.8)(encoding@0.1.13)': + '@react-native-community/cli@13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': dependencies: '@react-native-community/cli-clean': 13.6.6(encoding@0.1.13) '@react-native-community/cli-config': 13.6.6(encoding@0.1.13) '@react-native-community/cli-debugger-ui': 13.6.6 '@react-native-community/cli-doctor': 13.6.6(encoding@0.1.13) '@react-native-community/cli-hermes': 13.6.6(encoding@0.1.13) - '@react-native-community/cli-server-api': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native-community/cli-server-api': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native-community/cli-tools': 13.6.6(encoding@0.1.13) '@react-native-community/cli-types': 13.6.6 chalk: 4.1.2 @@ -13777,16 +13748,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@react-native/community-cli-plugin@0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)': + '@react-native/community-cli-plugin@0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': dependencies: - '@react-native-community/cli-server-api': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native-community/cli-server-api': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native-community/cli-tools': 13.6.6(encoding@0.1.13) - '@react-native/dev-middleware': 0.74.83(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native/dev-middleware': 0.74.83(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native/metro-babel-transformer': 0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6)) chalk: 4.1.2 execa: 5.1.1 - metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) - metro-config: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + metro-config: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) metro-core: 0.80.9 node-fetch: 2.7.0(encoding@0.1.13) querystring: 0.2.1 @@ -13801,7 +13772,7 @@ snapshots: '@react-native/debugger-frontend@0.74.83': {} - '@react-native/dev-middleware@0.74.83(bufferutil@4.0.8)(encoding@0.1.13)': + '@react-native/dev-middleware@0.74.83(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)': dependencies: '@isaacs/ttlcache': 1.4.1 '@react-native/debugger-frontend': 0.74.83 @@ -13815,7 +13786,7 @@ snapshots: selfsigned: 2.4.1 serve-static: 1.15.0 temp-dir: 2.0.0 - ws: 6.2.2(bufferutil@4.0.8) + ws: 6.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - encoding @@ -13838,12 +13809,12 @@ snapshots: '@react-native/normalize-colors@0.74.83': {} - '@react-native/virtualized-lists@0.74.83(@types/react@18.3.1)(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)': + '@react-native/virtualized-lists@0.74.83(@types/react@18.3.1)(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(react@18.3.1)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 18.3.1 - react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1) + react-native: 0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3) optionalDependencies: '@types/react': 18.3.1 @@ -14684,11 +14655,6 @@ snapshots: '@types/minimist@1.2.2': {} - '@types/node-fetch@2.6.11': - dependencies: - '@types/node': 20.12.12 - form-data: 4.0.0 - '@types/node-forge@1.3.11': dependencies: '@types/node': 20.12.12 @@ -15124,7 +15090,7 @@ snapshots: pathe: 1.1.2 picocolors: 1.0.1 sirv: 2.0.4 - vitest: 1.6.0(@types/node@18.19.33)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) + vitest: 1.6.0(@types/node@20.12.12)(@vitest/ui@1.6.0)(lightningcss@1.25.1)(terser@5.31.0) '@vitest/utils@1.6.0': dependencies: @@ -15425,9 +15391,6 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 - aws-ssl-profiles@1.1.1: - optional: true - axios@1.6.8: dependencies: follow-redirects: 1.15.6 @@ -16313,11 +16276,11 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.27.2(@aws-sdk/client-rds-data@3.583.0)(@cloudflare/workers-types@4.20240524.0)(@libsql/client@0.5.6)(@neondatabase/serverless@0.9.3)(@opentelemetry/api@1.8.0)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(@vercel/postgres@0.8.0)(better-sqlite3@9.6.0)(bun-types@1.0.3)(knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.11.0)(pg@8.11.5)(sqlite3@5.1.7))(kysely@0.25.0)(mysql2@3.11.0)(pg@8.11.5)(postgres@3.4.4)(sql.js@1.10.3)(sqlite3@5.1.7): + drizzle-orm@0.27.2(@aws-sdk/client-rds-data@3.583.0)(@cloudflare/workers-types@4.20240524.0)(@libsql/client@0.10.0)(@neondatabase/serverless@0.9.3)(@opentelemetry/api@1.8.0)(@planetscale/database@1.18.0)(@types/better-sqlite3@7.6.10)(@types/pg@8.11.6)(@types/sql.js@1.4.9)(@vercel/postgres@0.8.0)(better-sqlite3@9.6.0)(bun-types@1.0.3)(knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7))(kysely@0.25.0)(mysql2@3.3.3)(pg@8.11.5)(postgres@3.4.4)(sql.js@1.10.3)(sqlite3@5.1.7): optionalDependencies: '@aws-sdk/client-rds-data': 3.583.0 '@cloudflare/workers-types': 4.20240524.0 - '@libsql/client': 0.5.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + '@libsql/client': 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) '@neondatabase/serverless': 0.9.3 '@opentelemetry/api': 1.8.0 '@planetscale/database': 1.18.0 @@ -16327,9 +16290,9 @@ snapshots: '@vercel/postgres': 0.8.0 better-sqlite3: 9.6.0 bun-types: 1.0.3 - knex: 2.5.1(better-sqlite3@9.6.0)(mysql2@3.11.0)(pg@8.11.5)(sqlite3@5.1.7) + knex: 2.5.1(better-sqlite3@9.6.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7) kysely: 0.25.0 - mysql2: 3.11.0 + mysql2: 3.3.3 pg: 8.11.5 postgres: 3.4.4 sql.js: 1.10.3 @@ -17154,35 +17117,35 @@ snapshots: expand-template@2.0.3: {} - expo-asset@10.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-asset@10.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: '@react-native/assets-registry': 0.74.83 - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) - expo-constants: 16.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) + expo-constants: 16.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) invariant: 2.2.4 md5-file: 3.2.3 transitivePeerDependencies: - supports-color - expo-constants@16.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-constants@16.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: '@expo/config': 9.0.2 - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) transitivePeerDependencies: - supports-color - expo-file-system@17.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-file-system@17.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) - expo-font@12.0.5(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-font@12.0.5(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) fontfaceobserver: 2.3.0 - expo-keep-awake@13.0.2(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-keep-awake@13.0.2(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) expo-modules-autolinking@1.11.1: dependencies: @@ -17196,24 +17159,24 @@ snapshots: dependencies: invariant: 2.2.4 - expo-sqlite@13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)): + expo-sqlite@13.4.0(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)): dependencies: '@expo/websql': 1.0.1 - expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + expo: 51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) - expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13): + expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3): dependencies: '@babel/runtime': 7.24.6 - '@expo/cli': 0.18.13(bufferutil@4.0.8)(encoding@0.1.13)(expo-modules-autolinking@1.11.1) + '@expo/cli': 0.18.13(bufferutil@4.0.8)(encoding@0.1.13)(expo-modules-autolinking@1.11.1)(utf-8-validate@6.0.3) '@expo/config': 9.0.2 '@expo/config-plugins': 8.0.4 '@expo/metro-config': 0.18.4 '@expo/vector-icons': 14.0.2 babel-preset-expo: 11.0.6(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6)) - expo-asset: 10.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) - expo-file-system: 17.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) - expo-font: 12.0.5(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) - expo-keep-awake: 13.0.2(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)) + expo-asset: 10.0.6(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) + expo-file-system: 17.0.1(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) + expo-font: 12.0.5(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) + expo-keep-awake: 13.0.2(expo@51.0.8(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3)) expo-modules-autolinking: 1.11.1 expo-modules-core: 1.12.11 fbemitter: 3.0.0(encoding@0.1.13) @@ -18320,7 +18283,7 @@ snapshots: transitivePeerDependencies: - supports-color - knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.11.0)(pg@8.11.5)(sqlite3@5.1.7): + knex@2.5.1(better-sqlite3@9.6.0)(mysql2@3.3.3)(pg@8.11.5)(sqlite3@5.1.7): dependencies: colorette: 2.0.19 commander: 10.0.1 @@ -18338,7 +18301,7 @@ snapshots: tildify: 2.0.0 optionalDependencies: better-sqlite3: 9.6.0 - mysql2: 3.11.0 + mysql2: 3.3.3 pg: 8.11.5 sqlite3: 5.1.7 transitivePeerDependencies: @@ -18354,32 +18317,32 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libsql@0.2.0: + libsql@0.3.19: dependencies: '@neon-rs/load': 0.0.4 detect-libc: 2.0.2 optionalDependencies: - '@libsql/darwin-arm64': 0.2.0 - '@libsql/darwin-x64': 0.2.0 - '@libsql/linux-arm64-gnu': 0.2.0 - '@libsql/linux-arm64-musl': 0.2.0 - '@libsql/linux-x64-gnu': 0.2.0 - '@libsql/linux-x64-musl': 0.2.0 - '@libsql/win32-x64-msvc': 0.2.0 - optional: true + '@libsql/darwin-arm64': 0.3.19 + '@libsql/darwin-x64': 0.3.19 + '@libsql/linux-arm64-gnu': 0.3.19 + '@libsql/linux-arm64-musl': 0.3.19 + '@libsql/linux-x64-gnu': 0.3.19 + '@libsql/linux-x64-musl': 0.3.19 + '@libsql/win32-x64-msvc': 0.3.19 - libsql@0.3.18: + libsql@0.4.1: dependencies: '@neon-rs/load': 0.0.4 detect-libc: 2.0.2 + libsql: 0.3.19 optionalDependencies: - '@libsql/darwin-arm64': 0.3.18 - '@libsql/darwin-x64': 0.3.18 - '@libsql/linux-arm64-gnu': 0.3.18 - '@libsql/linux-arm64-musl': 0.3.18 - '@libsql/linux-x64-gnu': 0.3.18 - '@libsql/linux-x64-musl': 0.3.18 - '@libsql/win32-x64-msvc': 0.3.18 + '@libsql/darwin-arm64': 0.4.1 + '@libsql/darwin-x64': 0.4.1 + '@libsql/linux-arm64-gnu': 0.4.1 + '@libsql/linux-arm64-musl': 0.4.1 + '@libsql/linux-x64-gnu': 0.4.1 + '@libsql/linux-x64-musl': 0.4.1 + '@libsql/win32-x64-msvc': 0.4.1 lighthouse-logger@1.4.2: dependencies: @@ -18548,8 +18511,6 @@ snapshots: lru-cache@8.0.5: {} - lru-cache@9.1.2: {} - lru-queue@0.1.0: dependencies: es5-ext: 0.10.62 @@ -18689,12 +18650,12 @@ snapshots: metro-core: 0.80.9 rimraf: 3.0.2 - metro-config@0.80.9(bufferutil@4.0.8)(encoding@0.1.13): + metro-config@0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3): dependencies: connect: 3.7.0 cosmiconfig: 5.2.1 jest-validate: 29.7.0 - metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) metro-cache: 0.80.9 metro-core: 0.80.9 metro-runtime: 0.80.9 @@ -18770,13 +18731,13 @@ snapshots: transitivePeerDependencies: - supports-color - metro-transform-worker@0.80.9(bufferutil@4.0.8)(encoding@0.1.13): + metro-transform-worker@0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3): dependencies: '@babel/core': 7.24.6 '@babel/generator': 7.24.6 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 - metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) metro-babel-transformer: 0.80.9 metro-cache: 0.80.9 metro-cache-key: 0.80.9 @@ -18790,7 +18751,7 @@ snapshots: - supports-color - utf-8-validate - metro@0.80.9(bufferutil@4.0.8)(encoding@0.1.13): + metro@0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3): dependencies: '@babel/code-frame': 7.24.6 '@babel/core': 7.24.6 @@ -18816,7 +18777,7 @@ snapshots: metro-babel-transformer: 0.80.9 metro-cache: 0.80.9 metro-cache-key: 0.80.9 - metro-config: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro-config: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) metro-core: 0.80.9 metro-file-map: 0.80.9 metro-resolver: 0.80.9 @@ -18824,7 +18785,7 @@ snapshots: metro-source-map: 0.80.9 metro-symbolicate: 0.80.9 metro-transform-plugins: 0.80.9 - metro-transform-worker: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13) + metro-transform-worker: 0.80.9(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) mime-types: 2.1.35 node-fetch: 2.7.0(encoding@0.1.13) nullthrows: 1.1.1 @@ -18833,7 +18794,7 @@ snapshots: source-map: 0.5.7 strip-ansi: 6.0.1 throat: 5.0.0 - ws: 7.5.9(bufferutil@4.0.8) + ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@6.0.3) yargs: 17.7.2 transitivePeerDependencies: - bufferutil @@ -18988,19 +18949,6 @@ snapshots: rimraf: 2.4.5 optional: true - mysql2@3.11.0: - dependencies: - aws-ssl-profiles: 1.1.1 - denque: 2.1.0 - generate-function: 2.3.1 - iconv-lite: 0.6.3 - long: 5.2.3 - lru-cache: 8.0.5 - named-placeholders: 1.1.3 - seq-queue: 0.0.5 - sqlstring: 2.3.3 - optional: true - mysql2@3.3.3: dependencies: denque: 2.1.0 @@ -19413,7 +19361,7 @@ snapshots: path-scurry@1.10.1: dependencies: - lru-cache: 9.1.2 + lru-cache: 10.2.2 minipass: 5.0.0 path-scurry@1.11.1: @@ -19638,6 +19586,8 @@ snapshots: promise-inflight@1.0.1: optional: true + promise-limit@2.7.0: {} + promise-retry@2.0.1: dependencies: err-code: 2.0.3 @@ -19717,10 +19667,10 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-devtools-core@5.2.0(bufferutil@4.0.8): + react-devtools-core@5.2.0(bufferutil@4.0.8)(utf-8-validate@6.0.3): dependencies: shell-quote: 1.8.1 - ws: 7.5.9(bufferutil@4.0.8) + ws: 7.5.9(bufferutil@4.0.8)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -19733,19 +19683,19 @@ snapshots: react-is@18.3.1: {} - react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1): + react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3): dependencies: '@jest/create-cache-key-function': 29.7.0 - '@react-native-community/cli': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native-community/cli': 13.6.6(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native-community/cli-platform-android': 13.6.6(encoding@0.1.13) '@react-native-community/cli-platform-ios': 13.6.6(encoding@0.1.13) '@react-native/assets-registry': 0.74.83 '@react-native/codegen': 0.74.83(@babel/preset-env@7.24.6(@babel/core@7.24.6)) - '@react-native/community-cli-plugin': 0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13) + '@react-native/community-cli-plugin': 0.74.83(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@6.0.3) '@react-native/gradle-plugin': 0.74.83 '@react-native/js-polyfills': 0.74.83 '@react-native/normalize-colors': 0.74.83 - '@react-native/virtualized-lists': 0.74.83(@types/react@18.3.1)(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) + '@react-native/virtualized-lists': 0.74.83(@types/react@18.3.1)(react-native@0.74.1(@babel/core@7.24.6)(@babel/preset-env@7.24.6(@babel/core@7.24.6))(@types/react@18.3.1)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)(utf-8-validate@6.0.3))(react@18.3.1) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -19764,14 +19714,14 @@ snapshots: pretty-format: 26.6.2 promise: 8.3.0 react: 18.3.1 - react-devtools-core: 5.2.0(bufferutil@4.0.8) + react-devtools-core: 5.2.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) react-refresh: 0.14.2 react-shallow-renderer: 16.15.0(react@18.3.1) regenerator-runtime: 0.13.11 scheduler: 0.24.0-canary-efb381bbf-20230505 stacktrace-parser: 0.1.10 whatwg-fetch: 3.6.20 - ws: 6.2.2(bufferutil@4.0.8) + ws: 6.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3) yargs: 17.7.2 optionalDependencies: '@types/react': 18.3.1 @@ -21624,15 +21574,17 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.0.2 - ws@6.2.2(bufferutil@4.0.8): + ws@6.2.2(bufferutil@4.0.8)(utf-8-validate@6.0.3): dependencies: async-limiter: 1.0.1 optionalDependencies: bufferutil: 4.0.8 + utf-8-validate: 6.0.3 - ws@7.5.9(bufferutil@4.0.8): + ws@7.5.9(bufferutil@4.0.8)(utf-8-validate@6.0.3): optionalDependencies: bufferutil: 4.0.8 + utf-8-validate: 6.0.3 ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3): optionalDependencies: