From 0fcf975a8566341c758984859204da29e059c202 Mon Sep 17 00:00:00 2001 From: Sergey Reka <71607800+Sukairo-02@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:02:45 +0300 Subject: [PATCH] Split libsql (#3170) * Separated different libsql versions into different import paths, adapted tests, added `@libsql/client-wasm` peer and dev dep * Switched default libsql driver initializer to new initializer type, adapted tests * Fixed tests * Excluded ESM-only libsql/wasm package from CJS import tests * Tests for libsql submodules * Fixed imports in tests * Added remote libsql envs * Modified workflows to include required env params * Modified tests for `libsql/http`,`libsql/ws` * Crash prevention on tests after failed view tests in sqlite, increased msDelay for `libsql/ws`/`libsql/http` tests * Add 0.35.3 version release notes * Fixed planetscale driver rejecting `Connection` on type level * No Connection in planetscale, only client * Remove connection tests --------- Co-authored-by: AndriiSherman --- .github/workflows/release-feature-branch.yaml | 2 + .github/workflows/release-latest.yaml | 2 + changelogs/drizzle-orm/0.35.3.md | 80 ++++++++ drizzle-orm/package.json | 4 +- drizzle-orm/src/libsql/driver-core.ts | 64 ++++++ drizzle-orm/src/libsql/driver.ts | 64 +----- drizzle-orm/src/libsql/http/index.ts | 62 ++++++ drizzle-orm/src/libsql/node/index.ts | 62 ++++++ drizzle-orm/src/libsql/sqlite3/index.ts | 62 ++++++ drizzle-orm/src/libsql/wasm/index.ts | 62 ++++++ drizzle-orm/src/libsql/web/index.ts | 62 ++++++ drizzle-orm/src/libsql/ws/index.ts | 62 ++++++ .../src/planetscale-serverless/driver.ts | 23 +-- integration-tests/.env.example | 2 + .../driver-init/commonjs/planetscale.test.cjs | 47 +---- .../driver-init/module/planetscale.test.mjs | 47 +---- integration-tests/tests/imports/index.test.ts | 2 +- .../tests/sqlite/libsql-http.test.ts | 186 +++++++++++++++++ .../tests/sqlite/libsql-node.test.ts | 97 +++++++++ .../tests/sqlite/libsql-sqlite3.test.ts | 97 +++++++++ .../tests/sqlite/libsql-ws.test.ts | 187 ++++++++++++++++++ .../tests/sqlite/sqlite-common.ts | 6 +- pnpm-lock.yaml | 155 ++++++++------- 23 files changed, 1189 insertions(+), 248 deletions(-) create mode 100644 changelogs/drizzle-orm/0.35.3.md create mode 100644 drizzle-orm/src/libsql/driver-core.ts create mode 100644 drizzle-orm/src/libsql/http/index.ts create mode 100644 drizzle-orm/src/libsql/node/index.ts create mode 100644 drizzle-orm/src/libsql/sqlite3/index.ts create mode 100644 drizzle-orm/src/libsql/wasm/index.ts create mode 100644 drizzle-orm/src/libsql/web/index.ts create mode 100644 drizzle-orm/src/libsql/ws/index.ts create mode 100644 integration-tests/tests/sqlite/libsql-http.test.ts create mode 100644 integration-tests/tests/sqlite/libsql-node.test.ts create mode 100644 integration-tests/tests/sqlite/libsql-sqlite3.test.ts create mode 100644 integration-tests/tests/sqlite/libsql-ws.test.ts diff --git a/.github/workflows/release-feature-branch.yaml b/.github/workflows/release-feature-branch.yaml index 5c2d76fb7..eb360da2d 100644 --- a/.github/workflows/release-feature-branch.yaml +++ b/.github/workflows/release-feature-branch.yaml @@ -151,6 +151,8 @@ jobs: XATA_API_KEY: ${{ secrets.XATA_API_KEY }} XATA_BRANCH: ${{ secrets.XATA_BRANCH }} LIBSQL_URL: file:local.db + LIBSQL_REMOTE_URL: ${{ secrets.LIBSQL_REMOTE_URL }} + LIBSQL_REMOTE_TOKEN: ${{ secrets.LIBSQL_REMOTE_TOKEN }} run: | if [[ ${{ github.event_name }} != "push" && "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then export SKIP_EXTERNAL_DB_TESTS=1 diff --git a/.github/workflows/release-latest.yaml b/.github/workflows/release-latest.yaml index d81a0bcab..f9292b2e0 100644 --- a/.github/workflows/release-latest.yaml +++ b/.github/workflows/release-latest.yaml @@ -154,6 +154,8 @@ jobs: XATA_API_KEY: ${{ secrets.XATA_API_KEY }} XATA_BRANCH: ${{ secrets.XATA_BRANCH }} LIBSQL_URL: file:local.db + LIBSQL_REMOTE_URL: ${{ secrets.LIBSQL_REMOTE_URL }} + LIBSQL_REMOTE_TOKEN: ${{ secrets.LIBSQL_REMOTE_TOKEN }} run: | if [[ "${{ matrix.package }}" == "drizzle-orm" ]]; then pnpm test --filter ${{ matrix.package }} --filter integration-tests diff --git a/changelogs/drizzle-orm/0.35.3.md b/changelogs/drizzle-orm/0.35.3.md new file mode 100644 index 000000000..b25658e93 --- /dev/null +++ b/changelogs/drizzle-orm/0.35.3.md @@ -0,0 +1,80 @@ +# New LibSQL driver modules + +Drizzle now has native support for all `@libsql/client` driver variations: + +1. `@libsql/client` - defaults to node import, automatically changes to web if target or platform is set for bundler, e.g. `esbuild --platform=browser` + +```ts +import { drizzle } from 'drizzle-orm/libsql'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +2. `@libsql/client/node` node compatible module, supports :memory:, file, wss, http and turso connection protocols + +```ts +import { drizzle } from 'drizzle-orm/libsql/node'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +3. `@libsql/client/web` module for fullstack web frameworks like next, nuxt, astro, etc. + +```ts +import { drizzle } from 'drizzle-orm/libsql/web'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +4. `@libsql/client/http` module for http and https connection protocols + +```ts +import { drizzle } from 'drizzle-orm/libsql/http'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +5. `@libsql/client/ws` module for ws and wss connection protocols + +```ts +import { drizzle } from 'drizzle-orm/libsql/ws'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +6. `@libsql/client/sqlite3` module for :memory: and file connection protocols + +```ts +import { drizzle } from 'drizzle-orm/libsql/wasm'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` + +7. `@libsql/client-wasm` Separate experimental package for WASM + +```ts +import { drizzle } from 'drizzle-orm/libsql'; + +const db = drizzle({ connection: { + url: process.env.DATABASE_URL, + authToken: process.env.DATABASE_AUTH_TOKEN +}}); +``` diff --git a/drizzle-orm/package.json b/drizzle-orm/package.json index ceceab709..8622fea11 100644 --- a/drizzle-orm/package.json +++ b/drizzle-orm/package.json @@ -1,6 +1,6 @@ { "name": "drizzle-orm", - "version": "0.35.2", + "version": "0.35.3", "description": "Drizzle ORM package for SQL databases", "type": "module", "scripts": { @@ -47,6 +47,7 @@ "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.1.1", "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.1", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", @@ -162,6 +163,7 @@ "@cloudflare/workers-types": "^4.20230904.0", "@electric-sql/pglite": "^0.2.12", "@libsql/client": "^0.10.0", + "@libsql/client-wasm": "^0.10.0", "@miniflare/d1": "^2.14.4", "@neondatabase/serverless": "^0.9.0", "@op-engineering/op-sqlite": "^2.0.16", diff --git a/drizzle-orm/src/libsql/driver-core.ts b/drizzle-orm/src/libsql/driver-core.ts new file mode 100644 index 000000000..1bee47d7b --- /dev/null +++ b/drizzle-orm/src/libsql/driver-core.ts @@ -0,0 +1,64 @@ +import type { Client, ResultSet } from '@libsql/client'; +import type { BatchItem, BatchResponse } from '~/batch.ts'; +import { entityKind } from '~/entity.ts'; +import { DefaultLogger } from '~/logger.ts'; +import { + createTableRelationsHelpers, + extractTablesRelationalConfig, + type ExtractTablesWithRelations, + type RelationalSchemaConfig, + type TablesRelationalConfig, +} from '~/relations.ts'; +import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts'; +import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts'; +import type { DrizzleConfig } from '~/utils.ts'; +import { LibSQLSession } from './session.ts'; + +export class LibSQLDatabase< + TSchema extends Record = Record, +> extends BaseSQLiteDatabase<'async', ResultSet, TSchema> { + static override readonly [entityKind]: string = 'LibSQLDatabase'; + + /** @internal */ + declare readonly session: LibSQLSession>; + + async batch, T extends Readonly<[U, ...U[]]>>( + batch: T, + ): Promise> { + return this.session.batch(batch) as Promise>; + } +} + +/** @internal */ +export function construct< + TSchema extends Record = Record, +>(client: Client, config: DrizzleConfig = {}): LibSQLDatabase & { + $client: Client; +} { + const dialect = new SQLiteAsyncDialect({ casing: config.casing }); + 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 LibSQLSession(client, dialect, schema, { logger }, undefined); + const db = new LibSQLDatabase('async', dialect, session, schema) as LibSQLDatabase; + ( db).$client = client; + + return db as any; +} diff --git a/drizzle-orm/src/libsql/driver.ts b/drizzle-orm/src/libsql/driver.ts index 378ae15d2..bf0ef6662 100644 --- a/drizzle-orm/src/libsql/driver.ts +++ b/drizzle-orm/src/libsql/driver.ts @@ -1,66 +1,8 @@ -import { type Client, type Config, createClient, type ResultSet } from '@libsql/client'; -import type { BatchItem, BatchResponse } from '~/batch.ts'; -import { entityKind } from '~/entity.ts'; -import { DefaultLogger } from '~/logger.ts'; -import { - createTableRelationsHelpers, - extractTablesRelationalConfig, - type ExtractTablesWithRelations, - type RelationalSchemaConfig, - type TablesRelationalConfig, -} from '~/relations.ts'; -import { BaseSQLiteDatabase } from '~/sqlite-core/db.ts'; -import { SQLiteAsyncDialect } from '~/sqlite-core/dialect.ts'; +import { type Client, type Config, createClient } from '@libsql/client'; import { type DrizzleConfig, type IfNotImported, type ImportTypeError, isConfig } from '~/utils.ts'; -import { LibSQLSession } from './session.ts'; +import { construct as construct, type LibSQLDatabase } from './driver-core.ts'; -export class LibSQLDatabase< - TSchema extends Record = Record, -> extends BaseSQLiteDatabase<'async', ResultSet, TSchema> { - static override readonly [entityKind]: string = 'LibSQLDatabase'; - - /** @internal */ - declare readonly session: LibSQLSession>; - - async batch, T extends Readonly<[U, ...U[]]>>( - batch: T, - ): Promise> { - return this.session.batch(batch) as Promise>; - } -} - -function construct< - TSchema extends Record = Record, ->(client: Client, config: DrizzleConfig = {}): LibSQLDatabase & { - $client: Client; -} { - const dialect = new SQLiteAsyncDialect({ casing: config.casing }); - 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 LibSQLSession(client, dialect, schema, { logger }, undefined); - const db = new LibSQLDatabase('async', dialect, session, schema) as LibSQLDatabase; - ( db).$client = client; - - return db as any; -} +export { LibSQLDatabase } from './driver-core.ts'; export function drizzle< TSchema extends Record = Record, diff --git a/drizzle-orm/src/libsql/http/index.ts b/drizzle-orm/src/libsql/http/index.ts new file mode 100644 index 000000000..fddf910f5 --- /dev/null +++ b/drizzle-orm/src/libsql/http/index.ts @@ -0,0 +1,62 @@ +import { type Client, type Config, createClient } from '@libsql/client/http'; +import { type DrizzleConfig, type IfNotImported, type ImportTypeError, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: IfNotImported< + Client, + [ImportTypeError<'@libsql/client'>], + [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] + > +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/node/index.ts b/drizzle-orm/src/libsql/node/index.ts new file mode 100644 index 000000000..003a5c899 --- /dev/null +++ b/drizzle-orm/src/libsql/node/index.ts @@ -0,0 +1,62 @@ +import { type Client, type Config, createClient } from '@libsql/client/node'; +import { type DrizzleConfig, type IfNotImported, type ImportTypeError, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: IfNotImported< + Client, + [ImportTypeError<'@libsql/client'>], + [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] + > +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/sqlite3/index.ts b/drizzle-orm/src/libsql/sqlite3/index.ts new file mode 100644 index 000000000..0f17ef935 --- /dev/null +++ b/drizzle-orm/src/libsql/sqlite3/index.ts @@ -0,0 +1,62 @@ +import { type Client, type Config, createClient } from '@libsql/client/sqlite3'; +import { type DrizzleConfig, type IfNotImported, type ImportTypeError, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: IfNotImported< + Client, + [ImportTypeError<'@libsql/client'>], + [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] + > +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/wasm/index.ts b/drizzle-orm/src/libsql/wasm/index.ts new file mode 100644 index 000000000..23c4f4dcc --- /dev/null +++ b/drizzle-orm/src/libsql/wasm/index.ts @@ -0,0 +1,62 @@ +import { type Client, type Config, createClient } from '@libsql/client-wasm'; +import { type DrizzleConfig, type IfNotImported, type ImportTypeError, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: IfNotImported< + Client, + [ImportTypeError<'@libsql/client-wasm'>], + [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] + > +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/web/index.ts b/drizzle-orm/src/libsql/web/index.ts new file mode 100644 index 000000000..e7c31c9bb --- /dev/null +++ b/drizzle-orm/src/libsql/web/index.ts @@ -0,0 +1,62 @@ +import { type Client, type Config, createClient } from '@libsql/client/web'; +import { type DrizzleConfig, type IfNotImported, type ImportTypeError, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: IfNotImported< + Client, + [ImportTypeError<'@libsql/client'>], + [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] + > +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/libsql/ws/index.ts b/drizzle-orm/src/libsql/ws/index.ts new file mode 100644 index 000000000..115ec9b24 --- /dev/null +++ b/drizzle-orm/src/libsql/ws/index.ts @@ -0,0 +1,62 @@ +import { type Client, type Config, createClient } from '@libsql/client/ws'; +import { type DrizzleConfig, type IfNotImported, type ImportTypeError, isConfig } from '~/utils.ts'; +import { construct, type LibSQLDatabase } from '../driver-core.ts'; + +export function drizzle< + TSchema extends Record = Record, + TClient extends Client = Client, +>( + ...params: IfNotImported< + Client, + [ImportTypeError<'@libsql/client'>], + [ + TClient | string, + ] | [ + TClient | string, + DrizzleConfig, + ] | [ + ( + & DrizzleConfig + & ({ + connection: string | Config; + } | { + client: TClient; + }) + ), + ] + > +): LibSQLDatabase & { + $client: TClient; +} { + if (typeof params[0] === 'string') { + const instance = createClient({ + url: params[0], + }); + + return construct(instance, params[1]) as any; + } + + if (isConfig(params[0])) { + const { connection, client, ...drizzleConfig } = params[0] as + & { connection?: Config; client?: TClient } + & DrizzleConfig; + + if (client) return construct(client, drizzleConfig) as any; + + const instance = typeof connection === 'string' ? createClient({ url: connection }) : createClient(connection!); + + return construct(instance, drizzleConfig) as any; + } + + return construct(params[0] as TClient, params[1] as DrizzleConfig | undefined) as any; +} + +export namespace drizzle { + export function mock = Record>( + config?: DrizzleConfig, + ): LibSQLDatabase & { + $client: '$client is not available on drizzle.mock()'; + } { + return construct({} as any, config) as any; + } +} diff --git a/drizzle-orm/src/planetscale-serverless/driver.ts b/drizzle-orm/src/planetscale-serverless/driver.ts index 47988e5f3..e8309e10a 100644 --- a/drizzle-orm/src/planetscale-serverless/driver.ts +++ b/drizzle-orm/src/planetscale-serverless/driver.ts @@ -1,4 +1,4 @@ -import type { Config, Connection } from '@planetscale/database'; +import type { Config } from '@planetscale/database'; import { Client } from '@planetscale/database'; import { entityKind } from '~/entity.ts'; import type { Logger } from '~/logger.ts'; @@ -27,7 +27,7 @@ export class PlanetScaleDatabase< function construct< TSchema extends Record = Record, - TClient extends Client | Connection = Client | Connection, + TClient extends Client = Client, >( client: TClient, config: DrizzleConfig = {}, @@ -37,22 +37,7 @@ function construct< // Client is not Drizzle Object, so we can ignore this rule here // eslint-disable-next-line no-instanceof/no-instanceof if (!(client instanceof Client)) { - // Should use error on 0.30.0 release - // throw new DrizzleError({ - // message: `You need to pass an instance of Client: - - // import { Client } from "@planetscale/database"; - - // const client = new Client({ - // host: process.env["DATABASE_HOST"], - // username: process.env["DATABASE_USERNAME"], - // password: process.env["DATABASE_PASSWORD"], - // }); - - // const db = drizzle(client); - // `, - // }); - console.log(`Warning: You need to pass an instance of Client: + throw new Error(`Warning: You need to pass an instance of Client: import { Client } from "@planetscale/database"; @@ -63,8 +48,6 @@ const client = new Client({ }); const db = drizzle(client); - -Starting from version 0.30.0, you will encounter an error if you attempt to use anything other than a Client instance.\nPlease make the necessary changes now to prevent any runtime errors in the future `); } diff --git a/integration-tests/.env.example b/integration-tests/.env.example index c2e01d4f6..ceff7d132 100644 --- a/integration-tests/.env.example +++ b/integration-tests/.env.example @@ -5,6 +5,8 @@ TIDB_CONNECTION_STRING= NEON_CONNECTION_STRING= LIBSQL_URL="file:local.db" LIBSQL_AUTH_TOKEN="ey..." # For Turso only +LIBSQL_REMOTE_URL="libsql://..." +LIBSQL_REMOTE_TOKEN="ey..." AWS_DATA_API_DB= AWS_DATA_API_SECRET_ARN= AWS_DATA_API_RESOURCE_ARN= diff --git a/integration-tests/js-tests/driver-init/commonjs/planetscale.test.cjs b/integration-tests/js-tests/driver-init/commonjs/planetscale.test.cjs index 2a7bffec1..0894d30c2 100644 --- a/integration-tests/js-tests/driver-init/commonjs/planetscale.test.cjs +++ b/integration-tests/js-tests/driver-init/commonjs/planetscale.test.cjs @@ -1,5 +1,5 @@ require('dotenv/config'); -const { Client, Connection } = require('@planetscale/database'); +const { Client } = require('@planetscale/database'); const { drizzle } = require('drizzle-orm/planetscale-serverless'); const { mysql: schema } = require('./schema.cjs'); import { describe, expect } from 'vitest'; @@ -99,48 +99,3 @@ describe('planetscale', async (it) => { expect(db.query.User).not.toStrictEqual(undefined); }); }); - -describe('planetscale:Connection', async (it) => { - it('drizzle(client)', async () => { - const client = new Connection({ - url: process.env['PLANETSCALE_CONNECTION_STRING'], - }); - const db = drizzle(client); - - await db.$client.execute('SELECT 1;'); - - expect(db.$client).not.toBeInstanceOf(Client); - expect(db.$client).toBeInstanceOf(Connection); - }); - - it('drizzle(client, config)', async () => { - const client = new Connection({ - url: process.env['PLANETSCALE_CONNECTION_STRING'], - }); - const db = drizzle(client, { - schema, - }); - - await db.$client.execute('SELECT 1;'); - - expect(db.$client).not.toBeInstanceOf(Client); - expect(db.$client).toBeInstanceOf(Connection); - expect(db.query.User).not.toStrictEqual(undefined); - }); - - it('drizzle({client, ...config})', async () => { - const client = new Connection({ - url: process.env['PLANETSCALE_CONNECTION_STRING'], - }); - const db = drizzle({ - client, - schema, - }); - - await db.$client.execute('SELECT 1;'); - - expect(db.$client).not.toBeInstanceOf(Client); - expect(db.$client).toBeInstanceOf(Connection); - expect(db.query.User).not.toStrictEqual(undefined); - }); -}); diff --git a/integration-tests/js-tests/driver-init/module/planetscale.test.mjs b/integration-tests/js-tests/driver-init/module/planetscale.test.mjs index c56537b06..0a68b7e9f 100644 --- a/integration-tests/js-tests/driver-init/module/planetscale.test.mjs +++ b/integration-tests/js-tests/driver-init/module/planetscale.test.mjs @@ -1,5 +1,5 @@ import 'dotenv/config'; -import { Client, Connection } from '@planetscale/database'; +import { Client } from '@planetscale/database'; import { drizzle } from 'drizzle-orm/planetscale-serverless'; import { describe, expect } from 'vitest'; import { mysql as schema } from './schema.mjs'; @@ -99,48 +99,3 @@ describe('planetscale', async (it) => { expect(db.query.User).not.toStrictEqual(undefined); }); }); - -describe('planetscale:Connection', async (it) => { - it('drizzle(client)', async () => { - const client = new Connection({ - url: process.env['PLANETSCALE_CONNECTION_STRING'], - }); - const db = drizzle(client); - - await db.$client.execute('SELECT 1;'); - - expect(db.$client).not.toBeInstanceOf(Client); - expect(db.$client).toBeInstanceOf(Connection); - }); - - it('drizzle(client, config)', async () => { - const client = new Connection({ - url: process.env['PLANETSCALE_CONNECTION_STRING'], - }); - const db = drizzle(client, { - schema, - }); - - await db.$client.execute('SELECT 1;'); - - expect(db.$client).not.toBeInstanceOf(Client); - expect(db.$client).toBeInstanceOf(Connection); - expect(db.query.User).not.toStrictEqual(undefined); - }); - - it('drizzle({client, ...config})', async () => { - const client = new Connection({ - url: process.env['PLANETSCALE_CONNECTION_STRING'], - }); - const db = drizzle({ - client, - schema, - }); - - await db.$client.execute('SELECT 1;'); - - expect(db.$client).not.toBeInstanceOf(Client); - expect(db.$client).toBeInstanceOf(Connection); - expect(db.query.User).not.toStrictEqual(undefined); - }); -}); diff --git a/integration-tests/tests/imports/index.test.ts b/integration-tests/tests/imports/index.test.ts index 875d1d24c..7a44942fa 100644 --- a/integration-tests/tests/imports/index.test.ts +++ b/integration-tests/tests/imports/index.test.ts @@ -20,7 +20,7 @@ it('dynamic imports check for CommonJS', async () => { const o1 = path.join('drizzle-orm', key); if ( o1.startsWith('drizzle-orm/bun-sqlite') || o1.startsWith('drizzle-orm/pglite') - || o1.startsWith('drizzle-orm/expo-sqlite') + || o1.startsWith('drizzle-orm/expo-sqlite') || o1.startsWith('drizzle-orm/libsql/wasm') ) { continue; } diff --git a/integration-tests/tests/sqlite/libsql-http.test.ts b/integration-tests/tests/sqlite/libsql-http.test.ts new file mode 100644 index 000000000..bd4f66879 --- /dev/null +++ b/integration-tests/tests/sqlite/libsql-http.test.ts @@ -0,0 +1,186 @@ +import { type Client, createClient } from '@libsql/client/http'; +import retry from 'async-retry'; +import { asc, eq, getTableColumns, sql } from 'drizzle-orm'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; +import { drizzle } from 'drizzle-orm/libsql/http'; +import { migrate } from 'drizzle-orm/libsql/migrator'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { anotherUsersMigratorTable, tests, usersMigratorTable, usersOnUpdate } from './sqlite-common'; + +const ENABLE_LOGGING = false; + +let db: LibSQLDatabase; +let client: Client; + +beforeAll(async () => { + const url = process.env['LIBSQL_REMOTE_URL']; + const authToken = process.env['LIBSQL_REMOTE_TOKEN']; + if (!url) { + throw new Error('LIBSQL_REMOTE_URL is not set'); + } + client = await retry(async () => { + client = createClient({ url, authToken }); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.close(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + client?.close(); +}); + +beforeEach((ctx) => { + ctx.sqlite = { + db, + }; +}); + +test('migrator', async () => { + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = await db.select().from(usersMigratorTable).all(); + + await db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = await db.select().from(anotherUsersMigratorTable).all(); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table __drizzle_migrations`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists ${sql.identifier(customTable)}`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite', migrationsTable: customTable }); + + // test if the custom migrations table was created + const res = await db.all(sql`select * from ${sql.identifier(customTable)};`); + expect(res.length > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table ${sql.identifier(customTable)}`); +}); + +test('test $onUpdateFn and $onUpdate works as $default', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists ${usersOnUpdate}`); + + await db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1 not null, + updated_at integer, + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { name: 'John' }, + { name: 'Jane' }, + { name: 'Jack' }, + { name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'John', id: 1, updateCounter: 1, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: 1, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 750; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } +}); + +test('test $onUpdateFn and $onUpdate works updating', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists ${usersOnUpdate}`); + + await db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1, + updated_at integer, + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { name: 'John', alwaysNull: 'this will be null after updating' }, + { name: 'Jane' }, + { name: 'Jack' }, + { name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + await db.update(usersOnUpdate).set({ name: 'Angel' }).where(eq(usersOnUpdate.id, 1)); + await db.update(usersOnUpdate).set({ updateCounter: null }).where(eq(usersOnUpdate.id, 2)); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'Angel', id: 1, updateCounter: 2, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: null, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 750; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } +}); + +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', + 'test $onUpdateFn and $onUpdate works as $default', + 'test $onUpdateFn and $onUpdate works updating', +]); + +tests(); diff --git a/integration-tests/tests/sqlite/libsql-node.test.ts b/integration-tests/tests/sqlite/libsql-node.test.ts new file mode 100644 index 000000000..bfe2c1574 --- /dev/null +++ b/integration-tests/tests/sqlite/libsql-node.test.ts @@ -0,0 +1,97 @@ +import { type Client, createClient } from '@libsql/client/node'; +import retry from 'async-retry'; +import { sql } from 'drizzle-orm'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; +import { migrate } from 'drizzle-orm/libsql/migrator'; +import { drizzle } from 'drizzle-orm/libsql/node'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { anotherUsersMigratorTable, tests, usersMigratorTable } from './sqlite-common'; + +const ENABLE_LOGGING = false; + +let db: LibSQLDatabase; +let client: Client; + +beforeAll(async () => { + const url = process.env['LIBSQL_URL']; + const authToken = process.env['LIBSQL_AUTH_TOKEN']; + if (!url) { + throw new Error('LIBSQL_URL is not set'); + } + client = await retry(async () => { + client = createClient({ url, authToken }); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.close(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + client?.close(); +}); + +beforeEach((ctx) => { + ctx.sqlite = { + db, + }; +}); + +test('migrator', async () => { + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = await db.select().from(usersMigratorTable).all(); + + await db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = await db.select().from(anotherUsersMigratorTable).all(); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table __drizzle_migrations`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists ${sql.identifier(customTable)}`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite', migrationsTable: customTable }); + + // test if the custom migrations table was created + const res = await db.all(sql`select * from ${sql.identifier(customTable)};`); + expect(res.length > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table ${sql.identifier(customTable)}`); +}); + +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', +]); + +tests(); diff --git a/integration-tests/tests/sqlite/libsql-sqlite3.test.ts b/integration-tests/tests/sqlite/libsql-sqlite3.test.ts new file mode 100644 index 000000000..8d8419f17 --- /dev/null +++ b/integration-tests/tests/sqlite/libsql-sqlite3.test.ts @@ -0,0 +1,97 @@ +import { type Client, createClient } from '@libsql/client/sqlite3'; +import retry from 'async-retry'; +import { sql } from 'drizzle-orm'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; +import { migrate } from 'drizzle-orm/libsql/migrator'; +import { drizzle } from 'drizzle-orm/libsql/sqlite3'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { anotherUsersMigratorTable, tests, usersMigratorTable } from './sqlite-common'; + +const ENABLE_LOGGING = false; + +let db: LibSQLDatabase; +let client: Client; + +beforeAll(async () => { + const url = ':memory:'; + client = await retry(async () => { + client = createClient({ url }); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.close(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + client?.close(); +}); + +beforeEach((ctx) => { + ctx.sqlite = { + db, + }; +}); + +test('migrator', async () => { + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = await db.select().from(usersMigratorTable).all(); + + await db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = await db.select().from(anotherUsersMigratorTable).all(); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table __drizzle_migrations`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists ${sql.identifier(customTable)}`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite', migrationsTable: customTable }); + + // test if the custom migrations table was created + const res = await db.all(sql`select * from ${sql.identifier(customTable)};`); + expect(res.length > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table ${sql.identifier(customTable)}`); +}); + +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', + 'transaction', + 'transaction rollback', + 'nested transaction', + 'nested transaction rollback', +]); + +tests(); diff --git a/integration-tests/tests/sqlite/libsql-ws.test.ts b/integration-tests/tests/sqlite/libsql-ws.test.ts new file mode 100644 index 000000000..6d84d6c9e --- /dev/null +++ b/integration-tests/tests/sqlite/libsql-ws.test.ts @@ -0,0 +1,187 @@ +import { type Client, createClient } from '@libsql/client/ws'; +import retry from 'async-retry'; +import { asc, eq, getTableColumns, sql } from 'drizzle-orm'; +import type { LibSQLDatabase } from 'drizzle-orm/libsql'; +import { migrate } from 'drizzle-orm/libsql/migrator'; +import { drizzle } from 'drizzle-orm/libsql/ws'; +import { afterAll, beforeAll, beforeEach, expect, test } from 'vitest'; +import { skipTests } from '~/common'; +import { randomString } from '~/utils'; +import { anotherUsersMigratorTable, tests, usersMigratorTable, usersOnUpdate } from './sqlite-common'; + +const ENABLE_LOGGING = false; + +let db: LibSQLDatabase; +let client: Client; + +beforeAll(async () => { + const url = process.env['LIBSQL_REMOTE_URL']; + const authToken = process.env['LIBSQL_REMOTE_TOKEN']; + if (!url) { + throw new Error('LIBSQL_REMOTE_URL is not set'); + } + client = await retry(async () => { + client = createClient({ url, authToken }); + return client; + }, { + retries: 20, + factor: 1, + minTimeout: 250, + maxTimeout: 250, + randomize: false, + onRetry() { + client?.close(); + }, + }); + db = drizzle(client, { logger: ENABLE_LOGGING }); +}); + +afterAll(async () => { + client?.close(); +}); + +beforeEach((ctx) => { + ctx.sqlite = { + db, + }; +}); + +test('migrator', async () => { + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists __drizzle_migrations`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite' }); + + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result = await db.select().from(usersMigratorTable).all(); + + await db.insert(anotherUsersMigratorTable).values({ name: 'John', email: 'email' }).run(); + const result2 = await db.select().from(anotherUsersMigratorTable).all(); + + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + expect(result2).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table __drizzle_migrations`); +}); + +test('migrator : migrate with custom table', async () => { + const customTable = randomString(); + await db.run(sql`drop table if exists another_users`); + await db.run(sql`drop table if exists users12`); + await db.run(sql`drop table if exists ${sql.identifier(customTable)}`); + + await migrate(db, { migrationsFolder: './drizzle2/sqlite', migrationsTable: customTable }); + + // test if the custom migrations table was created + const res = await db.all(sql`select * from ${sql.identifier(customTable)};`); + expect(res.length > 0).toBeTruthy(); + + // test if the migrated table are working as expected + await db.insert(usersMigratorTable).values({ name: 'John', email: 'email' }); + const result = await db.select().from(usersMigratorTable); + expect(result).toEqual([{ id: 1, name: 'John', email: 'email' }]); + + await db.run(sql`drop table another_users`); + await db.run(sql`drop table users12`); + await db.run(sql`drop table ${sql.identifier(customTable)}`); +}); + +test('test $onUpdateFn and $onUpdate works as $default', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists ${usersOnUpdate}`); + + await db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1 not null, + updated_at integer, + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { name: 'John' }, + { name: 'Jane' }, + { name: 'Jack' }, + { name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'John', id: 1, updateCounter: 1, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: 1, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 750; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } +}); + +test('test $onUpdateFn and $onUpdate works updating', async (ctx) => { + const { db } = ctx.sqlite; + + await db.run(sql`drop table if exists ${usersOnUpdate}`); + + await db.run( + sql` + create table ${usersOnUpdate} ( + id integer primary key autoincrement, + name text not null, + update_counter integer default 1, + updated_at integer, + always_null text + ) + `, + ); + + await db.insert(usersOnUpdate).values([ + { name: 'John', alwaysNull: 'this will be null after updating' }, + { name: 'Jane' }, + { name: 'Jack' }, + { name: 'Jill' }, + ]); + const { updatedAt, ...rest } = getTableColumns(usersOnUpdate); + + await db.update(usersOnUpdate).set({ name: 'Angel' }).where(eq(usersOnUpdate.id, 1)); + await db.update(usersOnUpdate).set({ updateCounter: null }).where(eq(usersOnUpdate.id, 2)); + + const justDates = await db.select({ updatedAt }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + const response = await db.select({ ...rest }).from(usersOnUpdate).orderBy(asc(usersOnUpdate.id)); + + expect(response).toEqual([ + { name: 'Angel', id: 1, updateCounter: 2, alwaysNull: null }, + { name: 'Jane', id: 2, updateCounter: null, alwaysNull: null }, + { name: 'Jack', id: 3, updateCounter: 1, alwaysNull: null }, + { name: 'Jill', id: 4, updateCounter: 1, alwaysNull: null }, + ]); + const msDelay = 750; + + for (const eachUser of justDates) { + expect(eachUser.updatedAt!.valueOf()).toBeGreaterThan(Date.now() - msDelay); + } +}); + +skipTests([ + 'delete with limit and order by', + 'update with limit and order by', + 'join view as subquery', + 'test $onUpdateFn and $onUpdate works as $default', + 'test $onUpdateFn and $onUpdate works updating', +]); + +tests(); diff --git a/integration-tests/tests/sqlite/sqlite-common.ts b/integration-tests/tests/sqlite/sqlite-common.ts index f31bdbbd2..83beff74d 100644 --- a/integration-tests/tests/sqlite/sqlite-common.ts +++ b/integration-tests/tests/sqlite/sqlite-common.ts @@ -63,7 +63,7 @@ export const usersTable = sqliteTable('users', { createdAt: integer('created_at', { mode: 'timestamp' }).notNull().default(sql`strftime('%s', 'now')`), }); -const usersOnUpdate = sqliteTable('users_on_update', { +export const usersOnUpdate = sqliteTable('users_on_update', { id: integer('id').primaryKey({ autoIncrement: true }), name: text('name').notNull(), updateCounter: integer('update_counter').default(sql`1`).$onUpdateFn(() => sql`update_counter + 1`), @@ -1388,7 +1388,7 @@ export function tests() { cityId: integer('city_id').notNull(), }).existing(); - await db.run(sql`create view new_yorkers as ${getViewConfig(newYorkers1).query}`); + await db.run(sql`create view if not exists new_yorkers as ${getViewConfig(newYorkers1).query}`); await db.insert(citiesTable).values([{ name: 'New York' }, { name: 'Paris' }]).run(); @@ -1782,7 +1782,7 @@ export function tests() { await db.run( sql`create table ${users} (id integer not null primary key, name text not null, city_id integer not null)`, ); - await db.run(sql`create view ${newYorkers} as ${getViewConfig(newYorkers).query}`); + await db.run(sql`create view if not exists ${newYorkers} as ${getViewConfig(newYorkers).query}`); db.insert(users).values([ { name: 'John', cityId: 1 }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c064253b8..20ff6ca0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -303,6 +303,9 @@ importers: '@libsql/client': specifier: ^0.10.0 version: 0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3) + '@libsql/client-wasm': + specifier: ^0.10.0 + version: 0.10.0 '@miniflare/d1': specifier: ^2.14.4 version: 2.14.4 @@ -311,7 +314,7 @@ importers: 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 @@ -359,7 +362,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) @@ -548,7 +551,7 @@ importers: version: 3.583.0 '@aws-sdk/credential-providers': specifier: ^3.549.0 - version: 3.569.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)) + version: 3.569.0(@aws-sdk/client-sso-oidc@3.583.0) '@electric-sql/pglite': specifier: ^0.2.12 version: 0.2.12 @@ -3094,6 +3097,11 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@libsql/client-wasm@0.10.0': + resolution: {integrity: sha512-xSlpGdBGEr4mRtjCnDejTqtDpct2ng8cqHUQs+S4xG1yv0h+hLdzOtQJSY9JV9T/2MWWDfdCiEntPs2SdErSJA==} + bundledDependencies: + - '@libsql/libsql-wasm-experimental' + '@libsql/client@0.10.0': resolution: {integrity: sha512-2ERn08T4XOVx34yBtUPq0RDjAdd9TJ5qNH/izugr208ml2F94mk92qC64kXyDVQINodWJvp3kAdq6P4zTtCZ7g==} @@ -10911,12 +10919,12 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-ini@3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0))': + '@aws-sdk/credential-provider-ini@3.568.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0))': dependencies: '@aws-sdk/client-sts': 3.569.0(@aws-sdk/client-sso-oidc@3.569.0) '@aws-sdk/credential-provider-env': 3.568.0 '@aws-sdk/credential-provider-process': 3.568.0 - '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)) + '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/types': 3.567.0 '@smithy/credential-provider-imds': 2.3.0 @@ -10999,13 +11007,13 @@ snapshots: - '@aws-sdk/client-sts' - aws-crt - '@aws-sdk/credential-provider-node@3.569.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0))': + '@aws-sdk/credential-provider-node@3.569.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0))': dependencies: '@aws-sdk/credential-provider-env': 3.568.0 '@aws-sdk/credential-provider-http': 3.568.0 - '@aws-sdk/credential-provider-ini': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/credential-provider-ini': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/credential-provider-process': 3.568.0 - '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)) + '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/types': 3.567.0 '@smithy/credential-provider-imds': 2.3.0 @@ -11086,10 +11094,10 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-sso@3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))': + '@aws-sdk/credential-provider-sso@3.568.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: '@aws-sdk/client-sso': 3.568.0 - '@aws-sdk/token-providers': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)) + '@aws-sdk/token-providers': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/types': 3.567.0 '@smithy/property-provider': 2.2.0 '@smithy/shared-ini-file-loader': 2.4.0 @@ -11143,7 +11151,7 @@ snapshots: '@smithy/types': 3.0.0 tslib: 2.6.2 - '@aws-sdk/credential-providers@3.569.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))': + '@aws-sdk/credential-providers@3.569.0(@aws-sdk/client-sso-oidc@3.583.0)': dependencies: '@aws-sdk/client-cognito-identity': 3.569.0 '@aws-sdk/client-sso': 3.568.0 @@ -11151,10 +11159,10 @@ snapshots: '@aws-sdk/credential-provider-cognito-identity': 3.569.0 '@aws-sdk/credential-provider-env': 3.568.0 '@aws-sdk/credential-provider-http': 3.568.0 - '@aws-sdk/credential-provider-ini': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) - '@aws-sdk/credential-provider-node': 3.569.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/credential-provider-ini': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) + '@aws-sdk/credential-provider-node': 3.569.0(@aws-sdk/client-sso-oidc@3.583.0)(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/credential-provider-process': 3.568.0 - '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0)) + '@aws-sdk/credential-provider-sso': 3.568.0(@aws-sdk/client-sso-oidc@3.583.0) '@aws-sdk/credential-provider-web-identity': 3.568.0(@aws-sdk/client-sts@3.569.0(@aws-sdk/client-sso-oidc@3.569.0)) '@aws-sdk/types': 3.567.0 '@smithy/credential-provider-imds': 2.3.0 @@ -11336,7 +11344,7 @@ snapshots: '@smithy/types': 2.12.0 tslib: 2.6.2 - '@aws-sdk/token-providers@3.568.0(@aws-sdk/client-sso-oidc@3.583.0(@aws-sdk/client-sts@3.583.0))': + '@aws-sdk/token-providers@3.568.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.567.0 @@ -13021,7 +13029,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 @@ -13039,7 +13047,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 @@ -13461,6 +13469,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 + '@libsql/client-wasm@0.10.0': + dependencies: + '@libsql/core': 0.10.0 + js-base64: 3.7.7 + '@libsql/client@0.10.0(bufferutil@4.0.8)(utf-8-validate@6.0.3)': dependencies: '@libsql/core': 0.10.0 @@ -13615,10 +13628,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': {} @@ -13755,7 +13768,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) @@ -13765,7 +13778,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 @@ -13792,14 +13805,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 @@ -13888,16 +13901,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 @@ -13912,7 +13925,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 @@ -13926,7 +13939,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 @@ -13949,12 +13962,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 @@ -15264,7 +15277,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) optional: true '@vitest/ui@1.6.0(vitest@2.1.2)': @@ -17325,35 +17338,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: @@ -17367,24 +17380,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) @@ -18866,12 +18879,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 @@ -18947,13 +18960,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 @@ -18967,7 +18980,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 @@ -18993,7 +19006,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 @@ -19001,7 +19014,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 @@ -19010,7 +19023,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 @@ -19898,10 +19911,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 @@ -19914,19 +19927,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 @@ -19945,14 +19958,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 @@ -21872,15 +21885,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: