From d289ac7c401a8423145795f2d82f64dc6ed64666 Mon Sep 17 00:00:00 2001 From: Daniel Steigerwald Date: Wed, 13 Dec 2023 01:29:17 +0100 Subject: [PATCH] Improve table and database schema DX. --- .changeset/green-cars-lay.md | 37 ++++ apps/web/components/NextJsExample.tsx | 8 +- apps/web/pages/docs/quickstart.mdx | 6 +- apps/web/pages/index.mdx | 16 +- packages/eslint-config-evolu/package.json | 4 +- packages/evolu-common/package.json | 4 +- packages/evolu-common/src/Db.ts | 103 +++++++++- packages/evolu-common/src/ErrorStore.ts | 4 + packages/evolu-common/src/Evolu.ts | 24 +-- packages/evolu-common/src/Public.ts | 1 + packages/evolu-react-native/src/index.ts | 2 + pnpm-lock.yaml | 234 ++++++++++++++++------ 12 files changed, 341 insertions(+), 102 deletions(-) create mode 100644 .changeset/green-cars-lay.md diff --git a/.changeset/green-cars-lay.md b/.changeset/green-cars-lay.md new file mode 100644 index 000000000..b96120f77 --- /dev/null +++ b/.changeset/green-cars-lay.md @@ -0,0 +1,37 @@ +--- +"@evolu/common": major +--- + +Improve table and database schema DX. + +In the previous Evolu version, table and database schemas were created with `S.struct` and validated with createEvolu. Because of how the TypeScript compiler works, type errors were incomprehensible. + +We added two new helper functions to improve a DX: `table` and `database`. + +Previous schema definition: + +```ts +const TodoTable = S.struct({ + id: TodoId, + title: NonEmptyString1000, +}); +const Database = S.struct({ + todo: TodoTable, +}); +``` + +New schema definition: + +```ts +const TodoTable = table({ + id: TodoId, + title: NonEmptyString1000, +}); +const Database = database({ + todo: TodoTable, +}); +``` + +Those two helpers also detect missing ID columns and the usage of reserved columns. + +This update is a breaking change because reserved columns (createdAt, updatedAt, isDeleted) are created with `table` function now. diff --git a/apps/web/components/NextJsExample.tsx b/apps/web/components/NextJsExample.tsx index 82450b10b..0362cad85 100644 --- a/apps/web/components/NextJsExample.tsx +++ b/apps/web/components/NextJsExample.tsx @@ -8,9 +8,11 @@ import { canUseDom, cast, createEvolu, + database, id, jsonArrayFrom, parseMnemonic, + table, useEvolu, useEvoluError, useOwner, @@ -40,7 +42,7 @@ const NonEmptyString50 = String.pipe( ); type NonEmptyString50 = S.Schema.To; -const TodoTable = S.struct({ +const TodoTable = table({ id: TodoId, title: NonEmptyString1000, isCompleted: S.nullable(SqliteBoolean), @@ -51,14 +53,14 @@ type TodoTable = S.Schema.To; const SomeJson = S.struct({ foo: S.string, bar: S.boolean }); type SomeJson = S.Schema.To; -const TodoCategoryTable = S.struct({ +const TodoCategoryTable = table({ id: TodoCategoryId, name: NonEmptyString50, json: S.nullable(SomeJson), }); type TodoCategoryTable = S.Schema.To; -const Database = S.struct({ +const Database = database({ todo: TodoTable, todoCategory: TodoCategoryTable, }); diff --git a/apps/web/pages/docs/quickstart.mdx b/apps/web/pages/docs/quickstart.mdx index 2711b5bad..6c9704fb1 100644 --- a/apps/web/pages/docs/quickstart.mdx +++ b/apps/web/pages/docs/quickstart.mdx @@ -25,19 +25,21 @@ import { SqliteBoolean, createEvolu, id, + table, + database, } from "@evolu/react"; const TodoId = id("Todo"); type TodoId = S.Schema.To; -const TodoTable = S.struct({ +const TodoTable = table({ id: TodoId, title: NonEmptyString1000, isCompleted: SqliteBoolean, }); type TodoTable = S.Schema.To; -const Database = S.struct({ +const Database = database({ todo: TodoTable, }); type Database = S.Schema.To; diff --git a/apps/web/pages/index.mdx b/apps/web/pages/index.mdx index 785b36e89..89e0cd450 100644 --- a/apps/web/pages/index.mdx +++ b/apps/web/pages/index.mdx @@ -32,36 +32,32 @@ import { SqliteBoolean, cast, createEvolu, + database, id, + table, useEvolu, useQuery, } from "@evolu/react"; -// Create TodoId schema. const TodoId = id("Todo"); - // It's branded string: string & Brand<"Id"> & Brand<"Todo"> // TodoId type ensures no other ID can be used where TodoId is expected. type TodoId = S.Schema.To; -// Create TodoTable schema. -const TodoTable = S.struct({ +const TodoTable = table({ id: TodoId, // Note we can enforce NonEmptyString1000. title: NonEmptyString1000, - // SQLite doesn't support the boolean type, so Evolu uses - // SqliteBoolean (a branded number) instead. + // SQLite doesn't support the boolean type, so Evolu uses SqliteBoolean instead. isCompleted: S.nullable(SqliteBoolean), }); type TodoTable = S.Schema.To; -// Create database schema. -const Database = S.struct({ +const Database = database({ todo: TodoTable, }); type Database = S.Schema.To; -// Create Evolu. const evolu = createEvolu(Database); // Create a typed SQL query. Yes, with autocomplete and type-checking. @@ -78,7 +74,7 @@ const allTodos = evolu.createQuery((db) => const allTodosPromise = evolu.loadQuery(allTodos); // Use the query in React reactively (it's updated on a mutation). -const { rows, row } = useQuery(allTodos); +const { rows } = useQuery(allTodos); // Create a todo. const { create } = useEvolu(); diff --git a/packages/eslint-config-evolu/package.json b/packages/eslint-config-evolu/package.json index 230f88f9e..0d6cb1fca 100644 --- a/packages/eslint-config-evolu/package.json +++ b/packages/eslint-config-evolu/package.json @@ -7,8 +7,8 @@ "clean": "rm -rf node_modules" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^6.13.1", - "@typescript-eslint/parser": "^6.13.1", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", "eslint-config-next": "14.0.4", "eslint-config-prettier": "^9.1.0", "eslint-config-turbo": "^1.11.1", diff --git a/packages/evolu-common/package.json b/packages/evolu-common/package.json index 06989833b..eeac39cb7 100644 --- a/packages/evolu-common/package.json +++ b/packages/evolu-common/package.json @@ -53,8 +53,8 @@ "protobuf": "pnpm protoc --ts_out ./src --proto_path protobuf protobuf/Protobuf.proto --ts_opt eslint_disable --ts_opt optimize_code_size && pnpm format" }, "dependencies": { - "@noble/ciphers": "^0.4.0", - "@noble/hashes": "^1.3.2", + "@noble/ciphers": "^0.4.1", + "@noble/hashes": "^1.3.3", "@protobuf-ts/runtime": "^2.9.3", "@scure/bip39": "^1.2.1", "kysely": "^0.26.3", diff --git a/packages/evolu-common/src/Db.ts b/packages/evolu-common/src/Db.ts index 647df449c..5a9d0f51d 100644 --- a/packages/evolu-common/src/Db.ts +++ b/packages/evolu-common/src/Db.ts @@ -13,6 +13,7 @@ import { ReadonlyArray, ReadonlyRecord, String, + Types, pipe, } from "effect"; import * as Kysely from "kysely"; @@ -23,7 +24,7 @@ import { timestampToString, } from "./Crdt.js"; import { Bip39, Mnemonic, NanoId } from "./Crypto.js"; -import { Id } from "./Model.js"; +import { Id, SqliteBoolean, SqliteDate } from "./Model.js"; import { Owner, makeOwner } from "./Owner.js"; import { createMessageTable, @@ -39,13 +40,105 @@ import { isJsonObjectOrArray, } from "./Sqlite.js"; import { Store, makeStore } from "./Store.js"; +import { EvoluTypeError } from "./ErrorStore.js"; export type DatabaseSchema = ReadonlyRecord.ReadonlyRecord; -export type TableSchema = ReadonlyRecord.ReadonlyRecord & { +type TableSchema = ReadonlyRecord.ReadonlyRecord & { readonly id: Id; }; +/** + * Create table schema. + * + * Supported types are null, string, number, Uint8Array, JSON Object, and JSON + * Array. Use SqliteDate for dates and SqliteBoolean for booleans. + * + * Reserved columns are createdAt, updatedAt, isDeleted. Those columns are added + * by default. + * + * @example + * const TodoId = id("Todo"); + * type TodoId = S.Schema.To; + * + * const TodoTable = table({ + * id: TodoId, + * title: NonEmptyString1000, + * isCompleted: S.nullable(SqliteBoolean), + * }); + * type TodoTable = S.Schema.To; + */ +export const table = ( + fields: Fields, + // Because Schema is invariant, we have to do validation like this. +): ValidateFieldsTypes extends true + ? ValidateFieldsNames extends true + ? ValidateFieldsHasId extends true + ? S.Schema< + Types.Simplify< + S.FromStruct & S.Schema.From + >, + Types.Simplify< + S.ToStruct & S.Schema.To + > + > + : EvoluTypeError<"table() called without id column."> + : EvoluTypeError<"table() called with a reserved column. Reserved columns are createdAt, updatedAt, isDeleted. Those columns are added by default."> + : EvoluTypeError<"table() called with unsupported type. Supported types are null, string, number, Uint8Array, JSON Object, and JSON Array. Use SqliteDate for dates and SqliteBoolean for booleans."> => + S.struct(fields).pipe(S.extend(ReservedColumns)) as never; + +const ReservedColumns = S.struct({ + createdAt: SqliteDate, + updatedAt: SqliteDate, + isDeleted: SqliteBoolean, +}); + +type TableFields = Record>; + +type ValidateFieldsTypes = + keyof Fields extends infer K + ? K extends keyof Fields + ? Fields[K] extends TableFields + ? ValidateFieldsTypes + : // eslint-disable-next-line @typescript-eslint/no-unused-vars + Fields[K] extends S.Schema + ? A extends Value + ? true + : false + : never + : never + : never; + +type ValidateFieldsNames = + keyof Fields extends infer K + ? K extends keyof Fields + ? K extends "createdAt" | "updatedAt" | "isDeleted" + ? false + : true + : never + : never; + +type ValidateFieldsHasId = "id" extends keyof Fields + ? true + : false; + +/** + * Create database schema. + * + * Tables with a name prefixed with _ are local-only, which means they are not + * synced. Local-only tables are useful for device-specific or temporal data. + * + * @example + * const Database = database({ + * // A local-only table. + * _todo: TodoTable, + * todo: TodoTable, + * todoCategory: TodoCategoryTable, + * }); + * type Database = S.Schema.To; + */ +export const database = S.struct; + // https://blog.beraliv.dev/2021-05-07-opaque-type-in-typescript declare const __queryBrand: unique symbol; @@ -168,8 +261,6 @@ const getPropertySignatures = ( return out as any; }; -const commonColumns = ["createdAt", "updatedAt", "isDeleted"] as const; - export const schemaToTables = (schema: S.Schema): Tables => pipe( getPropertySignatures(schema), @@ -177,9 +268,7 @@ export const schemaToTables = (schema: S.Schema): Tables => ReadonlyArray.map( ([name, schema]): Table => ({ name, - columns: Object.keys(getPropertySignatures(schema)).concat( - commonColumns, - ), + columns: Object.keys(getPropertySignatures(schema)), }), ), ); diff --git a/packages/evolu-common/src/ErrorStore.ts b/packages/evolu-common/src/ErrorStore.ts index ccf35c961..d96f50982 100644 --- a/packages/evolu-common/src/ErrorStore.ts +++ b/packages/evolu-common/src/ErrorStore.ts @@ -40,3 +40,7 @@ interface TransferableError { readonly message: string; readonly stack: string | undefined; } + +export interface EvoluTypeError { + readonly __evoluTypeError__: E; +} diff --git a/packages/evolu-common/src/Evolu.ts b/packages/evolu-common/src/Evolu.ts index a3fc48f62..f65d4f977 100644 --- a/packages/evolu-common/src/Evolu.ts +++ b/packages/evolu-common/src/Evolu.ts @@ -298,23 +298,15 @@ type QueryCallback = ( ) => Kysely.SelectQueryBuilder; type QuerySchema = { - readonly [Table in keyof S]: NullableExceptId< - { - readonly [Column in keyof S[Table]]: S[Table][Column]; - } & CommonColumns - >; + readonly [Table in keyof S]: NullableExceptId<{ + readonly [Column in keyof S[Table]]: S[Table][Column]; + }>; }; type NullableExceptId = { readonly [K in keyof T]: K extends "id" ? T[K] : T[K] | null; }; -export interface CommonColumns { - readonly createdAt: SqliteDate; - readonly updatedAt: SqliteDate; - readonly isDeleted: SqliteBoolean; -} - const kysely = new Kysely.Kysely>({ dialect: { createAdapter: (): Kysely.DialectAdapter => new Kysely.SqliteAdapter(), @@ -568,10 +560,12 @@ export type Mutate< table: K, values: Kysely.Simplify< Mode extends "create" - ? PartialForNullable>> - : Partial< - Castable & Pick> - > & { readonly id: S[K]["id"] } + ? PartialForNullable< + Castable> + > + : Partial>> & { + readonly id: S[K]["id"]; + } >, onComplete?: () => void, ) => { diff --git a/packages/evolu-common/src/Public.ts b/packages/evolu-common/src/Public.ts index f905166f6..6972fcc10 100644 --- a/packages/evolu-common/src/Public.ts +++ b/packages/evolu-common/src/Public.ts @@ -3,6 +3,7 @@ export type { Timestamp, TimestampError } from "./Crdt.js"; export type { Mnemonic, InvalidMnemonicError } from "./Crypto.js"; export type { EvoluError, UnexpectedError } from "./ErrorStore.js"; export * from "./Model.js"; +export { table, database } from "./Db.js"; export type { Owner, OwnerId } from "./Owner.js"; export { canUseDom } from "./Platform.js"; export type { SyncState } from "./SyncWorker.js"; diff --git a/packages/evolu-react-native/src/index.ts b/packages/evolu-react-native/src/index.ts index 99081b946..bac1481be 100644 --- a/packages/evolu-react-native/src/index.ts +++ b/packages/evolu-react-native/src/index.ts @@ -36,6 +36,8 @@ export { canUseDom, cast, id, + table, + database, } from "@evolu/common"; export type { EvoluError, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03235a735..6e869cff0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,11 +195,11 @@ importers: packages/eslint-config-evolu: dependencies: '@typescript-eslint/eslint-plugin': - specifier: ^6.13.1 - version: 6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.3) + specifier: ^6.14.0 + version: 6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.55.0)(typescript@5.3.3) '@typescript-eslint/parser': - specifier: ^6.13.1 - version: 6.13.2(eslint@8.55.0)(typescript@5.3.3) + specifier: ^6.14.0 + version: 6.14.0(eslint@8.55.0)(typescript@5.3.3) eslint-config-next: specifier: 14.0.4 version: 14.0.4(eslint@8.55.0)(typescript@5.3.3) @@ -235,11 +235,11 @@ importers: packages/evolu-common: dependencies: '@noble/ciphers': - specifier: ^0.4.0 - version: 0.4.0 + specifier: ^0.4.1 + version: 0.4.1 '@noble/hashes': - specifier: ^1.3.2 - version: 1.3.2 + specifier: ^1.3.3 + version: 1.3.3 '@protobuf-ts/runtime': specifier: ^2.9.3 version: 2.9.3 @@ -282,7 +282,7 @@ importers: version: 5.3.3 vitest: specifier: ^1.0.4 - version: 1.0.4(@types/node@20.10.4) + version: 1.0.4 packages/evolu-common-react: devDependencies: @@ -3322,12 +3322,12 @@ packages: dev: false optional: true - /@noble/ciphers@0.4.0: - resolution: {integrity: sha512-xaUaUUDWbHIFSxaQ/pIe+33VG2mfJp6N/KxKLmZr5biWdNznCAmfu24QRhX10BbVAuqOahAoyp0S4M9md6GPDw==} + /@noble/ciphers@0.4.1: + resolution: {integrity: sha512-QCOA9cgf3Rc33owG0AYBB9wszz+Ul2kramWN8tXG44Gyciud/tbkEqvxRF/IpqQaBpRBNi9f4jdNxqB2CQCIXg==} dev: false - /@noble/hashes@1.3.2: - resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} + /@noble/hashes@1.3.3: + resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} engines: {node: '>= 16'} dev: false @@ -3746,7 +3746,7 @@ packages: /@scure/bip39@1.2.1: resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} dependencies: - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@scure/base': 1.1.3 dev: false @@ -4069,8 +4069,8 @@ packages: dependencies: '@types/yargs-parser': 21.0.3 - /@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.3): - resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==} + /@typescript-eslint/eslint-plugin@6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.55.0)(typescript@5.3.3): + resolution: {integrity: sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -4081,11 +4081,11 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.3) - '@typescript-eslint/scope-manager': 6.13.2 - '@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.3) - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.13.2 + '@typescript-eslint/parser': 6.14.0(eslint@8.55.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.14.0 + '@typescript-eslint/type-utils': 6.14.0(eslint@8.55.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.14.0(eslint@8.55.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.14.0 debug: 4.3.4 eslint: 8.55.0 graphemer: 1.4.0 @@ -4098,8 +4098,8 @@ packages: - supports-color dev: false - /@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.3): - resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==} + /@typescript-eslint/parser@6.14.0(eslint@8.55.0)(typescript@5.3.3): + resolution: {integrity: sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -4108,10 +4108,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.13.2 - '@typescript-eslint/types': 6.13.2 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.3) - '@typescript-eslint/visitor-keys': 6.13.2 + '@typescript-eslint/scope-manager': 6.14.0 + '@typescript-eslint/types': 6.14.0 + '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.14.0 debug: 4.3.4 eslint: 8.55.0 typescript: 5.3.3 @@ -4119,16 +4119,16 @@ packages: - supports-color dev: false - /@typescript-eslint/scope-manager@6.13.2: - resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==} + /@typescript-eslint/scope-manager@6.14.0: + resolution: {integrity: sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.13.2 - '@typescript-eslint/visitor-keys': 6.13.2 + '@typescript-eslint/types': 6.14.0 + '@typescript-eslint/visitor-keys': 6.14.0 dev: false - /@typescript-eslint/type-utils@6.13.2(eslint@8.55.0)(typescript@5.3.3): - resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==} + /@typescript-eslint/type-utils@6.14.0(eslint@8.55.0)(typescript@5.3.3): + resolution: {integrity: sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -4137,8 +4137,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.3) - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.14.0(eslint@8.55.0)(typescript@5.3.3) debug: 4.3.4 eslint: 8.55.0 ts-api-utils: 1.0.3(typescript@5.3.3) @@ -4147,13 +4147,13 @@ packages: - supports-color dev: false - /@typescript-eslint/types@6.13.2: - resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==} + /@typescript-eslint/types@6.14.0: + resolution: {integrity: sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==} engines: {node: ^16.0.0 || >=18.0.0} dev: false - /@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.3): - resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==} + /@typescript-eslint/typescript-estree@6.14.0(typescript@5.3.3): + resolution: {integrity: sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -4161,8 +4161,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.13.2 - '@typescript-eslint/visitor-keys': 6.13.2 + '@typescript-eslint/types': 6.14.0 + '@typescript-eslint/visitor-keys': 6.14.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -4173,8 +4173,8 @@ packages: - supports-color dev: false - /@typescript-eslint/utils@6.13.2(eslint@8.55.0)(typescript@5.3.3): - resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==} + /@typescript-eslint/utils@6.14.0(eslint@8.55.0)(typescript@5.3.3): + resolution: {integrity: sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -4182,9 +4182,9 @@ packages: '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) '@types/json-schema': 7.0.15 '@types/semver': 7.5.6 - '@typescript-eslint/scope-manager': 6.13.2 - '@typescript-eslint/types': 6.13.2 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.14.0 + '@typescript-eslint/types': 6.14.0 + '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) eslint: 8.55.0 semver: 7.5.4 transitivePeerDependencies: @@ -4192,11 +4192,11 @@ packages: - typescript dev: false - /@typescript-eslint/visitor-keys@6.13.2: - resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==} + /@typescript-eslint/visitor-keys@6.14.0: + resolution: {integrity: sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.13.2 + '@typescript-eslint/types': 6.14.0 eslint-visitor-keys: 3.4.3 dev: false @@ -6338,11 +6338,11 @@ packages: dependencies: '@next/eslint-plugin-next': 14.0.4 '@rushstack/eslint-patch': 1.6.0 - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.14.0(eslint@8.55.0)(typescript@5.3.3) eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.55.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.55.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.55.0) eslint-plugin-react: 7.33.2(eslint@8.55.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.55.0) @@ -6380,7 +6380,7 @@ packages: - supports-color dev: false - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.55.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.55.0): resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -6390,8 +6390,8 @@ packages: debug: 4.3.4 enhanced-resolve: 5.15.0 eslint: 8.55.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) - eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) fast-glob: 3.3.2 get-tsconfig: 4.7.2 is-core-module: 2.13.1 @@ -6403,7 +6403,7 @@ packages: - supports-color dev: false - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -6424,11 +6424,11 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.14.0(eslint@8.55.0)(typescript@5.3.3) debug: 3.2.7 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.55.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.55.0) transitivePeerDependencies: - supports-color dev: false @@ -6444,7 +6444,7 @@ packages: regexpp: 3.2.0 dev: false - /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): + /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0): resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==} engines: {node: '>=4'} peerDependencies: @@ -6454,7 +6454,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.14.0(eslint@8.55.0)(typescript@5.3.3) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 @@ -6463,7 +6463,7 @@ packages: doctrine: 2.1.0 eslint: 8.55.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -13208,6 +13208,27 @@ packages: vfile-message: 4.0.2 dev: false + /vite-node@1.0.4: + resolution: {integrity: sha512-9xQQtHdsz5Qn8hqbV7UKqkm8YkJhzT/zr41Dmt5N7AlD8hJXw/Z7y0QiD5I8lnTthV9Rvcvi0QW7PI0Fq83ZPg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 5.0.7 + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-node@1.0.4(@types/node@20.10.4): resolution: {integrity: sha512-9xQQtHdsz5Qn8hqbV7UKqkm8YkJhzT/zr41Dmt5N7AlD8hJXw/Z7y0QiD5I8lnTthV9Rvcvi0QW7PI0Fq83ZPg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -13229,6 +13250,41 @@ packages: - terser dev: true + /vite@5.0.7: + resolution: {integrity: sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==} + 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 + dependencies: + esbuild: 0.19.9 + postcss: 8.4.32 + rollup: 4.7.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vite@5.0.7(@types/node@20.10.4): resolution: {integrity: sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==} engines: {node: ^18.0.0 || >=20.0.0} @@ -13265,6 +13321,62 @@ packages: fsevents: 2.3.3 dev: true + /vitest@1.0.4: + resolution: {integrity: sha512-s1GQHp/UOeWEo4+aXDOeFBJwFzL6mjycbQwwKWX2QcYfh/7tIerS59hWQ20mxzupTJluA2SdwiBuWwQHH67ckg==} + 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.0.0 + '@vitest/ui': ^1.0.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 + dependencies: + '@vitest/expect': 1.0.4 + '@vitest/runner': 1.0.4 + '@vitest/snapshot': 1.0.4 + '@vitest/spy': 1.0.4 + '@vitest/utils': 1.0.4 + acorn-walk: 8.3.1 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.5 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.6.0 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.8.1 + vite: 5.0.7 + vite-node: 1.0.4 + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vitest@1.0.4(@types/node@20.10.4): resolution: {integrity: sha512-s1GQHp/UOeWEo4+aXDOeFBJwFzL6mjycbQwwKWX2QcYfh/7tIerS59hWQ20mxzupTJluA2SdwiBuWwQHH67ckg==} engines: {node: ^18.0.0 || >=20.0.0}