diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..26566dc8 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + root: true, + extends: ["evolu"], + settings: { + next: { + rootDir: ["apps/*/"], + }, + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..69b92637 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +node_modules +.turbo +*.log +.next +dist +dist-ssr +*.local +.env +.cache +server/dist +public/dist +storybook-static/ diff --git a/apps/server/.eslintrc.cjs b/apps/server/.eslintrc.cjs new file mode 100644 index 00000000..edc85074 --- /dev/null +++ b/apps/server/.eslintrc.cjs @@ -0,0 +1,8 @@ +module.exports = { + root: true, + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json"], + }, + extends: ["evolu"], +}; diff --git a/apps/server/.gitignore b/apps/server/.gitignore new file mode 100644 index 00000000..ae911319 --- /dev/null +++ b/apps/server/.gitignore @@ -0,0 +1,11 @@ +/node_modules +/dist + +# misc +.DS_Store +*.pem + +# typescript +*.tsbuildinfo + +db.sqlite \ No newline at end of file diff --git a/apps/server/README.md b/apps/server/README.md new file mode 100644 index 00000000..5ec3335f --- /dev/null +++ b/apps/server/README.md @@ -0,0 +1,3 @@ +# evolu server + +Node.js Evolu server with Sqlite. diff --git a/apps/server/package.json b/apps/server/package.json new file mode 100644 index 00000000..48fd1685 --- /dev/null +++ b/apps/server/package.json @@ -0,0 +1,34 @@ +{ + "name": "server", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "tsc && node dist/index.js", + "build": "rm -rf dist && tsc", + "start": "node dist/index.js", + "lint": "TIMING=1 eslint src --ext .ts,.tsx", + "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" + }, + "dependencies": { + "better-sqlite3": "^7.6.2", + "body-parser": "^1.20.0", + "cors": "^2.8.5", + "evolu": "workspace:0.0.0", + "express": "^4.18.1", + "fp-ts": "^2.12.3" + }, + "devDependencies": { + "@evolu/tsconfig": "workspace:0.0.0", + "@types/better-sqlite3": "^7.6.0", + "@types/body-parser": "^1.19.2", + "@types/cors": "^2.8.12", + "@types/express": "^4.17.14", + "@types/node": "^16.11.60", + "ts-node": "^10.9.1", + "typescript": "^4.8.3" + }, + "engines": { + "node": ">=16.17" + } +} diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts new file mode 100644 index 00000000..ffa38507 --- /dev/null +++ b/apps/server/src/index.ts @@ -0,0 +1,258 @@ +import sqlite3, { Statement } from "better-sqlite3"; +import bodyParser from "body-parser"; +import cors from "cors"; +import { + createInitialMerkleTree, + createSyncTimestamp, + diffMerkleTrees, + EncryptedCrdtMessage, + insertIntoMerkleTree, + MerkleTree, + merkleTreeFromString, + MerkleTreeString, + merkleTreeToString, + Millis, + SyncRequest, + SyncResponse, + timestampFromString, + TimestampString, + timestampToString, +} from "evolu"; +import express from "express"; +import * as fpts from "fp-ts"; +import { either, option, readerEither, readonlyArray } from "fp-ts"; +import { ReaderEither } from "fp-ts/lib/ReaderEither.js"; +import path from "path"; + +// A workaround until fp-ts 3 release. +const { flow, pipe } = fpts.function; + +interface Db { + readonly begin: Statement; + readonly rollback: Statement; + readonly commit: Statement; + readonly selectMerkleTree: Statement; + readonly insertOrIgnoreIntoMessage: Statement; + readonly insertOrReplaceIntoMerkleTree: Statement; + readonly selectMessages: Statement; +} + +interface DbEnv { + readonly db: Db; +} + +interface ReqEnv { + readonly req: SyncRequest; +} + +type DbAndReqEnvs = DbEnv & ReqEnv; + +interface ParseBodyError { + readonly type: "ParseBodyError"; + readonly error: unknown; +} + +interface SqliteError { + readonly type: "SqliteError"; + readonly error: unknown; +} + +const createDb: (fileName: string) => Db = flow( + (fileName) => path.join(process.cwd(), "/", fileName), + sqlite3, + (sqlite) => { + sqlite.exec(` + CREATE TABLE IF NOT EXISTS "message" ( + "timestamp" TEXT, + "userId" TEXT, + "content" BLOB, + PRIMARY KEY(timestamp, userId) + ); + CREATE TABLE IF NOT EXISTS "merkleTree" ( + "userId" TEXT PRIMARY KEY, + "merkleTree" TEXT + ); + `); + + return { + begin: sqlite.prepare(`BEGIN`), + rollback: sqlite.prepare(`ROLLBACK`), + commit: sqlite.prepare(`COMMIT`), + + selectMerkleTree: sqlite.prepare( + `SELECT "merkleTree" FROM "merkleTree" WHERE "userId" = ?` + ), + + insertOrIgnoreIntoMessage: sqlite.prepare(` + INSERT OR IGNORE INTO "message" ( + "timestamp", "userId", "content" + ) VALUES (?, ?, ?) ON CONFLICT DO NOTHING + `), + + insertOrReplaceIntoMerkleTree: sqlite.prepare(` + INSERT OR REPLACE INTO "merkleTree" ( + "userId", "merkleTree" + ) VALUES (?, ?) + `), + + selectMessages: sqlite.prepare(` + SELECT "timestamp", "content" FROM "message" + WHERE "userId" = ? AND "timestamp" > ? AND "timestamp" NOT LIKE '%' || ? + ORDER BY "timestamp" + `), + }; + } +); + +const sqliteErrorFromError = (error: unknown): SqliteError => ({ + type: "SqliteError", + error, +}); + +const parseBody: ReaderEither = (body) => + pipe( + either.tryCatch( + () => SyncRequest.fromBinary(body), + (error): ParseBodyError => ({ type: "ParseBodyError", error }) + ), + either.map((req) => ({ req })) + ); + +const getMerkleTree: ReaderEither = ({ + db, + req, +}) => + pipe( + either.tryCatch( + () => + db.selectMerkleTree.get(req.userId) as + | { readonly merkleTree: MerkleTreeString } + | undefined, + sqliteErrorFromError + ), + either.map((row) => + row ? merkleTreeFromString(row.merkleTree) : createInitialMerkleTree() + ) + ); + +const addMessages = + ( + merkleTree: MerkleTree + ): ReaderEither => + ({ db, req }) => + either.tryCatch( + () => { + if (req.messages.length === 0) return merkleTree; + + db.begin.run(); + req.messages.forEach((message) => { + const result = db.insertOrIgnoreIntoMessage.run( + message.timestamp, + req.userId, + message.content + ); + if (result.changes === 1) + // eslint-disable-next-line no-param-reassign + merkleTree = insertIntoMerkleTree( + timestampFromString(message.timestamp as TimestampString) + )(merkleTree); + }); + db.insertOrReplaceIntoMerkleTree.run( + req.userId, + merkleTreeToString(merkleTree) + ); + db.commit.run(); + return merkleTree; + }, + (error): SqliteError => { + db.rollback.run(); + return sqliteErrorFromError(error); + } + ); + +const getMessages = + ({ + merkleTree, + }: { + readonly merkleTree: MerkleTree; + }): ReaderEither< + DbAndReqEnvs, + SqliteError, + readonly EncryptedCrdtMessage[] + > => + ({ db, req }) => + pipe( + diffMerkleTrees( + merkleTree, + merkleTreeFromString(req.merkleTree as MerkleTreeString) + ), + // TODO: Remove `:Millis` with fp-ts 3 + option.map((millis: Millis) => + either.tryCatch( + () => + db.selectMessages.all( + req.userId, + pipe(millis, createSyncTimestamp, timestampToString), + req.nodeId + ) as readonly EncryptedCrdtMessage[], + sqliteErrorFromError + ) + ), + option.getOrElseW(() => either.right(readonlyArray.empty)) + ); + +const sync: ReaderEither< + DbAndReqEnvs, + SqliteError, + { + readonly merkleTree: MerkleTree; + readonly messages: readonly EncryptedCrdtMessage[]; + } +> = pipe( + getMerkleTree, + readerEither.chain(addMessages), + readerEither.bindTo("merkleTree"), + readerEither.bind("messages", getMessages) +); + +const dbEnv: DbEnv = { db: createDb("db.sqlite") }; + +const app = express(); +app.use(cors()); +app.use(bodyParser.raw({ limit: "20mb" })); + +app.post("/", (req, res) => { + pipe( + parseBody(req.body), + either.map((reqEnv): DbAndReqEnvs => ({ ...dbEnv, ...reqEnv })), + either.chainW(sync), + either.match( + (error) => { + // eslint-disable-next-line no-console + console.log(error); + res.status(500).json("oh noes!"); + }, + ({ merkleTree, messages }) => { + res.setHeader("Content-Type", "application/octet-stream"); + res.send( + Buffer.from( + SyncResponse.toBinary({ + merkleTree: merkleTreeToString(merkleTree), + messages: [...messages], // to mutable array + }) + ) + ); + } + ) + ); +}); + +app.get("/ping", (_req, res) => { + res.send("ok"); +}); + +const port = 4000; +app.listen(port, () => { + // eslint-disable-next-line no-console + console.log(`Server is listening at http://localhost:${port}`); +}); diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json new file mode 100644 index 00000000..3b7801d8 --- /dev/null +++ b/apps/server/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@evolu/tsconfig/universal-esm.json", + "compilerOptions": { + "outDir": "dist", + "module": "Node16", + "moduleResolution": "Node16" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js new file mode 100644 index 00000000..edc85074 --- /dev/null +++ b/apps/web/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + root: true, + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json"], + }, + extends: ["evolu"], +}; diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 00000000..1d67ece7 --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1,30 @@ +## Getting Started + +First, run the development server: + +```bash +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. + +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 00000000..4f11a03d --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/web/next.config.js b/apps/web/next.config.js new file mode 100644 index 00000000..4723b70a --- /dev/null +++ b/apps/web/next.config.js @@ -0,0 +1,17 @@ +// @ts-check +const withTM = require("next-transpile-modules")(["evolu"]); +const withBundleAnalyzer = require("@next/bundle-analyzer")({ + enabled: process.env.ANALYZE === "true", +}); + +/** + * @type {import('next').NextConfig} + **/ +const nextConfig = { + reactStrictMode: true, + experimental: { + esmExternals: "loose", + }, +}; + +module.exports = withBundleAnalyzer(withTM(nextConfig)); diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 00000000..1dba1680 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,31 @@ +{ + "name": "web", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "clean": "rm -rf .turbo && rm -rf node_modules", + "analyze": "cross-env ANALYZE=true npm run build" + }, + "dependencies": { + "@next/bundle-analyzer": "^12.3.1", + "cross-env": "^7.0.3", + "evolu": "workspace:0.0.0", + "next": "^12.3.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@evolu/tsconfig": "workspace:0.0.0", + "@types/node": "^16.11.60", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "eslint": "^8.24.0", + "eslint-config-evolu": "workspace:0.0.0", + "next-transpile-modules": "^9.0.0", + "typescript": "^4.8.3" + } +} diff --git a/apps/web/pages/index.tsx b/apps/web/pages/index.tsx new file mode 100644 index 00000000..b1fffe7b --- /dev/null +++ b/apps/web/pages/index.tsx @@ -0,0 +1,132 @@ +import { createHooks, getOwner, model, resetOwner, restoreOwner } from "evolu"; + +// Every table needs its own branded string ID. +// Branded types is a TypeScript feature to distinguish primitive types. +const TodoId = model.id<"todo">(); +type TodoId = model.infer; + +// Define database schema and create `useQuery` and `useMutation` React Hooks. +const { useQuery, useMutation } = createHooks({ + todo: { + id: TodoId, + // While it's possible to use just string, we don't recommend it. + // NonEmptyString1000 is a non-empty string with a maximum of 1000 chars. + title: model.NonEmptyString1000, + // SQLite has no Boolean datatype, so we emulate it. + isCompleted: model.SqliteBoolean, + }, +}); + +const TodoItem = ({ + row: { id, title, isCompleted }, +}: { + readonly row: { + readonly id: TodoId; + readonly title: model.NonEmptyString1000 | null; + readonly isCompleted: model.SqliteBoolean | null; + }; +}): JSX.Element => { + const { mutate } = useMutation(); + + const handleCompleteClick = (): void => { + mutate("todo", { id, isCompleted: !isCompleted }); + }; + + const handleRenameClick = (): void => { + const newTitle = model.NonEmptyString1000.safeParse( + prompt("What needs to be done?", title || "") + ); + if (!newTitle.success) { + alert(JSON.stringify(newTitle.error, null, 2)); + return; + } + mutate("todo", { id, title: newTitle.data }); + }; + + const handleDeleteClick = (): void => { + mutate("todo", { id, isDeleted: true }); + }; + + return ( +
  • +

    + {title} +

    + + + +
  • + ); +}; + +export default function Web(): JSX.Element { + const { rows } = useQuery((db) => + // Note typed SQL query via https://github.com/koskimas/kysely. + db + .selectFrom("todo") + .select(["id", "title", "isCompleted"]) + // Note auto-generated (isDeleted, createdAt) columns. + .where("isDeleted", "is not", model.cast(true)) + .orderBy("createdAt") + ); + + const { mutate } = useMutation(); + + const handleAddTodoClick = (): void => { + const title = model.NonEmptyString1000.safeParse( + prompt("What needs to be done?") + ); + if (!title.success) { + alert(JSON.stringify(title.error, null, 2)); + return; + } + // Add new todo. Note UI is updated automatically. + mutate("todo", { title: title.data }); + }; + + const handleShowMnemonic = (): void => { + getOwner().then((owner) => { + alert(owner.mnemonic); + }); + }; + + const handleResetOwner = (): void => { + if (confirm("Are you sure? It will delete all your local data.")) + resetOwner(); + }; + + const handleRestoreOwner = (): void => { + const mnemonic = prompt("Your Mnemonic"); + if (mnemonic == null) return; + const either = restoreOwner(mnemonic); + if (either._tag === "Left") alert(JSON.stringify(either.left, null, 2)); + }; + + return ( +
    +

    Evolu TodoMVC

    +
      + {rows.map((row) => ( + + ))} +
    +

    + +

    +

    + + + +

    +

    + Mnemonic is your password generated by Evolu. Try it on another device. +

    +
    + ); +} diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml new file mode 100644 index 00000000..ef66db47 --- /dev/null +++ b/apps/web/pnpm-lock.yaml @@ -0,0 +1,43 @@ +lockfileVersion: 5.4 + +specifiers: + '@evolu/tsconfig': workspace:0.0.0 + '@types/react-dom': ^18.0.6 + eslint-config-evolu: workspace:0.0.0 + evolu: workspace:0.0.0 + +dependencies: + evolu: link:../../packages/evolu + +devDependencies: + '@evolu/tsconfig': link:../../packages/evolu-tsconfig + '@types/react-dom': 18.0.6 + eslint-config-evolu: link:../../packages/eslint-config-evolu + +packages: + + /@types/prop-types/15.7.5: + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + dev: true + + /@types/react-dom/18.0.6: + resolution: {integrity: sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==} + dependencies: + '@types/react': 18.0.20 + dev: true + + /@types/react/18.0.20: + resolution: {integrity: sha512-MWul1teSPxujEHVwZl4a5HxQ9vVNsjTchVA+xRqv/VYGCuKGAU6UhfrTdF5aBefwD1BHUD8i/zq+O/vyCm/FrA==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 + dev: true + + /@types/scheduler/0.16.2: + resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + dev: true + + /csstype/3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + dev: true diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 00000000..0432ba1b --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@evolu/tsconfig/nextjs.json", + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} + diff --git a/package.json b/package.json new file mode 100755 index 00000000..3d4031d9 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "private": true, + "workspaces": [ + "packages/*", + "apps/*" + ], + "scripts": { + "build": "turbo run build", + "dev": "turbo run dev --no-cache --parallel", + "start": "turbo run start --no-cache --parallel", + "lint": "turbo run lint", + "test": "turbo run test", + "clean": "turbo run clean && rm -rf node_modules", + "format": "prettier --write \"**/*.{ts,tsx,md}\"", + "changeset": "changeset", + "version-packages": "changeset version", + "release": "turbo run build --filter=docs^... && changeset publish" + }, + "devDependencies": { + "@changesets/cli": "^2.24.4", + "eslint": "^8.24.0", + "eslint-config-evolu": "workspace:0.0.0", + "prettier": "^2.7.1", + "turbo": "^1.5.3" + }, + "engines": { + "node": ">=16.0.0" + }, + "packageManager": "pnpm@7.9.5" +} diff --git a/packages/eslint-config-evolu/index.js b/packages/eslint-config-evolu/index.js new file mode 100644 index 00000000..4d21b549 --- /dev/null +++ b/packages/eslint-config-evolu/index.js @@ -0,0 +1,29 @@ +module.exports = { + plugins: ["functional", "node"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + "plugin:functional/lite", + "plugin:functional/stylistic", + "plugin:functional/external-recommended", + "next/core-web-vitals", + "turbo", + ], + rules: { + // Default offs from turborepo example. + // Not working for some reason https://github.com/vercel/next.js/discussions/24254 + "@next/next/no-html-link-for-pages": "off", + // Not explained. Do we really need it? + // "react/jsx-key": "off", + + // Evolu + "@typescript-eslint/prefer-readonly-parameter-types": "off", + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "functional/no-return-void": "off", + "react-hooks/exhaustive-deps": "error", + "no-console": "error", + "import/no-cycle": "error", + }, +}; diff --git a/packages/eslint-config-evolu/package.json b/packages/eslint-config-evolu/package.json new file mode 100644 index 00000000..9ed7698e --- /dev/null +++ b/packages/eslint-config-evolu/package.json @@ -0,0 +1,24 @@ +{ + "name": "eslint-config-evolu", + "version": "0.0.0", + "main": "index.js", + "scripts": { + "clean": "rm -rf node_modules" + }, + "dependencies": { + "@typescript-eslint/eslint-plugin": "^5.38.0", + "@typescript-eslint/parser": "^5.38.0", + "eslint-config-next": "^12.3.1", + "eslint-config-prettier": "^8.5.0", + "eslint-config-turbo": "^0.0.4", + "eslint-plugin-functional": "^4.4.0", + "eslint-plugin-node": "^11.1.0" + }, + "devDependencies": { + "eslint": "^8.24.0", + "typescript": "^4.8.3" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/evolu-tsconfig/base.json b/packages/evolu-tsconfig/base.json new file mode 100644 index 00000000..d72a9f3a --- /dev/null +++ b/packages/evolu-tsconfig/base.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Default", + "compilerOptions": { + "composite": false, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "isolatedModules": true, + "moduleResolution": "node", + "noUnusedLocals": false, + "noUnusedParameters": false, + "preserveWatchOutput": true, + "skipLibCheck": true, + "strict": true + }, + "exclude": ["node_modules"] +} diff --git a/packages/evolu-tsconfig/nextjs.json b/packages/evolu-tsconfig/nextjs.json new file mode 100644 index 00000000..e1d8ac68 --- /dev/null +++ b/packages/evolu-tsconfig/nextjs.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Next.js", + "extends": "./base.json", + "compilerOptions": { + "allowJs": true, + "declaration": false, + "declarationMap": false, + "incremental": true, + "jsx": "preserve", + "lib": ["dom", "dom.iterable", "esnext"], + "module": "esnext", + "noEmit": true, + "resolveJsonModule": true, + "strict": true, + "target": "es5" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/packages/evolu-tsconfig/package.json b/packages/evolu-tsconfig/package.json new file mode 100644 index 00000000..b507b4d6 --- /dev/null +++ b/packages/evolu-tsconfig/package.json @@ -0,0 +1,8 @@ +{ + "name": "@evolu/tsconfig", + "version": "0.0.0", + "private": true, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/evolu-tsconfig/universal-esm.json b/packages/evolu-tsconfig/universal-esm.json new file mode 100644 index 00000000..e94529ef --- /dev/null +++ b/packages/evolu-tsconfig/universal-esm.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "ESM for browsers and Node16+", + "extends": "./base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["dom", "esnext"], + "module": "ESNext", + "target": "ES2021" + } +} diff --git a/packages/evolu/.eslintrc.cjs b/packages/evolu/.eslintrc.cjs new file mode 100644 index 00000000..edc85074 --- /dev/null +++ b/packages/evolu/.eslintrc.cjs @@ -0,0 +1,8 @@ +module.exports = { + root: true, + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json"], + }, + extends: ["evolu"], +}; diff --git a/packages/evolu/package.json b/packages/evolu/package.json new file mode 100644 index 00000000..8bdc6c1f --- /dev/null +++ b/packages/evolu/package.json @@ -0,0 +1,73 @@ +{ + "name": "evolu", + "version": "0.0.0", + "description": "Local-first React library with end-to-end encrypted backup and sync without conflicts.", + "keywords": [ + "SQLite", + "local-first", + "CRDT", + "E2EE", + "react" + ], + "author": "Daniel Steigerwald ", + "license": "GNU GPL V3.0", + "bugs": { + "url": "https://github.com/evoluhq/evolu/issues" + }, + "homepage": "https://github.com/evoluhq/evolu", + "type": "module", + "types": "./dist/src/index.d.ts", + "exports": "./dist/src/index.js", + "files": [ + "dist/src/**" + ], + "scripts": { + "dev": "tsc --watch", + "build": "rm -rf dist && tsc", + "lint": "TIMING=1 eslint src --ext .ts,.tsx", + "test": "vitest run", + "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist" + }, + "dependencies": { + "@protobuf-ts/runtime": "^2.8.1", + "immutable-json-patch": "^5.0.0", + "kysely": "^0.21.6", + "murmurhash": "^2.0.1", + "nanoid": "4.0.0", + "rfc6902": "^5.0.1", + "sha256-uint8array": "^0.10.3", + "wa-sqlite": "github:rhashimoto/wa-sqlite#5fdc80ff1c153aeb0dab65a5e23ca22938c827a3" + }, + "devDependencies": { + "@evolu/tsconfig": "workspace:0.0.0", + "@protobuf-ts/plugin": "^2.8.1", + "@types/node": "^16.11.60", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@types/web-locks-api": "^0.0.2", + "cross-env": "^7.0.3", + "esbuild": "^0.15.9", + "esbuild-node-externals": "^1.5.0", + "eslint": "^8.24.0", + "eslint-config-evolu": "workspace:0.0.0", + "fp-ts": "^2.12.3", + "openpgp": "^5.5.0", + "react": "^18.2.0", + "tslib": "^2.4.0", + "typescript": "^4.8.3", + "vitest": "^0.23.4", + "zod": "^3.19.1" + }, + "peerDependencies": { + "fp-ts": "^2.12.3", + "openpgp": "^5.5.0", + "react": "^18.2.0", + "zod": "^3.19.1" + }, + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=16.17" + } +} diff --git a/packages/evolu/src/applyMessages.ts b/packages/evolu/src/applyMessages.ts new file mode 100644 index 00000000..35044eb8 --- /dev/null +++ b/packages/evolu/src/applyMessages.ts @@ -0,0 +1,131 @@ +import { + apply, + ioRef, + option, + readonlyArray, + readonlyRecord, + taskEither, +} from "fp-ts"; +import { constNull, constVoid, flow, pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { ReadonlyNonEmptyArray } from "fp-ts/ReadonlyNonEmptyArray"; +import { ReadonlyRecord } from "fp-ts/ReadonlyRecord"; +import { TaskEither } from "fp-ts/TaskEither"; +import { insertIntoMerkleTree } from "./merkleTree.js"; +import { timestampFromString } from "./timestamp.js"; +import { + CrdtMessage, + CrdtValue, + DbEnv, + MerkleTree, + PreparedStatement, + TimestampString, + UnknownError, +} from "./types.js"; + +export const applyMessages = + (merkleTree: MerkleTree) => + ( + messages: ReadonlyNonEmptyArray + ): ReaderTaskEither => + ({ db }) => + taskEither.bracket( + apply.sequenceT(taskEither.ApplySeq)( + db.prepare(` + SELECT "timestamp" FROM "__message" + WHERE "table" = ? AND + "row" = ? AND + "column" = ? + ORDER BY "timestamp" DESC LIMIT 1 + `), + db.prepare(` + INSERT INTO "__message" ( + "timestamp", "table", "row", "column", "value" + ) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING + `), + pipe( + new ioRef.IORef< + ReadonlyRecord> + >({}), + (cache) => + taskEither.right({ + exec: (sql: string, bindings: readonly CrdtValue[]) => + pipe( + cache.read(), + readonlyRecord.lookup(sql), + option.getOrElse(() => { + const p = db.prepare(sql); + cache.modify(readonlyRecord.upsertAt(sql, p))(); + return p; + }), + taskEither.chain((prepared) => prepared.exec(bindings)) + ), + release: () => + pipe( + Object.values(cache.read()), + taskEither.sequenceSeqArray, + taskEither.chain( + taskEither.traverseSeqArray((p) => p.release()) + ), + taskEither.map(constVoid) + ), + }) + ) + ), + ([selectMostRecentTimestamp, insertMessage, updateTable]) => + pipe( + messages, + taskEither.traverseSeqArray((message) => + pipe( + selectMostRecentTimestamp.exec([ + message.table, + message.row, + message.column, + ]), + taskEither.map( + flow( + readonlyArray.head, + option.map((r) => r.timestamp as TimestampString), + option.getOrElseW(constNull) + ) + ), + taskEither.chainFirst((t) => + t == null || t < message.timestamp + ? updateTable.exec( + ` + INSERT INTO "${message.table}" ("id", "${message.column}") + VALUES (?, ?) + ON CONFLICT DO UPDATE SET "${message.column}" = ? + `, + [message.row, message.value, message.value] + ) + : taskEither.right(undefined) + ), + taskEither.chainFirst((t) => + t == null || t !== message.timestamp + ? pipe( + insertMessage.exec([ + message.timestamp, + message.table, + message.row, + message.column, + message.value, + ]), + taskEither.map(() => { + // eslint-disable-next-line no-param-reassign + merkleTree = insertIntoMerkleTree( + timestampFromString(message.timestamp) + )(merkleTree); + }) + ) + : taskEither.right(undefined) + ) + ) + ), + taskEither.map(() => merkleTree) + ), + flow( + taskEither.traverseArray((a) => a.release()), + taskEither.map(constVoid) + ) + ); diff --git a/packages/evolu/src/config.ts b/packages/evolu/src/config.ts new file mode 100644 index 00000000..a09b2c0a --- /dev/null +++ b/packages/evolu/src/config.ts @@ -0,0 +1,12 @@ +import { Config } from "./types.js"; + +// eslint-disable-next-line functional/no-let +export let config: Config = { + syncUrl: "http://localhost:4000", + log: false, + maxDrift: 60000, +}; + +export const setConfig = (c: Config): void => { + config = c; +}; diff --git a/packages/evolu/src/createHooks.ts b/packages/evolu/src/createHooks.ts new file mode 100644 index 00000000..168cd043 --- /dev/null +++ b/packages/evolu/src/createHooks.ts @@ -0,0 +1,53 @@ +import { readonlyArray } from "fp-ts"; +import { pipe } from "fp-ts/lib/function.js"; +import { useEffect, useSyncExternalStore } from "react"; +import * as db from "./db.js"; +import { kysely } from "./kysely.js"; +import { DbSchema, Mutate, Query, sqlQueryToString } from "./types.js"; + +/** + * Define database schema and create `useQuery` and `useMutation` React Hooks. + */ +export const createHooks = + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + (dbSchema: S) => { + db.updateDbSchema(dbSchema)(); + + const useQuery = ( + query: Query | null | false + ): { + readonly rows: readonly T[]; + readonly row: T | null; + } => { + const sqlQueryString = query + ? pipe(query(kysely as never).compile(), sqlQueryToString) + : null; + + const rows = useSyncExternalStore( + db.listen, + () => db.getSubscribedQueryRows(sqlQueryString) as never, + () => readonlyArray.empty + ); + + useEffect(() => { + if (!sqlQueryString) return; + return db.subscribeQuery(sqlQueryString); + }, [sqlQueryString]); + + return { + rows, + row: rows[0] || null, + }; + }; + + const mutate = db.createMutate(); + + const useMutation = (): { readonly mutate: Mutate } => { + return { mutate }; + }; + + return { + useQuery, + useMutation, + }; + }; diff --git a/packages/evolu/src/db.ts b/packages/evolu/src/db.ts new file mode 100644 index 00000000..331104c0 --- /dev/null +++ b/packages/evolu/src/db.ts @@ -0,0 +1,386 @@ +import { + io, + ioEither, + ioOption, + ioRef, + option, + readonlyArray, + readonlyNonEmptyArray, + readonlyRecord, +} from "fp-ts"; +import { Either } from "fp-ts/Either"; +import { + absurd, + constVoid, + decrement, + flow, + increment, + pipe, +} from "fp-ts/lib/function.js"; +import { IO } from "fp-ts/IO"; +import type { ReadonlyNonEmptyArray } from "fp-ts/ReadonlyNonEmptyArray"; +import { Task } from "fp-ts/Task"; +import type { JSONArray } from "immutable-json-patch"; +import { immutableJSONPatch } from "immutable-json-patch"; +import { config } from "./config.js"; +import { dispatchError } from "./error.js"; +import { cast, createId, ID, Mnemonic, SqliteDateTime } from "./model.js"; +import { reloadAllTabs } from "./reloadAllTabs.js"; +import { safeParseToEither } from "./safeParseToEither.js"; +import { + commonColumns, + DbSchema, + eqSqlQueryString, + Mutate, + NewCrdtMessage, + Owner, + QueriesRowsCache, + QueryPatches, + SQLiteRowRecord, + SqlQueryString, + TableDefinition, + Unsubscribe, +} from "./types.js"; +import { + DbWorkerInput, + DbWorkerInputInit, + DbWorkerOutput, + SyncWorkerInputInit, +} from "./typesBrowser.js"; +import type { ReadonlyRecord } from "fp-ts/ReadonlyRecord"; + +const queriesRowsCacheRef = new ioRef.IORef({}); + +const listeners = new Set>(); + +export const listen = (listener: IO): IO => { + listeners.add(listener); + return () => { + listeners.delete(listener); + }; +}; + +const notifyListeners: IO = () => { + listeners.forEach((listener) => listener()); +}; + +const onQuery = ( + queriesPatches: ReadonlyNonEmptyArray +): IO => + pipe( + queriesPatches, + io.traverseArray(({ query, patches }) => + queriesRowsCacheRef.modify((a) => ({ + ...a, + [query]: immutableJSONPatch((a[query] as JSONArray) || [], patches), + })) + ), + io.chain(() => notifyListeners) + ); + +const query = (queries: readonly SqlQueryString[]): IO => + pipe( + queries, + readonlyNonEmptyArray.fromReadonlyArray, + option.match( + () => constVoid, + (queries) => postDbWorkerInput({ type: "query", queries }) + ) + ); + +const { postDbWorkerInput, owner } = pipe( + new Promise<{ + readonly postDbWorkerInput: (message: DbWorkerInput) => IO; + readonly owner: Owner; + }>((resolve) => { + if (typeof window === "undefined") return; + + // Because Safari does not support nested Web Workers. + // Otherwise, the sync worker would be created within the db worker. + const channel = new MessageChannel(); + + const dbWorker = new Worker(new URL("./db.worker.js", import.meta.url)); + const syncWorker = new Worker(new URL("./sync.worker.js", import.meta.url)); + + const postDbWorkerInput: ( + message: DbWorkerInputInit | DbWorkerInput, + port?: MessagePort + ) => IO = (message, port) => () => + port + ? dbWorker.postMessage(message, [port]) + : dbWorker.postMessage(message); + + const postSyncWorkerInputInit: IO = () => { + const message: SyncWorkerInputInit = { + type: "init", + config, + syncPort: channel.port2, + }; + syncWorker.postMessage(message, [channel.port2]); + }; + + dbWorker.addEventListener( + "message", + ({ data }: MessageEvent) => { + switch (data.type) { + case "onError": + dispatchError(data.error)(); + return; + + case "onInit": + resolve({ postDbWorkerInput, owner: data.owner }); + return; + + case "onQuery": + onQuery(data.queriesPatches)(); + break; + + case "onReceive": + query(Array.from(subscribedQueries.keys()))(); + break; + + case "reloadAllTabs": + reloadAllTabs(); + break; + + default: + absurd(data); + } + } + ); + + pipe( + postDbWorkerInput( + { type: "init", syncPort: channel.port1, config }, + channel.port1 + ), + io.chain(() => postSyncWorkerInputInit), + // For Evolu config to have time to be overridden. + setTimeout + ); + // }); + }), + ( + promise + ): { + readonly postDbWorkerInput: (message: DbWorkerInput) => IO; + readonly owner: Task; + } => ({ + postDbWorkerInput: (message) => () => + promise.then(({ postDbWorkerInput }) => postDbWorkerInput(message)()), + owner: () => promise.then(({ owner }) => owner), + }) +); + +const dbSchemaToTableDefinitions: ( + dbSchema: DbSchema +) => readonly TableDefinition[] = flow( + readonlyRecord.toEntries, + readonlyArray.map( + ([name, columns]): TableDefinition => ({ + name, + columns: Object.keys(columns) + .filter((c) => c !== "id") + .concat(commonColumns), + }) + ) +); + +export const updateDbSchema = (dbSchema: DbSchema): IO => + postDbWorkerInput({ + type: "updateDbSchema", + // Zod is not transferable. + tableDefinitions: dbSchemaToTableDefinitions(dbSchema), + }); + +/** + * Get subscribed queries rows. + * + * @example + * const rows = useSyncExternalStore( + * db.listen, + * () => db.getSubscribedQueryRows(sqlQueryString), + * () => readonlyArray.empty + * ); + */ +export const getSubscribedQueryRows = ( + query: SqlQueryString | null +): readonly SQLiteRowRecord[] => + query == null + ? readonlyArray.empty + : pipe( + queriesRowsCacheRef.read(), + readonlyRecord.lookup(query), + option.getOrElseW(() => readonlyArray.empty) + ); + +const subscribedQueries = new Map(); +const subscribedQueriesSnapshotRef = new ioRef.IORef< + readonly SqlQueryString[] | null +>(null); + +/** + * Subscribe SQL query. + * + * @example + * useEffect(() => { + * if (!sqlQueryString) return; + * return db.subscribeQuery(sqlQueryString); + * }, [sqlQueryString]); + */ +export const subscribeQuery = (sqlQueryString: SqlQueryString): Unsubscribe => { + if (subscribedQueriesSnapshotRef.read() == null) { + subscribedQueriesSnapshotRef.write(Array.from(subscribedQueries.keys()))(); + queueMicrotask(() => { + const subscribedQueriesSnapshot = subscribedQueriesSnapshotRef.read(); + if (subscribedQueriesSnapshot == null) return; + subscribedQueriesSnapshotRef.write(null)(); + + pipe( + Array.from(subscribedQueries.keys()), + readonlyArray.difference(eqSqlQueryString)(subscribedQueriesSnapshot), + query + )(); + }); + } + + const count = subscribedQueries.get(sqlQueryString); + subscribedQueries.set(sqlQueryString, increment(count ?? 0)); + + return () => { + const count = subscribedQueries.get(sqlQueryString); + if (count && count > 1) + subscribedQueries.set(sqlQueryString, decrement(count)); + else subscribedQueries.delete(sqlQueryString); + }; +}; + +const createNewCrdtMessages = ( + table: string, + row: ID<"string">, + values: ReadonlyRecord, + ownerId: ID<"owner">, + now: SqliteDateTime, + isInsert: boolean +): readonly NewCrdtMessage[] => + pipe( + readonlyRecord.toEntries(values), + readonlyArray.filter(([, value]) => value !== undefined), + readonlyArray.map(([key, value]) => [ + key, + typeof value === "boolean" || value instanceof Date + ? cast(value as never) + : value, + ]), + readonlyArray.concatW( + isInsert + ? [ + ["createdAt", now], + ["createdBy", ownerId], + ] + : [["updatedAt", now]] + ), + readonlyArray.map( + ([column, value]) => + ({ + table, + row, + column, + value, + } as NewCrdtMessage) + ) + ); + +export const createMutate = (): Mutate => { + const messageQueueRef = new ioRef.IORef([]); + + return (table, { id, ...values }) => { + const isInsert = id == null; + // eslint-disable-next-line no-param-reassign + if (isInsert) id = createId() as never; + const now = cast(new Date()); + + owner().then((owner) => { + const messages = createNewCrdtMessages( + table as string, + id as ID<"string">, + values, + owner.id, + now, + isInsert + ); + + const runQueueMicrotask = messageQueueRef.read().length === 0; + messageQueueRef.modify((a) => [...a, ...messages])(); + + if (!runQueueMicrotask) return; + pipe( + messageQueueRef.read, + io.chainFirst(() => messageQueueRef.write([])), + io.map(readonlyNonEmptyArray.fromReadonlyArray), + ioOption.chainIOK((messages) => + postDbWorkerInput({ + type: "send", + messages, + queries: Array.from(subscribedQueries.keys()), + }) + ), + queueMicrotask + ); + }); + + return { id } as never; + }; +}; + +export const getOwner: Task = owner; + +export const resetOwner: IO = postDbWorkerInput({ + type: "resetOwner", +}); + +export interface RestoreOwnerError { + readonly type: "invalid mnemonic"; +} + +// It should be IOEither but it would confuse non FP developers. +// nebo, mam po +// spravne by mnemonic mel tohle delat, ale, muze? +// imho ok, jen upravit error + +export const restoreOwner = ( + mnemonic: string +): Either => + pipe( + Mnemonic.safeParse(mnemonic.trim().split(/\s+/g).join(" ")), + safeParseToEither, + ioEither.fromEither, + ioEither.mapLeft((): RestoreOwnerError => ({ type: "invalid mnemonic" })), + ioEither.chainIOK((mnemonic) => + postDbWorkerInput({ type: "restoreOwner", mnemonic }) + ) + )(); + +if (typeof window !== "undefined") { + const sync = (refreshQueries: boolean): IO => + pipe( + () => + refreshQueries + ? readonlyNonEmptyArray.fromArray( + Array.from(subscribedQueries.keys()) + ) + : option.none, + io.chain((queries) => postDbWorkerInput({ type: "sync", queries })) + ); + + const handleReconnect = sync(false); + const handleReshow = sync(true); + + window.addEventListener("online", handleReconnect); + window.addEventListener("focus", handleReshow); + document.addEventListener("visibilitychange", () => { + if (document.visibilityState !== "hidden") handleReshow(); + }); + + handleReconnect(); +} diff --git a/packages/evolu/src/db.worker.ts b/packages/evolu/src/db.worker.ts new file mode 100644 index 00000000..d28515bc --- /dev/null +++ b/packages/evolu/src/db.worker.ts @@ -0,0 +1,141 @@ +import { apply, either, taskEither } from "fp-ts"; +import { Either } from "fp-ts/Either"; +import { constVoid, flow, pipe } from "fp-ts/lib/function.js"; +import { IORef } from "fp-ts/IORef"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { setConfig } from "./config.js"; +import { initDb } from "./initDb.js"; +import { initDbModel } from "./initDbModel.js"; +import { query } from "./query.js"; +import { receive } from "./receive.js"; +import { resetOwner } from "./resetOwner.js"; +import { restoreOwner } from "./restoreOwner.js"; +import { send } from "./send.js"; +import { sync } from "./sync.js"; +import { + createTimeEnv, + DbEnv, + DbTransactionEnv, + EvoluError, + LockManagerEnv, + OwnerEnv, + QueriesRowsCache, + QueriesRowsCacheEnv, + TimeEnv, +} from "./types.js"; +import { + DbWorkerInput, + DbWorkerInputInit, + PostDbWorkerOutputEnv, + PostSyncWorkerInputEnv, + SyncWorkerOutput, +} from "./typesBrowser.js"; +import { updateDbSchema } from "./updateDbSchema.js"; + +const postDbWorkerOutput: PostDbWorkerOutputEnv["postDbWorkerOutput"] = + (message) => () => + self.postMessage(message); + +const onError: (error: EvoluError["error"]) => void = (error) => + postDbWorkerOutput({ type: "onError", error })(); + +type Envs = DbEnv & + OwnerEnv & + PostDbWorkerOutputEnv & + QueriesRowsCacheEnv & + PostSyncWorkerInputEnv & + LockManagerEnv; + +const createWritableStream = ({ + dbTransaction, + ...envs +}: Envs & DbTransactionEnv): WritableStream => + new WritableStream({ + write: flow( + (data): ReaderTaskEither => { + switch (data.type) { + case "updateDbSchema": + return updateDbSchema(data); + case "send": + return send(data); + case "query": + return query(data.queries); + case "receive": + return receive(data); + case "sync": + return sync(data.queries); + case "resetOwner": + return resetOwner; + case "restoreOwner": + return restoreOwner(data.mnemonic); + } + }, + (rte) => rte({ ...envs, ...createTimeEnv() }), + dbTransaction, + (te) => te().then(either.match(onError, constVoid)) + ), + }); + +apply + .sequenceT(taskEither.ApplyPar)( + pipe( + initDb, + taskEither.chain(({ db, dbTransaction }) => + pipe( + initDbModel()({ db }), + dbTransaction, + taskEither.map(({ owner }) => ({ db, dbTransaction, owner })) + ) + ) + ), + pipe( + () => + new Promise>((resolve) => { + addEventListener( + "message", + ({ data }: MessageEvent) => + resolve(either.right(data)), + { once: true } + ); + }) + ) + )() + .then( + either.match(onError, ([envs, { config, syncPort }]) => { + setConfig(config); + + const postSyncWorkerInput: PostSyncWorkerInputEnv["postSyncWorkerInput"] = + (message) => () => syncPort.postMessage(message); + + const queriesRowsCache = new IORef({}); + + const stream = createWritableStream({ + ...envs, + postDbWorkerOutput, + postSyncWorkerInput, + queriesRowsCache, + locks: navigator.locks, + }); + + const writeToStream = (chunk: DbWorkerInput): void => { + const w = stream.getWriter(); + w.write(chunk); + w.releaseLock(); + }; + + addEventListener("message", (e: MessageEvent) => + writeToStream(e.data) + ); + + // eslint-disable-next-line functional/immutable-data + syncPort.onmessage = ({ data }: MessageEvent): void => + pipe( + data, + either.match(onError, (props) => { + writeToStream({ type: "receive", ...props }); + }) + ); + + postDbWorkerOutput({ type: "onInit", owner: envs.owner })(); + }) + ); diff --git a/packages/evolu/src/deleteAllTables.ts b/packages/evolu/src/deleteAllTables.ts new file mode 100644 index 00000000..cb9a28a0 --- /dev/null +++ b/packages/evolu/src/deleteAllTables.ts @@ -0,0 +1,25 @@ +import { taskEither } from "fp-ts"; +import { constVoid, pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { DbEnv, UnknownError } from "./types.js"; + +export const deleteAllTables: ReaderTaskEither = ({ + db, +}) => + pipe( + db.exec(` + SELECT name FROM sqlite_master WHERE type='table' + `), + taskEither.chain( + taskEither.traverseSeqArray(([name]) => + // The dropped table is completely removed from the database schema and + // the disk file. The table can not be recovered. All indices and triggers + // associated with the table are also deleted. + // https://sqlite.org/lang_droptable.html + db.exec(` + DROP TABLE ${name} + `) + ) + ), + taskEither.map(constVoid) + ); diff --git a/packages/evolu/src/error.ts b/packages/evolu/src/error.ts new file mode 100644 index 00000000..1a9b3e61 --- /dev/null +++ b/packages/evolu/src/error.ts @@ -0,0 +1,22 @@ +import { ioRef } from "fp-ts"; +import { IO } from "fp-ts/IO"; +import { EvoluError, Unsubscribe } from "./types.js"; + +const listeners = new Set>(); +const lastErrorRef = new ioRef.IORef(null); + +export const subscribeError = (listener: IO): Unsubscribe => { + listeners.add(listener); + return () => { + listeners.delete(listener); + }; +}; + +export const dispatchError = + (error: EvoluError["error"]): IO => + () => { + lastErrorRef.write({ type: "EvoluError", error })(); + listeners.forEach((listener) => listener()); + }; + +export const getError: IO = () => lastErrorRef.read(); diff --git a/packages/evolu/src/generateMnemonic.ts b/packages/evolu/src/generateMnemonic.ts new file mode 100644 index 00000000..7bc07a67 --- /dev/null +++ b/packages/evolu/src/generateMnemonic.ts @@ -0,0 +1,79 @@ +import { createHash } from "sha256-uint8array"; +import { Mnemonic } from "./model.js"; +import { defaultMnemonicWordList } from "./validateMnemonic.js"; + +const getRandomBytes = (length: number): Uint8Array => { + const randomBytesArray = new Uint8Array(length); + // eslint-disable-next-line functional/no-loop-statement, functional/no-let + for (let i = 0; i < length; i += 65536) { + crypto.getRandomValues( + randomBytesArray.subarray(i, i + Math.min(length - i, 65536)) + ); + } + return randomBytesArray; +}; + +const hexToBytes = (hexString: string): Uint8Array => + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + new Uint8Array(hexString.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))); + +const lpad = (str: string, padString: string, length: number): string => { + // eslint-disable-next-line functional/no-loop-statement + while (str.length < length) { + // eslint-disable-next-line no-param-reassign + str = padString + str; + } + return str; +}; + +const bytesToBinary = (bytes: readonly number[]): string => + bytes.map((x) => lpad(x.toString(2), "0", 8)).join(""); + +const deriveChecksumBits = (entropy: Uint8Array): string => { + const ENT = entropy.length * 8; + const CS = ENT / 32; + + const hash = createHash().update(entropy).digest(); + + return bytesToBinary(Array.from(hash)).slice(0, CS); +}; + +const binaryToByte = (bin: string): number => parseInt(bin, 2); + +const entropyToMnemonic = (entropyInput: string | Uint8Array): string => { + const entropy = + typeof entropyInput === "string" ? hexToBytes(entropyInput) : entropyInput; + + if (entropy.length < 16) { + // eslint-disable-next-line functional/no-throw-statement + throw new Error("INVALID_ENTROPY"); + } + if (entropy.length > 32) { + // eslint-disable-next-line functional/no-throw-statement + throw new Error("INVALID_ENTROPY"); + } + if (entropy.length % 4 !== 0) { + // eslint-disable-next-line functional/no-throw-statement + throw new Error("INVALID_ENTROPY"); + } + + const entropyBits = bytesToBinary(Array.from(entropy)); + const checksumBits = deriveChecksumBits(entropy); + + const bits = entropyBits + checksumBits; + const chunks = bits.match(/(.{1,11})/g); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const words = chunks!.map((binary) => { + const index = binaryToByte(binary); + return defaultMnemonicWordList[index]; + }); + + return words.join(" "); +}; + +// Extracted from bitcoinjs/bip39. +// https://github.com/bitcoinjs/bip39/issues/169#issuecomment-974191980 +export const generateMnemonic = (strength = 128): Mnemonic => { + const bytes = getRandomBytes(strength / 8); + return entropyToMnemonic(bytes) as Mnemonic; +}; diff --git a/packages/evolu/src/index.ts b/packages/evolu/src/index.ts new file mode 100644 index 00000000..f5129bf3 --- /dev/null +++ b/packages/evolu/src/index.ts @@ -0,0 +1,20 @@ +export * from "./config.js"; +export * from "./createHooks.js"; +export * from "./db.js"; +export { getError, subscribeError } from "./error.js"; +export * from "./merkleTree.js"; +export * from "./model.js"; +export * as model from "./model.js"; +export { + CrdtMessageContent, + EncryptedCrdtMessage, + SyncRequest, + SyncResponse, +} from "./protobuf.js"; +export { + createSyncTimestamp, + timestampFromString, + timestampToString, +} from "./timestamp.js"; +export * from "./types.js"; +export * from "./useOwner.js"; diff --git a/packages/evolu/src/initDb.ts b/packages/evolu/src/initDb.ts new file mode 100644 index 00000000..396b0b70 --- /dev/null +++ b/packages/evolu/src/initDb.ts @@ -0,0 +1,154 @@ +import { array, either, ioRef, record, taskEither } from "fp-ts"; +import { pipe } from "fp-ts/lib/function.js"; +import { TaskEither } from "fp-ts/TaskEither"; +import * as SQLite from "wa-sqlite"; +import SQLiteAsyncESMFactory from "wa-sqlite/dist/wa-sqlite-async.mjs"; +// @ts-expect-error Missing types. +import { IDBBatchAtomicVFS } from "wa-sqlite/src/examples/IDBBatchAtomicVFS.js"; +import { + CrdtValue, + Database, + DbEnv, + DbTransactionEnv, + errorToUnknownError, + PreparedStatement, + SQLiteCompatibleType, + SQLiteRow, + SQLiteRowRecord, + UnknownError, +} from "./types.js"; + +export const initDb: TaskEither = + taskEither.tryCatch(async () => { + const asyncModule = await SQLiteAsyncESMFactory(); + const sqlite3 = SQLite.Factory(asyncModule); + sqlite3.vfs_register( + new IDBBatchAtomicVFS("evolu", { durability: "relaxed" }) + ); + const connection = await sqlite3.open_v2("app", undefined, "evolu"); + + const exec: Database["exec"] = (sql) => + taskEither.tryCatch(async () => { + // console.log(sql); + const rowsRef = new ioRef.IORef([]); + await sqlite3.exec(connection, sql, (row) => { + rowsRef.modify((a) => [...a, row])(); + }); + // console.log("v"); + + return rowsRef.read(); + }, errorToUnknownError); + + const changes: Database["changes"] = () => sqlite3.changes(connection); + + // setTimeout(() => { + // exec("select * from __clock")().then((a) => { + // console.log(a); + // }); + // }, 3000); + + // eslint-disable-next-line functional/no-let + let ensureSequentialExecutionPromise = Promise.resolve(); + + const ensureTransactionsSequentialExecution: ( + te: TaskEither + ) => TaskEither = (te) => () => + (ensureSequentialExecutionPromise = ensureSequentialExecutionPromise.then( + () => te() + ) as never); + + // "A good example is when you are processing a database transaction." + // https://rlee.dev/practical-guide-to-fp-ts-part-3 + const dbTransaction: DbTransactionEnv["dbTransaction"] = (te) => + pipe( + exec("BEGIN"), + taskEither.chainW(() => te), + taskEither.chainFirstW(() => exec("COMMIT")), + taskEither.orElseW((originalError) => + pipe( + exec("ROLLBACK"), + taskEither.matchE(taskEither.left, () => + taskEither.left(originalError) + ) + ) + ), + ensureTransactionsSequentialExecution + ); + + const readRows = async ( + stmt: number, + rowsRef: ioRef.IORef + ): Promise => { + const columns = sqlite3.column_names(stmt); + // eslint-disable-next-line functional/no-loop-statement + while ((await sqlite3.step(stmt)) === SQLite.SQLITE_ROW) + pipe(columns, array.zip(sqlite3.row(stmt)), record.fromEntries, (r) => + rowsRef.modify((a) => [...a, r]) + )(); + }; + + const execSqlQuery: Database["execSqlQuery"] = (sqlQuery) => + taskEither.tryCatch(async () => { + // console.log(sqlQuery); + + const rowsRef = new ioRef.IORef([]); + + // eslint-disable-next-line functional/no-loop-statement + for await (const stmt of sqlite3.statements(connection, sqlQuery.sql)) { + sqlite3.bind_collection( + stmt, + // eslint-disable-next-line functional/prefer-readonly-type + sqlQuery.parameters as SQLiteCompatibleType[] + ); + await readRows(stmt, rowsRef); + // sqlQuery has only one statement that is going to be finalized anyway. + // sqlite3.reset(stmt); + } + + return rowsRef.read(); + }, errorToUnknownError); + + const prepare: Database["prepare"] = (sql) => { + const str = sqlite3.str_new(connection, sql); + return pipe( + taskEither.tryCatch( + () => sqlite3.prepare_v2(connection, sqlite3.str_value(str)), + errorToUnknownError + ), + taskEither.chain( + taskEither.fromNullable(errorToUnknownError("prepared is null")) + ), + taskEither.map( + ({ stmt }): PreparedStatement => ({ + exec: (bindings) => + taskEither.tryCatch(async () => { + const rowsRef = new ioRef.IORef([]); + // eslint-disable-next-line functional/prefer-readonly-type + sqlite3.bind_collection(stmt, bindings as CrdtValue[]); + await readRows(stmt, rowsRef); + sqlite3.reset(stmt); + return rowsRef.read(); + }, errorToUnknownError), + release: () => + pipe( + either.tryCatch(() => { + sqlite3.finalize(stmt); + sqlite3.str_finish(str); + }, errorToUnknownError), + taskEither.fromEither + ), + }) + ) + ); + }; + + return { + db: { + exec, + changes, + execSqlQuery, + prepare, + }, + dbTransaction, + }; + }, errorToUnknownError); diff --git a/packages/evolu/src/initDbModel.ts b/packages/evolu/src/initDbModel.ts new file mode 100644 index 00000000..8593eecd --- /dev/null +++ b/packages/evolu/src/initDbModel.ts @@ -0,0 +1,82 @@ +import { taskEither } from "fp-ts"; +import { pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { createHash } from "sha256-uint8array"; +import { createInitialMerkleTree } from "./merkleTree.js"; +import { generateMnemonic } from "./generateMnemonic.js"; +import { Mnemonic, OwnerId } from "./model.js"; +import { createInitialTimestamp, timestampToString } from "./timestamp.js"; +import { + DbEnv, + merkleTreeToString, + Owner, + OwnerEnv, + UnknownError, +} from "./types.js"; + +/** + * 12 words have entropy big enough for SHA256, and we are using only + * 1/3 of it. It's impossible to restore mnemonic from ownerId. + */ +const mnemonicToOwnerId = (mnemonic: Mnemonic): OwnerId => + createHash().update(mnemonic).digest("hex").slice(0, 21) as OwnerId; + +export const initDbModel = + ( + mnemonic: Mnemonic = generateMnemonic() + ): ReaderTaskEither => + ({ db }) => + pipe( + db.exec(` + PRAGMA table_info (__message) + `), + taskEither.chain((rows) => { + const isInitialized = rows.length > 0; + if (isInitialized) return taskEither.right(undefined); + + const timestamp = timestampToString(createInitialTimestamp()); + const merkleTree = merkleTreeToString(createInitialMerkleTree()); + // if (mnemonic == null) mnemonic = generateMnemonic(); + const ownerId = mnemonicToOwnerId(mnemonic); + + return db.exec(` + CREATE TABLE __message ( + "timestamp" BLOB PRIMARY KEY, + "table" BLOB, + "row" BLOB, + "column" BLOB, + "value" BLOB + ); + + CREATE INDEX index__message ON __message ( + "table", + "row", + "column", + "timestamp" + ); + + CREATE TABLE __clock ( + "timestamp" BLOB, + "merkleTree" BLOB + ); + + INSERT INTO __clock ("timestamp", "merkleTree") + VALUES ('${timestamp}', '${merkleTree}'); + + CREATE TABLE __owner ( + "id" BLOB, + "mnemonic" BLOB + ); + + INSERT INTO __owner ("id", "mnemonic") + VALUES ('${ownerId}', '${mnemonic}') + `); + }), + taskEither.chain(() => + db.exec(` + SELECT "id", "mnemonic" FROM __owner LIMIT 1 + `) + ), + taskEither.map(([[id, mnemonic]]) => ({ id, mnemonic } as Owner)), + taskEither.map((owner) => ({ owner })) + ); diff --git a/packages/evolu/src/kysely.ts b/packages/evolu/src/kysely.ts new file mode 100644 index 00000000..7fba1e14 --- /dev/null +++ b/packages/evolu/src/kysely.ts @@ -0,0 +1,27 @@ +import { + DatabaseIntrospector, + Driver, + DummyDriver, + Kysely, + QueryCompiler, + SqliteAdapter, + SqliteIntrospector, + SqliteQueryCompiler, +} from "kysely"; + +export const kysely = new Kysely({ + dialect: { + createAdapter(): SqliteAdapter { + return new SqliteAdapter(); + }, + createDriver(): Driver { + return new DummyDriver(); + }, + createIntrospector(db: Kysely): DatabaseIntrospector { + return new SqliteIntrospector(db); + }, + createQueryCompiler(): QueryCompiler { + return new SqliteQueryCompiler(); + }, + }, +}); diff --git a/packages/evolu/src/log.ts b/packages/evolu/src/log.ts new file mode 100644 index 00000000..040a72b8 --- /dev/null +++ b/packages/evolu/src/log.ts @@ -0,0 +1,37 @@ +import { IO } from "fp-ts/IO"; +import { config } from "./config.js"; +import { LogTarget } from "./types.js"; + +export const log: (target: LogTarget) => (a: A) => IO = + (target) => (a) => () => { + if ( + typeof config.log === "boolean" + ? config.log + : [config.log].flat().includes(target) + ) + // eslint-disable-next-line no-console + console.log(target, a); + }; + +// export const logTaskEitherDuration = +// (te: TaskEither): TaskEither => +// () => { +// const s = performance.now(); +// return te().then((a) => { +// // eslint-disable-next-line no-console +// console.log(performance.now() - s); +// return a; +// }); +// }; + +// export const logReaderTaskEitherDuration = +// (te: ReaderTaskEither): ReaderTaskEither => +// (r) => +// () => { +// const s = performance.now(); +// return te(r)().then((a) => { +// // eslint-disable-next-line no-console +// console.log(performance.now() - s); +// return a; +// }); +// }; diff --git a/packages/evolu/src/merkleTree.ts b/packages/evolu/src/merkleTree.ts new file mode 100644 index 00000000..84da7305 --- /dev/null +++ b/packages/evolu/src/merkleTree.ts @@ -0,0 +1,91 @@ +import { option } from "fp-ts"; +import { Option } from "fp-ts/Option"; +import { timestampToHash } from "./timestamp.js"; +import { MerkleTree, Millis, Timestamp, TimestampHash } from "./types.js"; + +export const createInitialMerkleTree = (): MerkleTree => ({}); + +const insertKey = ({ + tree, + key, + hash, +}: { + readonly tree: MerkleTree; + readonly key: string; + readonly hash: TimestampHash; +}): MerkleTree => { + if (key.length === 0) return tree; + const c = key[0] as "0" | "1" | "2"; + const n = tree[c] || {}; + return { + ...tree, + [c]: { + ...n, + ...insertKey({ tree: n, key: key.slice(1), hash }), + // @ts-expect-error undefined is OK + hash: n.hash ^ hash, + }, + }; +}; + +export const insertIntoMerkleTree = + (timestamp: Timestamp) => + (tree: MerkleTree): MerkleTree => { + // This is a quick way of converting the floating-point value to an integer (the bitwise + // operators only work on 32-bit integers, so it causes the 64-bit float to be converted + // to an integer). In this case, it ensures the base-3 encoded timestamp strings end up + // looking like "1211121022121110" instead of "1211121022121110.11221000121012222". + // https://github.com/jlongster/crdt-example-app/issues/3#issuecomment-599064327 + const key = Number((timestamp.millis / 1000 / 60) | 0).toString(3); + const hash = timestampToHash(timestamp); + return insertKey({ + tree: { + ...tree, + // @ts-expect-error undefined is OK + hash: (tree.hash ^ hash) as TimestampHash, + }, + key, + hash, + }); + }; + +const getKeys = (tree: MerkleTree): readonly ("0" | "1" | "2")[] => + Object.keys(tree).filter((x) => x !== "hash") as readonly ("0" | "1" | "2")[]; + +const keyToTimestamp = (key: string): Millis => { + // 16 is the length of the base 3 value of the current time in + // minutes. Ensure it's padded to create the full value. + const fullkey = key + "0".repeat(16 - key.length); + // Parse the base 3 representation. + return (parseInt(fullkey, 3) * 1000 * 60) as Millis; +}; + +export const diffMerkleTrees = ( + tree1: MerkleTree, + tree2: MerkleTree +): Option => { + if (tree1.hash === tree2.hash) return option.none; + // eslint-disable-next-line functional/no-let + let node1 = tree1; + // eslint-disable-next-line functional/no-let + let node2 = tree2; + // eslint-disable-next-line functional/no-let + let k = ""; + + // eslint-disable-next-line functional/no-loop-statement, no-constant-condition + while (1) { + const keyset = new Set([...getKeys(node1), ...getKeys(node2)]); + const keys = Array.from(keyset).sort(); + const diffkey = keys.find((key) => { + const next1 = node1[key] || {}; + const next2 = node2[key] || {}; + return next1.hash !== next2.hash; + }); + if (!diffkey) return option.some(keyToTimestamp(k)); + k += diffkey; + node1 = node1[diffkey] || {}; + node2 = node2[diffkey] || {}; + } + + return option.none; +}; diff --git a/packages/evolu/src/model.ts b/packages/evolu/src/model.ts new file mode 100644 index 00000000..d565fc56 --- /dev/null +++ b/packages/evolu/src/model.ts @@ -0,0 +1,114 @@ +/** + * The `model` module contains branded types for data modeling. + * You don't have to use branded types, but we recommend it. + * Note Evolu data are eternal. You can't delete local-first data + * because you can't delete data from offline devices - you can + * only mark them as deleted. That's why branded types are so helpful + * because they protect developers against accidental mistakes like + * storing a too large string, for example. + * Such eternal data also need append-only modeling. You can't + * remove a column, but you can decide you don't need it anymore. + * That's why all columns except `id` are nullable by default; + * it's a similar principle to GraphQL nullability. + */ +export { enum } from "zod"; +export type { infer } from "zod"; +import { nanoid } from "nanoid"; +import { BRAND, z, ZodBranded } from "zod"; +import { validateMnemonic } from "./validateMnemonic.js"; + +export type ID = string & BRAND<`${T}Id`>; + +/** + * Create branded ID type for a table. + * + * @example + * const PersonId = id<"person">(); + */ +export const id = (): ZodBranded< + z.ZodEffects, + `${T}Id` +> => + z + .string() + .refine((s) => /^[\w-]{21}$/.test(s)) + .brand<`${T}Id`>(); + +/** + * Create branded ID. + * + * @example + * const id = createId<"Person">(); + */ +export const createId = (): ID => nanoid() as ID; + +export const OwnerId = id<"owner">(); +export type OwnerId = z.TypeOf; + +export const Mnemonic = z.string().refine(validateMnemonic).brand<"Mnemonic">(); +export type Mnemonic = z.infer; + +/** z.string().min(1).max(1000).brand<"NonEmptyString1000">() */ +export const NonEmptyString1000 = z + .string() + .min(1) + .max(1000) + .brand<"NonEmptyString1000">(); +export type NonEmptyString1000 = z.infer; + +/** z.string().max(1000).brand<"String1000">() */ +export const String1000 = z.string().max(1000).brand<"String1000">(); +export type String1000 = z.infer; + +/** z.string().email().brand<"Email">() */ +export const Email = z.string().email().brand<"Email">(); +export type Email = z.infer; + +/** z.string().url().brand<"Url">() */ +export const Url = z.string().url().brand<"Url">(); +export type Url = z.infer; + +/** + * SQLite has no Boolean datatype. For casting, use `model.cast`. + * https://www.sqlite.org/quirks.html#no_separate_boolean_datatype + */ +export const SqliteBoolean = z + .number() + .refine((n) => n === 0 || n === 1) + .brand<"SqliteBoolean">(); +export type SqliteBoolean = z.infer; + +/** + * SQLite has no DateTime datatype. For casting, use `model.cast`. + * https://www.sqlite.org/quirks.html#no_separate_datetime_datatype + */ +export const SqliteDateTime = z + .string() + .refine((s) => !isNaN(new Date(s).getTime())) + .brand<"SqliteDateTime">(); +export type SqliteDateTime = z.infer; + +export function cast(value: boolean): SqliteBoolean; +export function cast(value: SqliteBoolean): boolean; +export function cast(value: Date): SqliteDateTime; +export function cast(value: SqliteDateTime): Date; +export function cast( + value: boolean | SqliteBoolean | Date | SqliteDateTime +): boolean | SqliteBoolean | Date | SqliteDateTime { + if (typeof value === "boolean") + return (value === true ? 1 : 0) as SqliteBoolean; + if (typeof value === "number") return value === 1; + if (value instanceof Date) return value.toISOString() as SqliteDateTime; + return new Date(value); +} + +/** z.number().refine(Number.isSafeInteger).brand<"Integer">() */ +export const Integer = z + .number() + .refine(Number.isSafeInteger) + .brand<"Integer">(); +export type Integer = z.infer; + +/** z.number().refine(Number.isFinite).brand<"Float">() */ +export const Float = z.number().refine(Number.isFinite).brand<"Float">(); +export type Float = z.infer; diff --git a/packages/evolu/src/protobuf.ts b/packages/evolu/src/protobuf.ts new file mode 100644 index 00000000..aab07002 --- /dev/null +++ b/packages/evolu/src/protobuf.ts @@ -0,0 +1,144 @@ +/* eslint-disable */ +// @generated by protobuf-ts 2.7.0 with parameter eslint_disable +// @generated from protobuf file "protobuf.proto" (syntax proto3) +// tslint:disable +import { MessageType } from "@protobuf-ts/runtime"; +/** + * @generated from protobuf message CrdtMessageContent + */ +export interface CrdtMessageContent { + /** + * @generated from protobuf field: string table = 1; + */ + table: string; + /** + * @generated from protobuf field: string row = 2; + */ + row: string; + /** + * @generated from protobuf field: string column = 3; + */ + column: string; + /** + * @generated from protobuf oneof: value + */ + value: { + oneofKind: "stringValue"; + /** + * @generated from protobuf field: string stringValue = 4; + */ + stringValue: string; + } | { + oneofKind: "numberValue"; + /** + * @generated from protobuf field: int32 numberValue = 5; + */ + numberValue: number; + } | { + oneofKind: undefined; + }; +} +/** + * @generated from protobuf message EncryptedCrdtMessage + */ +export interface EncryptedCrdtMessage { + /** + * @generated from protobuf field: string timestamp = 1; + */ + timestamp: string; + /** + * @generated from protobuf field: bytes content = 2; + */ + content: Uint8Array; +} +/** + * @generated from protobuf message SyncRequest + */ +export interface SyncRequest { + /** + * @generated from protobuf field: repeated EncryptedCrdtMessage messages = 1; + */ + messages: EncryptedCrdtMessage[]; + /** + * @generated from protobuf field: string userId = 2; + */ + userId: string; + /** + * @generated from protobuf field: string nodeId = 3; + */ + nodeId: string; + /** + * @generated from protobuf field: string merkleTree = 4; + */ + merkleTree: string; +} +/** + * @generated from protobuf message SyncResponse + */ +export interface SyncResponse { + /** + * @generated from protobuf field: repeated EncryptedCrdtMessage messages = 1; + */ + messages: EncryptedCrdtMessage[]; + /** + * @generated from protobuf field: string merkleTree = 2; + */ + merkleTree: string; +} +// @generated message type with reflection information, may provide speed optimized methods +class CrdtMessageContent$Type extends MessageType { + constructor() { + super("CrdtMessageContent", [ + { no: 1, name: "table", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "row", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "column", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "stringValue", kind: "scalar", oneof: "value", T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "numberValue", kind: "scalar", oneof: "value", T: 5 /*ScalarType.INT32*/ } + ]); + } +} +/** + * @generated MessageType for protobuf message CrdtMessageContent + */ +export const CrdtMessageContent = new CrdtMessageContent$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class EncryptedCrdtMessage$Type extends MessageType { + constructor() { + super("EncryptedCrdtMessage", [ + { no: 1, name: "timestamp", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "content", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } +} +/** + * @generated MessageType for protobuf message EncryptedCrdtMessage + */ +export const EncryptedCrdtMessage = new EncryptedCrdtMessage$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class SyncRequest$Type extends MessageType { + constructor() { + super("SyncRequest", [ + { no: 1, name: "messages", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => EncryptedCrdtMessage }, + { no: 2, name: "userId", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "nodeId", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "merkleTree", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } +} +/** + * @generated MessageType for protobuf message SyncRequest + */ +export const SyncRequest = new SyncRequest$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class SyncResponse$Type extends MessageType { + constructor() { + super("SyncResponse", [ + { no: 1, name: "messages", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => EncryptedCrdtMessage }, + { no: 2, name: "merkleTree", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } +} +/** + * @generated MessageType for protobuf message SyncResponse + */ +export const SyncResponse = new SyncResponse$Type(); diff --git a/packages/evolu/src/protos/protobuf.proto b/packages/evolu/src/protos/protobuf.proto new file mode 100644 index 00000000..37c25d0c --- /dev/null +++ b/packages/evolu/src/protos/protobuf.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +option optimize_for = CODE_SIZE; + +message CrdtMessageContent { + string table = 1; + string row = 2; + string column = 3; + oneof value { + string stringValue = 4; + int32 numberValue = 5; + } +} + +message EncryptedCrdtMessage { + string timestamp = 1; + bytes content = 2; +} + +message SyncRequest { + repeated EncryptedCrdtMessage messages = 1; + string userId = 2; + string nodeId = 3; + string merkleTree = 4; +} + +message SyncResponse { + repeated EncryptedCrdtMessage messages = 1; + string merkleTree = 2; +} \ No newline at end of file diff --git a/packages/evolu/src/query.ts b/packages/evolu/src/query.ts new file mode 100644 index 00000000..a143293d --- /dev/null +++ b/packages/evolu/src/query.ts @@ -0,0 +1,73 @@ +import { + option, + readonlyArray, + readonlyNonEmptyArray, + readonlyRecord, + taskEither, +} from "fp-ts"; +import { constVoid, pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { ReadonlyNonEmptyArray } from "fp-ts/ReadonlyNonEmptyArray"; +import { createPatch } from "rfc6902"; +import { + DbEnv, + QueriesRowsCacheEnv, + QueryPatches, + sqlQueryFromString, + SqlQueryString, + UnknownError, +} from "./types.js"; +import { PostDbWorkerOutputEnv } from "./typesBrowser.js"; + +export const query = + ( + queries: ReadonlyNonEmptyArray + ): ReaderTaskEither< + DbEnv & QueriesRowsCacheEnv & PostDbWorkerOutputEnv, + UnknownError, + void + > => + ({ db, queriesRowsCache, postDbWorkerOutput }) => + pipe( + queries, + taskEither.traverseSeqArray((query) => + pipe( + sqlQueryFromString(query), + db.execSqlQuery, + taskEither.map((rows) => [query, rows] as const) + ) + ), + taskEither.map(readonlyRecord.fromEntries), + taskEither.map((queriesRows) => { + const previous = queriesRowsCache.read(); + const next = { ...previous, ...queriesRows }; + + const queriesPatches = pipe( + Object.keys(queriesRows), + readonlyArray.map( + (query): QueryPatches => ({ + query: query as SqlQueryString, + // TODO: Replace createPatch with own logic. + // For inspiration: https://github.com/chbrown/rfc6902/pull/88 + patches: createPatch( + previous[query as SqlQueryString] || [], + next[query as keyof typeof next] + ), + }) + ), + readonlyArray.filter((a) => a.patches.length > 0) + ); + + queriesRowsCache.write(next)(); + + pipe( + queriesPatches, + readonlyNonEmptyArray.fromReadonlyArray, + option.match( + () => constVoid, + (queriesPatches) => + postDbWorkerOutput({ type: "onQuery", queriesPatches }) + ) + )(); + }) + ); diff --git a/packages/evolu/src/readClock.ts b/packages/evolu/src/readClock.ts new file mode 100644 index 00000000..a0ae0ac6 --- /dev/null +++ b/packages/evolu/src/readClock.ts @@ -0,0 +1,27 @@ +import { taskEither } from "fp-ts"; +import { pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { log } from "./log.js"; +import { timestampFromString } from "./timestamp.js"; +import { + CrdtClock, + DbEnv, + merkleTreeFromString, + MerkleTreeString, + TimestampString, + UnknownError, +} from "./types.js"; + +export const readClock: ReaderTaskEither = ({ + db, +}) => + pipe( + db.exec(` + SELECT "timestamp", "merkleTree" FROM "__clock" limit 1 + `), + taskEither.map(([[timestamp, merkleTree]]) => ({ + timestamp: timestampFromString(timestamp as TimestampString), + merkleTree: merkleTreeFromString(merkleTree as MerkleTreeString), + })), + taskEither.chainFirstIOK(log("clock:read")) + ); diff --git a/packages/evolu/src/receive.ts b/packages/evolu/src/receive.ts new file mode 100644 index 00000000..b3765df7 --- /dev/null +++ b/packages/evolu/src/receive.ts @@ -0,0 +1,201 @@ +import { + either, + option, + readerEither, + readerTaskEither, + readonlyNonEmptyArray, + taskEither, +} from "fp-ts"; +import { Either } from "fp-ts/Either"; +import { constVoid, flow, pipe } from "fp-ts/lib/function.js"; +import { Option } from "fp-ts/Option"; +import { ReaderEither } from "fp-ts/ReaderEither"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { ReadonlyNonEmptyArray } from "fp-ts/ReadonlyNonEmptyArray"; +import { applyMessages } from "./applyMessages.js"; +import { diffMerkleTrees } from "./merkleTree.js"; +import { readClock } from "./readClock.js"; +import { syncIsPendingOrHeld } from "./syncLock.js"; +import { + createSyncTimestamp, + receiveTimestamp, + timestampFromString, + timestampToString, +} from "./timestamp.js"; +import { + CrdtClock, + CrdtMessage, + DbEnv, + LockManagerEnv, + MerkleTree, + Millis, + OwnerEnv, + SyncError, + TimeEnv, + Timestamp, + TimestampCounterOverflowError, + TimestampDriftError, + TimestampDuplicateNodeError, + UnknownError, +} from "./types.js"; +import { + PostDbWorkerOutputEnv, + PostSyncWorkerInputEnv, +} from "./typesBrowser.js"; +import { updateClock } from "./updateClock.js"; + +const receiveMessages = + (timestamp: Timestamp) => + ( + messages: ReadonlyNonEmptyArray + ): ReaderEither< + TimeEnv, + | TimestampDriftError + | TimestampCounterOverflowError + | TimestampDuplicateNodeError, + Timestamp + > => + pipe( + messages, + readerEither.traverseArray((message) => + pipe( + receiveTimestamp(timestamp, timestampFromString(message.timestamp)), + // eslint-disable-next-line no-param-reassign + readerEither.map((t) => (timestamp = t)) + ) + ), + readerEither.map(() => timestamp) + ); + +const handleReceivedMessages = + (clock: CrdtClock) => + ( + messages: ReadonlyNonEmptyArray + ): ReaderTaskEither< + TimeEnv & DbEnv & PostDbWorkerOutputEnv, + | UnknownError + | TimestampDuplicateNodeError + | TimestampDriftError + | TimestampCounterOverflowError, + CrdtClock + > => + pipe( + messages, + receiveMessages(clock.timestamp), + readerTaskEither.fromReaderEither, + readerTaskEither.bindTo("timestamp"), + readerTaskEither.bindW("merkleTree", () => + pipe(messages, applyMessages(clock.merkleTree)) + ), + readerTaskEither.chainFirstW(updateClock), + readerTaskEither.chainFirstW(() => + pipe( + readerTaskEither.ask(), + readerTaskEither.chainFirstIOK(({ postDbWorkerOutput }) => + postDbWorkerOutput({ type: "onReceive" }) + ) + ) + ) + ); + +const ensureDiffIsDifferent = + (previousDiff: Option) => + (diff: Millis): Either => + option.isSome(previousDiff) && previousDiff.value === diff + ? either.left({ type: "SyncError" }) + : either.right(diff); + +const handleMerkleTreesDiff = + ({ + diff, + clock, + }: { + readonly diff: Millis; + readonly clock: CrdtClock; + }): ReaderTaskEither< + DbEnv & PostSyncWorkerInputEnv & OwnerEnv, + UnknownError, + void + > => + ({ db, postSyncWorkerInput, owner }) => + pipe( + db.execSqlQuery({ + sql: ` + SELECT * FROM "__message" where "timestamp" > ? ORDER BY "timestamp" + `, + parameters: [pipe(diff, createSyncTimestamp, timestampToString)], + }), + taskEither.map((a) => a as unknown as readonly CrdtMessage[]), + taskEither.chainIOK( + flow( + readonlyNonEmptyArray.fromReadonlyArray, + option.map((messages) => + postSyncWorkerInput({ + type: "sync", + clock, + messages: option.some(messages), + owner, + previousDiff: option.some(diff), + }) + ), + option.getOrElse(() => constVoid) + ) + ) + ); + +export const receive = ({ + messages, + merkleTree, + previousDiff, +}: { + readonly messages: readonly CrdtMessage[]; + readonly merkleTree: MerkleTree; + readonly previousDiff: Option; +}): ReaderTaskEither< + DbEnv & + TimeEnv & + PostDbWorkerOutputEnv & + PostSyncWorkerInputEnv & + OwnerEnv & + LockManagerEnv, + | TimestampDriftError + | TimestampCounterOverflowError + | TimestampDuplicateNodeError + | SyncError + | UnknownError, + void +> => + pipe( + readClock, + readerTaskEither.chain((clock) => + pipe( + readonlyNonEmptyArray.fromReadonlyArray(messages), + option.match( + () => readerTaskEither.right(clock), + handleReceivedMessages(clock) + ) + ) + ), + readerTaskEither.chainW((clock) => + pipe( + diffMerkleTrees(merkleTree, clock.merkleTree), + option.match( + () => readerTaskEither.right(undefined), + flow( + ensureDiffIsDifferent(previousDiff), + readerTaskEither.fromEither, + readerTaskEither.bindTo("diff"), + readerTaskEither.bindW( + "syncIsPendingOrHeld", + () => syncIsPendingOrHeld + ), + readerTaskEither.chainW(({ diff, syncIsPendingOrHeld }) => + syncIsPendingOrHeld + ? readerTaskEither.right(undefined) + : handleMerkleTreesDiff({ diff, clock }) + ) + ) + ) + ) + ) + ); diff --git a/packages/evolu/src/reloadAllTabs.ts b/packages/evolu/src/reloadAllTabs.ts new file mode 100644 index 00000000..d6469e97 --- /dev/null +++ b/packages/evolu/src/reloadAllTabs.ts @@ -0,0 +1,13 @@ +import { IO } from "fp-ts/IO"; + +const localStorageKey = "evolu:reloadAllTabs"; + +if (typeof window !== "undefined") + window.addEventListener("storage", (e) => { + if (e.key === localStorageKey) location.reload(); + }); + +export const reloadAllTabs: IO = () => { + localStorage.setItem(localStorageKey, Date.now().toString()); + location.reload(); +}; diff --git a/packages/evolu/src/resetOwner.ts b/packages/evolu/src/resetOwner.ts new file mode 100644 index 00000000..10ac7ec4 --- /dev/null +++ b/packages/evolu/src/resetOwner.ts @@ -0,0 +1,22 @@ +import { readerTaskEither } from "fp-ts"; +import { pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { deleteAllTables } from "./deleteAllTables.js"; +import { DbEnv, UnknownError } from "./types.js"; +import { PostDbWorkerOutputEnv } from "./typesBrowser.js"; + +export const resetOwner: ReaderTaskEither< + DbEnv & PostDbWorkerOutputEnv, + UnknownError, + void +> = pipe( + deleteAllTables, + readerTaskEither.chainW(() => + pipe( + readerTaskEither.ask(), + readerTaskEither.chainIOK(({ postDbWorkerOutput }) => + postDbWorkerOutput({ type: "reloadAllTabs" }) + ) + ) + ) +); diff --git a/packages/evolu/src/restoreOwner.ts b/packages/evolu/src/restoreOwner.ts new file mode 100644 index 00000000..0d5775a6 --- /dev/null +++ b/packages/evolu/src/restoreOwner.ts @@ -0,0 +1,24 @@ +import { readerTaskEither } from "fp-ts"; +import { pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { deleteAllTables } from "./deleteAllTables.js"; +import { initDbModel } from "./initDbModel.js"; +import { Mnemonic } from "./model.js"; +import { DbEnv, UnknownError } from "./types.js"; +import { PostDbWorkerOutputEnv } from "./typesBrowser.js"; + +export const restoreOwner = ( + mnemonic: Mnemonic +): ReaderTaskEither => + pipe( + deleteAllTables, + readerTaskEither.chainW(() => initDbModel(mnemonic)), + readerTaskEither.chainW(() => + pipe( + readerTaskEither.ask(), + readerTaskEither.chainIOK(({ postDbWorkerOutput }) => + postDbWorkerOutput({ type: "reloadAllTabs" }) + ) + ) + ) + ); diff --git a/packages/evolu/src/safeParseToEither.ts b/packages/evolu/src/safeParseToEither.ts new file mode 100644 index 00000000..de32d5ae --- /dev/null +++ b/packages/evolu/src/safeParseToEither.ts @@ -0,0 +1,8 @@ +import { either } from "fp-ts"; +import { Either } from "fp-ts/Either"; +import { SafeParseReturnType, ZodError } from "zod"; + +export const safeParseToEither = ( + a: SafeParseReturnType +): Either, Output> => + a.success ? either.right(a.data) : either.left(a.error); diff --git a/packages/evolu/src/send.ts b/packages/evolu/src/send.ts new file mode 100644 index 00000000..e9515211 --- /dev/null +++ b/packages/evolu/src/send.ts @@ -0,0 +1,138 @@ +import { + option, + readerEither, + readerTaskEither, + readonlyNonEmptyArray, + taskEither, +} from "fp-ts"; +import { pipe } from "fp-ts/lib/function.js"; +import { ReaderEither } from "fp-ts/ReaderEither"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { ReadonlyNonEmptyArray } from "fp-ts/ReadonlyNonEmptyArray"; +import { applyMessages } from "./applyMessages.js"; +import { query } from "./query.js"; +import { readClock } from "./readClock.js"; +import { sendTimestamp, timestampToString } from "./timestamp.js"; +import { + CrdtClock, + CrdtMessage, + DbEnv, + NewCrdtMessage, + OwnerEnv, + QueriesRowsCacheEnv, + SqlQueryString, + TimeEnv, + Timestamp, + TimestampCounterOverflowError, + TimestampDriftError, + UnknownError, +} from "./types.js"; +import { + PostDbWorkerOutputEnv, + PostSyncWorkerInputEnv, +} from "./typesBrowser.js"; +import { updateClock } from "./updateClock.js"; + +const sendMessages = + (timestamp: Timestamp) => + ( + messages: ReadonlyNonEmptyArray + ): ReaderEither< + TimeEnv, + TimestampDriftError | TimestampCounterOverflowError, + { + readonly messages: ReadonlyNonEmptyArray; + readonly timestamp: Timestamp; + } + > => + pipe( + messages, + readerEither.traverseReadonlyNonEmptyArrayWithIndex((i, message) => + pipe( + sendTimestamp(timestamp), + readerEither.map((t): CrdtMessage => { + // eslint-disable-next-line no-param-reassign + timestamp = t; + return { + timestamp: timestampToString(t), + table: message.table, + row: message.row, + column: message.column, + value: message.value, + }; + }) + ) + ), + readerEither.map((messages) => ({ messages, timestamp })) + ); + +const callSync = + ({ + messages, + clock, + }: { + readonly messages: ReadonlyNonEmptyArray; + readonly clock: CrdtClock; + }): ReaderTaskEither => + ({ postSyncWorkerInput, owner }) => + taskEither.fromIO( + postSyncWorkerInput({ + type: "sync", + messages: option.some(messages), + clock, + owner, + previousDiff: option.none, + }) + ); + +const queryQueriesIfAny = + (queries: readonly SqlQueryString[]) => + (): ReaderTaskEither< + DbEnv & QueriesRowsCacheEnv & PostDbWorkerOutputEnv, + UnknownError, + void + > => + pipe( + readonlyNonEmptyArray.fromReadonlyArray(queries), + option.match(() => readerTaskEither.right(undefined), query) + ); + +export const send = ({ + messages, + queries, +}: { + readonly messages: ReadonlyNonEmptyArray; + readonly queries: readonly SqlQueryString[]; +}): ReaderTaskEither< + DbEnv & + OwnerEnv & + QueriesRowsCacheEnv & + PostDbWorkerOutputEnv & + PostSyncWorkerInputEnv & + TimeEnv, + UnknownError | TimestampDriftError | TimestampCounterOverflowError, + void +> => + pipe( + readClock, + readerTaskEither.chainW((clock) => + pipe( + messages, + sendMessages(clock.timestamp), + readerTaskEither.fromReaderEither, + readerTaskEither.chainW(({ messages, timestamp }) => + pipe( + messages, + applyMessages(clock.merkleTree), + readerTaskEither.map((merkleTree) => ({ + messages, + clock: { merkleTree, timestamp }, + })) + ) + ) + ) + ), + readerTaskEither.chainFirstW(({ clock }) => updateClock(clock)), + readerTaskEither.chainW(callSync), + readerTaskEither.chainW(queryQueriesIfAny(queries)) + ); diff --git a/packages/evolu/src/sync.ts b/packages/evolu/src/sync.ts new file mode 100644 index 00000000..a15f4f76 --- /dev/null +++ b/packages/evolu/src/sync.ts @@ -0,0 +1,68 @@ +import { option, readerTaskEither } from "fp-ts"; +import { flow, pipe } from "fp-ts/lib/function.js"; +import { Option } from "fp-ts/Option"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { ReadonlyNonEmptyArray } from "fp-ts/ReadonlyNonEmptyArray"; +import { query } from "./query.js"; +import { readClock } from "./readClock.js"; +import { syncIsPendingOrHeld } from "./syncLock.js"; +import { + DbEnv, + LockManagerEnv, + OwnerEnv, + QueriesRowsCacheEnv, + SqlQueryString, + UnknownError, +} from "./types.js"; +import { + PostDbWorkerOutputEnv, + PostSyncWorkerInputEnv, +} from "./typesBrowser.js"; + +const doSync: ( + queries: Option> +) => readerTaskEither.ReaderTaskEither< + DbEnv & + OwnerEnv & + PostSyncWorkerInputEnv & + QueriesRowsCacheEnv & + PostDbWorkerOutputEnv, + UnknownError, + void | undefined +> = flow( + option.match(() => readerTaskEither.right(undefined), query), + readerTaskEither.chainW(() => readClock), + readerTaskEither.chainW((clock) => + pipe( + readerTaskEither.ask(), + readerTaskEither.chainIOK(({ postSyncWorkerInput, owner }) => + postSyncWorkerInput({ + type: "sync", + clock, + owner, + messages: option.none, + previousDiff: option.none, + }) + ) + ) + ) +); + +export const sync = ( + queries: Option> +): ReaderTaskEither< + DbEnv & + OwnerEnv & + PostSyncWorkerInputEnv & + LockManagerEnv & + QueriesRowsCacheEnv & + PostDbWorkerOutputEnv, + UnknownError, + void +> => + pipe( + syncIsPendingOrHeld, + readerTaskEither.chain((syncIsPendingOrHeld) => + syncIsPendingOrHeld ? readerTaskEither.right(undefined) : doSync(queries) + ) + ); diff --git a/packages/evolu/src/sync.worker.ts b/packages/evolu/src/sync.worker.ts new file mode 100644 index 00000000..a04842d6 --- /dev/null +++ b/packages/evolu/src/sync.worker.ts @@ -0,0 +1,247 @@ +import { either, option, task, taskEither } from "fp-ts"; +import { constVoid, flow, pipe } from "fp-ts/lib/function.js"; +import { IO } from "fp-ts/IO"; +import { Task } from "fp-ts/Task"; +import { TaskEither } from "fp-ts/TaskEither"; +import { + createMessage, + decrypt, + encrypt, + readMessage, + // eslint-disable-next-line node/file-extension-in-import +} from "openpgp/lightweight"; +import { config, setConfig } from "./config.js"; +import { log } from "./log.js"; +import { ID, Mnemonic, OwnerId } from "./model.js"; +import { + CrdtMessageContent, + EncryptedCrdtMessage, + SyncRequest, + SyncResponse, +} from "./protobuf.js"; +import { requestSync } from "./syncLock.js"; +import { + CrdtClock, + CrdtMessage, + CrdtValue, + errorToUnknownError, + merkleTreeFromString, + MerkleTreeString, + merkleTreeToString, + TimestampString, + UnknownError, +} from "./types.js"; +import { + SyncWorkerInput, + SyncWorkerInputInit, + SyncWorkerOutput, +} from "./typesBrowser.js"; + +const crdtValueToProtobufFormat = ( + value: CrdtValue +): CrdtMessageContent["value"] => { + switch (typeof value) { + case "string": + return { oneofKind: "stringValue", stringValue: value }; + case "number": + return { oneofKind: "numberValue", numberValue: value }; + default: + return { oneofKind: undefined }; + } +}; + +const encryptMessages = ({ + messages, + mnemonic, +}: { + readonly messages: readonly CrdtMessage[]; + readonly mnemonic: Mnemonic; +}): TaskEither => + pipe( + messages, + taskEither.traverseArray(({ timestamp, ...props }) => + pipe( + CrdtMessageContent.toBinary({ + ...props, + value: crdtValueToProtobufFormat(props.value), + }), + (binary) => + taskEither.tryCatch( + () => createMessage({ binary }), + errorToUnknownError + ), + taskEither.chain((message) => + taskEither.tryCatch( + () => + encrypt({ + message, + passwords: mnemonic, + format: "binary", + // https://github.com/openpgpjs/openpgpjs/discussions/1481#discussioncomment-2125162 + config: { s2kIterationCountByte: 0 }, + }), + errorToUnknownError + ) + ), + taskEither.map( + (content): EncryptedCrdtMessage => ({ + timestamp, + content: content as Uint8Array, + }) + ) + ) + ) + ); + +const createSyncRequest = + ({ + ownerId, + clock, + }: { + readonly ownerId: OwnerId; + readonly clock: CrdtClock; + }) => + (messages: readonly EncryptedCrdtMessage[]): Uint8Array => + SyncRequest.toBinary({ + // eslint-disable-next-line functional/prefer-readonly-type + messages: messages as EncryptedCrdtMessage[], + userId: ownerId, + nodeId: clock.timestamp.node, + merkleTree: merkleTreeToString(clock.merkleTree), + }); + +interface FetchError { + readonly type: "FetchError"; + readonly error: unknown; +} + +// TODO: Add ServerError (can't parse a response or HttpStatus 500 etc.) +const postSyncRequest = ( + body: Uint8Array +): TaskEither => + taskEither.tryCatch( + () => + fetch(config.syncUrl, { + method: "POST", + body, + headers: { + "Content-Type": "application/octet-stream", + "Content-Length": body.length.toString(), + }, + }) + .then((res) => res.arrayBuffer()) + .then(Buffer.from) + .then((b) => SyncResponse.fromBinary(b)), + (error): FetchError => ({ type: "FetchError", error }) + ); + +const decryptMessages = ({ + messages, + mnemonic, +}: { + readonly messages: readonly EncryptedCrdtMessage[]; + readonly mnemonic: Mnemonic; +}): TaskEither => + pipe( + messages, + taskEither.traverseArray((message) => + pipe( + taskEither.tryCatch( + () => + readMessage({ binaryMessage: message.content }) + .then((message) => + decrypt({ message, passwords: mnemonic, format: "binary" }) + ) + .then(({ data }) => + CrdtMessageContent.fromBinary(data as Uint8Array) + ), + errorToUnknownError + ), + taskEither.map( + (content): CrdtMessage => ({ + timestamp: message.timestamp as TimestampString, + table: content.table, + row: content.row as ID, + column: content.column, + value: + content.value.oneofKind === "numberValue" + ? content.value.numberValue + : content.value.oneofKind === "stringValue" + ? content.value.stringValue + : null, + }) + ) + ) + ) + ); + +type PostSyncWorkerOutput = (message: SyncWorkerOutput) => IO; + +const sync = ({ + messages, + clock, + owner: { mnemonic, id: ownerId }, + postSyncWorkerOutput, + previousDiff, +}: SyncWorkerInput & { + readonly postSyncWorkerOutput: PostSyncWorkerOutput; +}): Task => + pipe( + log("sync:request")({ + messages, + userId: ownerId, + nodeId: clock.timestamp.node, + merkleTree: clock.merkleTree, + }), + taskEither.fromIO, + taskEither.chain(() => + encryptMessages({ + messages: pipe( + messages, + option.getOrElseW(() => []) + ), + mnemonic, + }) + ), + taskEither.map(createSyncRequest({ ownerId, clock })), + taskEither.chainW(postSyncRequest), + taskEither.chainW(({ merkleTree, messages }) => + pipe( + decryptMessages({ messages, mnemonic }), + taskEither.chainFirstIOK(log("sync:response")), + taskEither.map((messages) => ({ + messages, + merkleTree: merkleTreeFromString(merkleTree as MerkleTreeString), + previousDiff, + })) + ) + ), + task.chainIOK( + either.match((e) => { + // Ignore FetchError, because it's not an error we should handle + // elsewhere. It happens when it's impossible to make a request. + // Theoretically, we could use `navigator.onLine`, but it's not + // reliable. onLine can be false negative in Chrome and false + // positive everywhere, so we would have to ignore FetchError anyway. + // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine + // https://bugs.chromium.org/p/chromium/issues/detail?id=678075, + if (e.type === "FetchError") return constVoid; + return postSyncWorkerOutput(either.left(e)); + }, flow(either.right, postSyncWorkerOutput)) + ) + ); + +addEventListener( + "message", + ({ data: { config, syncPort } }: MessageEvent) => { + setConfig(config); + + const postSyncWorkerOutput: PostSyncWorkerOutput = (message) => () => + syncPort.postMessage(message); + + // eslint-disable-next-line functional/immutable-data + syncPort.onmessage = ({ data }: MessageEvent): void => + requestSync(sync({ ...data, postSyncWorkerOutput })); + }, + { once: true } +); diff --git a/packages/evolu/src/syncLock.ts b/packages/evolu/src/syncLock.ts new file mode 100644 index 00000000..4cbaf068 --- /dev/null +++ b/packages/evolu/src/syncLock.ts @@ -0,0 +1,29 @@ +import { array, option, taskEither } from "fp-ts"; +import { constFalse, flow, pipe } from "fp-ts/lib/function.js"; +import { Predicate } from "fp-ts/Predicate"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { Task } from "fp-ts/Task"; +import { errorToUnknownError, LockManagerEnv, UnknownError } from "./types.js"; + +const syncLockName = "evolu_sync"; + +export const requestSync = (sync: Task): void => { + navigator.locks.request(syncLockName, sync); +}; + +// eslint-disable-next-line functional/prefer-readonly-type +const hasLock: Predicate = flow( + option.fromNullable, + option.map(array.some((a) => a.name === syncLockName)), + option.getOrElse(constFalse) +); + +export const syncIsPendingOrHeld: ReaderTaskEither< + LockManagerEnv, + UnknownError, + boolean +> = ({ locks }) => + pipe( + taskEither.tryCatch(() => locks.query(), errorToUnknownError), + taskEither.map(({ pending, held }) => hasLock(pending) || hasLock(held)) + ); diff --git a/packages/evolu/src/timestamp.ts b/packages/evolu/src/timestamp.ts new file mode 100644 index 00000000..4d2577ae --- /dev/null +++ b/packages/evolu/src/timestamp.ts @@ -0,0 +1,165 @@ +import { either } from "fp-ts"; +import { Either } from "fp-ts/Either"; +import { increment, pipe } from "fp-ts/lib/function.js"; +import { IO } from "fp-ts/IO"; +import { ReaderEither } from "fp-ts/ReaderEither"; +import murmurhash from "murmurhash"; +import { config } from "./config.js"; +import { + Counter, + createNodeId, + MAX_COUNTER, + Millis, + NodeId, + TimeEnv, + Timestamp, + TimestampCounterOverflowError, + TimestampDriftError, + TimestampDuplicateNodeError, + TimestampHash, + TimestampString, +} from "./types.js"; + +// https://muratbuffalo.blogspot.com/2014/07/hybrid-logical-clocks.html +// https://jaredforsyth.com/posts/hybrid-logical-clocks/ +// https://github.com/jlongster/crdt-example-app/blob/master/shared/timestamp.js + +export const createInitialTimestamp: IO = () => ({ + millis: 0 as Millis, + counter: 0 as Counter, + node: createNodeId(), +}); + +const syncNodeId = NodeId.parse("0000000000000000"); + +export const createSyncTimestamp = ( + millis: Millis = 0 as Millis +): Timestamp => ({ + millis, + counter: 0 as Counter, + node: syncNodeId, +}); + +export const timestampToString = (t: Timestamp): TimestampString => + [ + new Date(t.millis).toISOString(), + t.counter.toString(16).toUpperCase().padStart(4, "0"), + t.node, + ].join("-") as TimestampString; + +export const timestampFromString = (s: TimestampString): Timestamp => + pipe(s.split("-"), (a) => ({ + millis: Date.parse(a.slice(0, 3).join("-")).valueOf() as Millis, + counter: parseInt(a[3], 16) as Counter, + node: a[4] as NodeId, + })); + +// timestampFromUnsafeString +// export const parseTimestamp = ( +// s: string +// ): Either => +// pipe( +// s.split("-"), +// option.fromPredicate((a) => a.length === 5), +// option.chain((a) => +// apply.sequenceS(option.Applicative)({ +// millis: option.fromEither( +// pipe( +// Millis.safeParse(Date.parse(a.slice(0, 3).join("-")).valueOf()), +// safeParseToEither +// ) +// ), +// counter: option.fromEither( +// pipe(Counter.safeParse(parseInt(a[3], 16)), safeParseToEither) +// ), +// node: option.fromEither( +// pipe(NodeId.safeParse(a[4]), safeParseToEither) +// ), +// }) +// ), +// either.fromOption( +// (): TimestampParseError => ({ +// type: "TimestampParseError", +// }) +// ) +// ); + +export const timestampToHash = (t: Timestamp): TimestampHash => + pipe(timestampToString(t), murmurhash) as TimestampHash; + +const incrementCounter = ( + counter: Counter +): Either => + counter < MAX_COUNTER + ? either.right(increment(counter) as Counter) + : either.left({ type: "TimestampCounterOverflowError" }); + +export const sendTimestamp = + ( + timestamp: Timestamp + ): ReaderEither< + TimeEnv, + TimestampDriftError | TimestampCounterOverflowError, + Timestamp + > => + ({ now }) => + pipe( + Math.max(timestamp.millis, now) as Millis, + either.fromPredicate( + (next) => next - now <= config.maxDrift, + (next): TimestampDriftError => ({ + type: "TimestampDriftError", + next, + now, + }) + ), + either.bindTo("millis"), + either.bindW("counter", ({ millis }) => + millis === timestamp.millis + ? incrementCounter(timestamp.counter) + : either.right(0 as Counter) + ), + either.map((a) => ({ ...a, node: timestamp.node })) + ); + +export const receiveTimestamp = + ( + local: Timestamp, + remote: Timestamp + ): ReaderEither< + TimeEnv, + | TimestampDriftError + | TimestampCounterOverflowError + | TimestampDuplicateNodeError, + Timestamp + > => + ({ now }) => + pipe( + Math.max(local.millis, remote.millis, now) as Millis, + either.fromPredicate( + (next) => next - now <= config.maxDrift, + (next): TimestampDriftError => ({ + type: "TimestampDriftError", + next, + now, + }) + ), + either.filterOrElseW( + () => local.node !== remote.node, + (): TimestampDuplicateNodeError => ({ + type: "TimestampDuplicateNodeError", + node: local.node, + }) + ), + either.bindTo("millis"), + either.bindW("counter", ({ millis }) => + millis === local.millis && millis === remote.millis + ? incrementCounter(Math.max(local.counter, remote.counter) as Counter) + : millis === local.millis + ? incrementCounter(local.counter) + : millis === remote.millis + ? incrementCounter(remote.counter) + : either.right(0 as Counter) + ), + either.map((a) => ({ ...a, node: local.node })) + ); diff --git a/packages/evolu/src/types.ts b/packages/evolu/src/types.ts new file mode 100644 index 00000000..8ffcbf3e --- /dev/null +++ b/packages/evolu/src/types.ts @@ -0,0 +1,368 @@ +import { eq } from "fp-ts"; +import { IO } from "fp-ts/IO"; +import { IORef } from "fp-ts/IORef"; +import { ReadonlyRecord } from "fp-ts/ReadonlyRecord"; +import { TaskEither } from "fp-ts/TaskEither"; +import type { JSONPatchDocument } from "immutable-json-patch"; +import type { Kysely, SelectQueryBuilder } from "kysely"; +import { customAlphabet } from "nanoid"; +import { BRAND, z } from "zod"; +import { + ID, + Mnemonic, + OwnerId, + SqliteBoolean, + SqliteDateTime, +} from "./model.js"; + +export type LogTarget = + | "clock:read" + | "clock:update" + | "sync:request" + | "sync:response" + | "dev"; + +/* eslint-disable functional/prefer-readonly-type */ +export type Config = { + syncUrl: string; + log: boolean | LogTarget | LogTarget[]; + /** Maximum physical clock drift allowed, in ms. */ + maxDrift: number; +}; +/* eslint-enable functional/prefer-readonly-type */ + +export type Unsubscribe = IO; + +// CRDT + +const nodeIdRegex = /^[0-9a-f]{16}$/i; +export const NodeId = z + .string() + .refine((s) => nodeIdRegex.test(s)) + .brand<"NodeId">(); +export type NodeId = z.infer; +const nodeIdNanoId = customAlphabet("0123456789abcdef", 16); +export const createNodeId = (): NodeId => nodeIdNanoId() as NodeId; + +export type Millis = number & BRAND<"Millis">; +export const Millis = z.number().refine((n: number): n is Millis => n >= 0); + +export const MAX_COUNTER = 65535; +export type Counter = number & BRAND<"Counter">; +export const Counter = z + .number() + .refine((n: number): n is Counter => n >= 0 && n <= MAX_COUNTER); + +export interface Timestamp { + readonly millis: Millis; + readonly counter: Counter; + readonly node: NodeId; +} + +export type TimestampString = string & BRAND<"TimestampString">; + +/** A murmurhash of stringified Timestamp. */ +export type TimestampHash = number & BRAND<"TimestampHash">; + +export interface MerkleTree { + readonly hash?: TimestampHash; + readonly "0"?: MerkleTree; + readonly "1"?: MerkleTree; + readonly "2"?: MerkleTree; +} + +export type MerkleTreeString = string & BRAND<"MerkleTreeString">; + +export const merkleTreeToString = (m: MerkleTree): MerkleTreeString => + JSON.stringify(m) as MerkleTreeString; + +export const merkleTreeFromString = (m: MerkleTreeString): MerkleTree => + JSON.parse(m) as MerkleTree; + +// A subset of SQLiteCompatibleType. +// TODO: Add Int8Array, https://github.com/evolu-io/evolu/issues/13 +export type CrdtValue = null | string | number; + +export interface NewCrdtMessage { + readonly table: string; + readonly row: ID; + readonly column: string; + readonly value: CrdtValue; +} + +export interface CrdtMessage extends NewCrdtMessage { + readonly timestamp: TimestampString; +} + +export interface CrdtClock { + readonly timestamp: Timestamp; + readonly merkleTree: MerkleTree; +} + +// SQL + +/** Like Kysely CompiledQuery but without a `query` prop. */ +export interface SqlQuery { + readonly sql: string; + readonly parameters: readonly unknown[]; +} + +export type SqlQueryString = string & BRAND<"SqlQueryString">; +export const eqSqlQueryString: eq.Eq = eq.eqStrict; + +export const sqlQueryToString = ({ + sql, + parameters, +}: SqlQuery): SqlQueryString => + JSON.stringify({ sql, parameters }) as SqlQueryString; + +export const sqlQueryFromString = (s: SqlQueryString): SqlQuery => + JSON.parse(s) as SqlQuery; + +// This is a workaround for: +// https://github.com/rhashimoto/wa-sqlite/blob/29a38da3dc001f8e6844837f6f3dffcdda2cdb10/src/types/index.d.ts#L18 +// It should be possible to import it, but it's somehow ambient. +export type SQLiteCompatibleType = + | number + | string + | Int8Array + // eslint-disable-next-line functional/prefer-readonly-type + | Array + | null; + +export type SQLiteRow = readonly SQLiteCompatibleType[]; + +/** SQLiteRowRecord is SQLiteRow with columns. */ +export type SQLiteRowRecord = ReadonlyRecord; + +export interface TableDefinition { + readonly name: string; + readonly columns: readonly string[]; +} + +// DB + +/* A database owner. */ +export interface Owner { + readonly id: OwnerId; + readonly mnemonic: Mnemonic; +} + +export interface PreparedStatement { + readonly exec: ( + bindings: readonly CrdtValue[] + ) => TaskEither; + readonly release: () => TaskEither; +} + +export interface Database { + readonly exec: ( + sql: string + ) => TaskEither; + + readonly changes: () => number; + + readonly execSqlQuery: ( + sqlQuery: SqlQuery + ) => TaskEither; + + readonly prepare: ( + sql: string + ) => TaskEither; +} + +export type QueriesRowsCache = ReadonlyRecord< + SqlQueryString, + readonly SQLiteRowRecord[] +>; + +export interface QueryPatches { + readonly query: SqlQueryString; + readonly patches: JSONPatchDocument; +} + +type DbTableSchema = { + readonly id: z.ZodBranded, string>; +} & ReadonlyRecord; + +export type DbSchema = Record; + +export const CommonColumns = z.object({ + createdAt: SqliteDateTime, + createdBy: OwnerId, + updatedAt: SqliteDateTime, + isDeleted: SqliteBoolean, +}); +export type CommonColumns = z.infer; +export const commonColumns = Object.keys(CommonColumns.shape); + +type NullableExceptOfId = { + readonly [P in keyof T]: P extends "id" ? T[P] : T[P] | null; +}; + +export type DbSchemaToType = { + readonly [TableName in keyof S]: NullableExceptOfId< + { + readonly [ColumnName in keyof S[TableName]]: z.TypeOf< + S[TableName][ColumnName] + >; + } & A + >; +}; + +type KyselyOnlyForReading = Omit< + Kysely, + | "connection" + | "deleteFrom" + | "destroy" + | "dynamic" + | "fn" + | "getExecutor" + | "insertInto" + | "introspection" + | "isTransaction" + | "migration" + | "raw" + | "schema" + | "transaction" + | "updateTable" + | "with" + | "withoutPlugins" + | "withPlugin" + | "withRecursive" + | "withSchema" + | "withTables" +>; + +// Hooks. + +export type Query = ( + db: KyselyOnlyForReading> +) => SelectQueryBuilder; + +type AllowCasting = { + readonly [P in keyof T]: T[P] extends SqliteBoolean | null + ? T[P] | boolean + : T[P] extends SqliteDateTime | null + ? T[P] | Date + : T[P]; +}; + +export type Mutate = < + V extends DbSchemaToType>, + T extends keyof V +>( + table: T, + values: Partial> +) => { + readonly id: V[T]["id"]; +}; + +// Environments. +// https://andywhite.xyz/posts/2021-01-28-rte-react/ + +export interface DbEnv { + readonly db: Database; +} + +export interface DbTransactionEnv { + readonly dbTransaction: ( + te: TaskEither + ) => TaskEither; +} + +export interface OwnerEnv { + readonly owner: Owner; +} + +export interface QueriesRowsCacheEnv { + readonly queriesRowsCache: IORef; +} + +export interface TimeEnv { + readonly now: Millis; +} + +export const createTimeEnv: IO = () => ({ + now: Date.now() as Millis, +}); + +export interface LockManagerEnv { + readonly locks: LockManager; +} + +// Errors. + +export interface TimestampDuplicateNodeError { + readonly type: "TimestampDuplicateNodeError"; + readonly node: NodeId; +} + +export interface TimestampDriftError { + readonly type: "TimestampDriftError"; + readonly next: Millis; + readonly now: Millis; +} + +export interface TimestampCounterOverflowError { + readonly type: "TimestampCounterOverflowError"; +} + +export interface TimestampParseError { + readonly type: "TimestampParseError"; +} + +export interface StringMaxLengthError { + readonly type: "StringMaxLengthError"; +} + +/** + * We can't use the whole error because of WebWorker postMessage + * DataCloneError in Safari and Firefox. + */ +interface TransferableError { + readonly message: string; + readonly stack: string | undefined; +} + +export const errorToTransferableError = (error: unknown): TransferableError => { + const isError = error instanceof Error; + return { + message: isError ? error.message : String(error), + stack: isError ? error.stack : undefined, + }; +}; + +/** + * A kitchen sink error for errors from OpenPGP, wa-sqlite, etc. that + * we don't handle specifically. + */ +export interface UnknownError { + readonly type: "UnknownError"; + readonly error: TransferableError; +} + +export const errorToUnknownError = (error: unknown): UnknownError => ({ + type: "UnknownError", + error: errorToTransferableError(error), +}); + +/** + * The client was unable to get in sync with the server. + * This error can happen only when MerkleTree has a bug. + */ +export interface SyncError { + readonly type: "SyncError"; +} + +export interface EvoluError { + readonly type: "EvoluError"; + readonly error: + | TimestampDuplicateNodeError + | TimestampDriftError + | TimestampCounterOverflowError + | TimestampParseError + | StringMaxLengthError + | UnknownError + | SyncError; +} diff --git a/packages/evolu/src/typesBrowser.ts b/packages/evolu/src/typesBrowser.ts new file mode 100644 index 00000000..37fb6152 --- /dev/null +++ b/packages/evolu/src/typesBrowser.ts @@ -0,0 +1,127 @@ +import { Either } from "fp-ts/Either"; +import { IO } from "fp-ts/IO"; +import { TaskEither } from "fp-ts/TaskEither"; +import { Option } from "fp-ts/Option"; +import { ReadonlyNonEmptyArray } from "fp-ts/ReadonlyNonEmptyArray"; +import { Mnemonic } from "./model.js"; +import { + Config, + CrdtClock, + CrdtMessage, + Database, + EvoluError, + MerkleTree, + Millis, + NewCrdtMessage, + Owner, + QueryPatches, + SqlQueryString, + TableDefinition, + UnknownError, +} from "./types.js"; + +// Workers. + +export type DbWorkerInputInit = { + readonly type: "init"; + readonly config: Config; + readonly syncPort: MessagePort; +}; + +export type DbWorkerInput = + | { + readonly type: "updateDbSchema"; + readonly tableDefinitions: readonly TableDefinition[]; + } + | { + readonly type: "send"; + readonly messages: ReadonlyNonEmptyArray; + readonly queries: readonly SqlQueryString[]; + } + | { + readonly type: "query"; + readonly queries: ReadonlyNonEmptyArray; + } + | { + readonly type: "receive"; + readonly messages: readonly CrdtMessage[]; + readonly merkleTree: MerkleTree; + readonly previousDiff: Option; + } + | { + readonly type: "sync"; + readonly queries: Option>; + } + | { + readonly type: "resetOwner"; + } + | { + readonly type: "restoreOwner"; + readonly mnemonic: Mnemonic; + }; + +export type DbWorkerOutput = + | { + readonly type: "onError"; + readonly error: EvoluError["error"]; + } + | { + readonly type: "onInit"; + readonly owner: Owner; + } + | { + readonly type: "onQuery"; + readonly queriesPatches: ReadonlyNonEmptyArray; + } + | { + readonly type: "onReceive"; + } + | { readonly type: "reloadAllTabs" }; + +export type SyncWorkerInputInit = { + readonly type: "init"; + readonly config: Config; + readonly syncPort: MessagePort; +}; + +export type SyncWorkerInput = { + readonly type: "sync"; + readonly messages: Option>; + readonly clock: CrdtClock; + readonly owner: Owner; + readonly previousDiff: Option; +}; + +export type SyncWorkerOutput = Either< + UnknownError, + { + readonly messages: readonly CrdtMessage[]; + readonly merkleTree: MerkleTree; + readonly previousDiff: Option; + } +>; + +// Environments. +// https://andywhite.xyz/posts/2021-01-28-rte-react/ + +export interface DbEnv { + readonly db: Database; +} + +export interface DbTransactionEnv { + readonly dbTransaction: ( + te: TaskEither + ) => TaskEither; +} + +export interface OwnerEnv { + readonly owner: Owner; +} + +export interface PostDbWorkerOutputEnv { + readonly postDbWorkerOutput: (message: DbWorkerOutput) => IO; +} + +export interface PostSyncWorkerInputEnv { + readonly postSyncWorkerInput: (message: SyncWorkerInput) => IO; +} diff --git a/packages/evolu/src/updateClock.ts b/packages/evolu/src/updateClock.ts new file mode 100644 index 00000000..fe598a29 --- /dev/null +++ b/packages/evolu/src/updateClock.ts @@ -0,0 +1,26 @@ +import { taskEither } from "fp-ts"; +import { constVoid, pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { log } from "./log.js"; +import { timestampToString } from "./timestamp.js"; +import { CrdtClock, DbEnv, merkleTreeToString, UnknownError } from "./types.js"; + +export const updateClock = + (clock: CrdtClock): ReaderTaskEither => + ({ db }) => + pipe( + db.execSqlQuery({ + sql: ` + UPDATE "__clock" + SET + "timestamp" = ?, + "merkleTree" = ? + `, + parameters: [ + timestampToString(clock.timestamp), + merkleTreeToString(clock.merkleTree), + ], + }), + taskEither.chainIOK(() => log("clock:update")(clock)), + taskEither.map(constVoid) + ); diff --git a/packages/evolu/src/updateDbSchema.ts b/packages/evolu/src/updateDbSchema.ts new file mode 100644 index 00000000..44600df8 --- /dev/null +++ b/packages/evolu/src/updateDbSchema.ts @@ -0,0 +1,103 @@ +import { + predicate, + readerTaskEither, + readonlyArray, + string, + taskEither, +} from "fp-ts"; +import { constVoid, flow, pipe } from "fp-ts/lib/function.js"; +import { ReaderTaskEither } from "fp-ts/ReaderTaskEither"; +import { DbEnv, TableDefinition, UnknownError } from "./types.js"; + +export const getExistingTables: ReaderTaskEither< + DbEnv, + UnknownError, + ReadonlySet +> = ({ db }) => + pipe( + db.exec(` + SELECT "name" FROM sqlite_schema WHERE type='table' + `), + taskEither.map( + flow( + readonlyArray.map((row) => row[0] + ""), + readonlyArray.filter(predicate.not(string.startsWith("__"))), + (a) => new Set(a) + ) + ) + ); + +const updateTable = + ({ + name, + columns, + }: TableDefinition): ReaderTaskEither => + ({ db }) => + pipe( + db.exec(` + PRAGMA table_info (${name}) + `), + taskEither.map( + flow( + readonlyArray.map((a) => a[1] as string), + (existingColumns) => + readonlyArray.difference(string.Eq)(existingColumns)(columns) + ) + ), + taskEither.chain((newColumns) => + !newColumns.length + ? taskEither.right(readonlyArray.empty) + : pipe( + newColumns.map( + (column) => `ALTER TABLE "${name}" ADD COLUMN "${column}" BLOB;` + ), + (a) => a.join(""), + db.exec + ) + ), + taskEither.map(constVoid) + ); + +const createTable = + ({ + name, + columns, + }: TableDefinition): ReaderTaskEither => + ({ db }) => + pipe( + db.exec(` + CREATE TABLE ${name} ( + "id" TEXT PRIMARY KEY, + ${columns + // Some people hate SQLite general dynamic type system. + // Some people love new SQLite strict tables. + // For Evolu, the BLOB behavior is the best. + // "A column with affinity BLOB does not prefer one storage class over another + // and no attempt is made to coerce data from one storage class into another." + // https://www.sqlite.org/datatype3.html + .map((name) => `"${name}" BLOB`) + .join(", ")} + ); + `), + taskEither.map(constVoid) + ); + +export const updateDbSchema = ({ + tableDefinitions, +}: { + readonly tableDefinitions: readonly TableDefinition[]; +}): ReaderTaskEither => + pipe( + getExistingTables, + readerTaskEither.chain((existingTables) => + pipe( + tableDefinitions, + readerTaskEither.traverseSeqArray((tableDefinition) => + existingTables.has(tableDefinition.name) + ? updateTable(tableDefinition) + : createTable(tableDefinition) + ) + ) + ), + readerTaskEither.map(constVoid) + ); diff --git a/packages/evolu/src/useOwner.ts b/packages/evolu/src/useOwner.ts new file mode 100644 index 00000000..cb08ce55 --- /dev/null +++ b/packages/evolu/src/useOwner.ts @@ -0,0 +1,13 @@ +import { useEffect, useState } from "react"; +import { getOwner } from "./db.js"; +import { Owner } from "./types.js"; + +export const useOwner = (): Owner | null => { + const [owner, setOwner] = useState(null); + + useEffect(() => { + getOwner().then(setOwner); + }, []); + + return owner; +}; diff --git a/packages/evolu/src/validateMnemonic.ts b/packages/evolu/src/validateMnemonic.ts new file mode 100644 index 00000000..92db7062 --- /dev/null +++ b/packages/evolu/src/validateMnemonic.ts @@ -0,0 +1,2058 @@ +import { Predicate } from "fp-ts/Predicate"; + +export const defaultMnemonicWordList = [ + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo", +]; + +export const validateMnemonic: Predicate = (s) => { + const words = s.split(" "); + if (words.length !== 12) return false; + return words.every((w) => defaultMnemonicWordList.includes(w)); +}; diff --git a/packages/evolu/test/__snapshots__/merkleTree.test.ts.snap b/packages/evolu/test/__snapshots__/merkleTree.test.ts.snap new file mode 100644 index 00000000..5a0efbb6 --- /dev/null +++ b/packages/evolu/test/__snapshots__/merkleTree.test.ts.snap @@ -0,0 +1,136 @@ +// Vitest Snapshot v1 + +exports[`createInitialMerkleTree 1`] = `{}`; + +exports[`diffMerkleTrees 1`] = ` +{ + "_tag": "None", +} +`; + +exports[`diffMerkleTrees 2`] = ` +{ + "_tag": "Some", + "value": 1656873720000, +} +`; + +exports[`insertIntoMerkleTree 1`] = ` +{ + "0": { + "hash": -1416139081, + }, + "hash": -1416139081, +} +`; + +exports[`insertIntoMerkleTree 2`] = ` +{ + "1": { + "2": { + "2": { + "0": { + "2": { + "2": { + "1": { + "2": { + "2": { + "2": { + "0": { + "0": { + "1": { + "1": { + "2": { + "0": { + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, +} +`; + +exports[`insertIntoMerkleTree 3`] = ` +{ + "0": { + "hash": -1416139081, + }, + "1": { + "2": { + "2": { + "0": { + "2": { + "2": { + "1": { + "2": { + "2": { + "2": { + "0": { + "0": { + "1": { + "1": { + "2": { + "0": { + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": -468843282, + }, + "hash": 1335454297, +} +`; diff --git a/packages/evolu/test/__snapshots__/timestamp.test.ts.snap b/packages/evolu/test/__snapshots__/timestamp.test.ts.snap new file mode 100644 index 00000000..5438bb24 --- /dev/null +++ b/packages/evolu/test/__snapshots__/timestamp.test.ts.snap @@ -0,0 +1,145 @@ +// Vitest Snapshot v1 + +exports[`receiveTimestamp > TimestampDuplicateNodeError 1`] = ` +{ + "_tag": "Left", + "left": { + "node": "0000000000000001", + "type": "TimestampDuplicateNodeError", + }, +} +`; + +exports[`receiveTimestamp > should fail with clock drift 1`] = ` +{ + "_tag": "Left", + "left": { + "next": 60001, + "now": 0, + "type": "TimestampDriftError", + }, +} +`; + +exports[`receiveTimestamp > should fail with clock drift 2`] = ` +{ + "_tag": "Left", + "left": { + "next": 60001, + "now": 0, + "type": "TimestampDriftError", + }, +} +`; + +exports[`receiveTimestamp > wall clock is later than both the local and remote timestamps 1`] = ` +{ + "_tag": "Right", + "right": { + "counter": 0, + "millis": 1, + "node": "0000000000000001", + }, +} +`; + +exports[`receiveTimestamp > wall clock is somehow behind > for the same timestamps millis, we take the bigger counter 1`] = ` +{ + "_tag": "Right", + "right": { + "counter": 2, + "millis": 1, + "node": "0000000000000001", + }, +} +`; + +exports[`receiveTimestamp > wall clock is somehow behind > for the same timestamps millis, we take the bigger counter 2`] = ` +{ + "_tag": "Right", + "right": { + "counter": 2, + "millis": 1, + "node": "0000000000000001", + }, +} +`; + +exports[`receiveTimestamp > wall clock is somehow behind > local millis is later than remote 1`] = ` +{ + "_tag": "Right", + "right": { + "counter": 1, + "millis": 2, + "node": "0000000000000001", + }, +} +`; + +exports[`receiveTimestamp > wall clock is somehow behind > remote millis is later than local 1`] = ` +{ + "_tag": "Right", + "right": { + "counter": 1, + "millis": 2, + "node": "0000000000000001", + }, +} +`; + +exports[`sendTimestamp > should fail with clock drift 1`] = ` +{ + "_tag": "Left", + "left": { + "next": 60001, + "now": 0, + "type": "TimestampDriftError", + }, +} +`; + +exports[`sendTimestamp > should fail with counter overflow 1`] = ` +{ + "_tag": "Left", + "left": { + "type": "TimestampCounterOverflowError", + }, +} +`; + +exports[`sendTimestamp > should send monotonically with a monotonic clock 1`] = ` +{ + "_tag": "Right", + "right": { + "counter": 0, + "millis": 1, + "node": "0000000000000000", + }, +} +`; + +exports[`sendTimestamp > should send monotonically with a regressing clock 1`] = ` +{ + "_tag": "Right", + "right": { + "counter": 1, + "millis": 1, + "node": "0000000000000000", + }, +} +`; + +exports[`sendTimestamp > should send monotonically with a stuttering clock 1`] = ` +{ + "_tag": "Right", + "right": { + "counter": 1, + "millis": 0, + "node": "0000000000000000", + }, +} +`; + +exports[`timestampToHash 1`] = `4179357717`; + +exports[`timestampToString 1`] = `"1970-01-01T00:00:00.000Z-0000-0000000000000000"`; diff --git a/packages/evolu/test/merkleTree.test.ts b/packages/evolu/test/merkleTree.test.ts new file mode 100644 index 00000000..a9064b9b --- /dev/null +++ b/packages/evolu/test/merkleTree.test.ts @@ -0,0 +1,61 @@ +import { pipe } from "fp-ts/lib/function.js"; +import { expect, test } from "vitest"; +import { + createInitialMerkleTree, + diffMerkleTrees, + insertIntoMerkleTree, +} from "../src/merkleTree.js"; +import { createNode1Timestamp } from "./testUtils.js"; + +const initialMerkleTree = createInitialMerkleTree(); + +test("createInitialMerkleTree", () => { + expect(initialMerkleTree).toMatchSnapshot(); +}); + +test("insertIntoMerkleTree", () => { + const ts1 = createNode1Timestamp(); + const ts2 = createNode1Timestamp(1656873738591); + + expect(insertIntoMerkleTree(ts1)(initialMerkleTree)).toMatchSnapshot(); + expect(insertIntoMerkleTree(ts2)(initialMerkleTree)).toMatchSnapshot(); + expect( + pipe( + initialMerkleTree, + insertIntoMerkleTree(ts1), + insertIntoMerkleTree(ts2) + ) + ).toMatchSnapshot(); + + expect( + pipe( + initialMerkleTree, + insertIntoMerkleTree(ts1), + insertIntoMerkleTree(ts2) + ) + ).toEqual( + pipe( + initialMerkleTree, + insertIntoMerkleTree(ts2), + insertIntoMerkleTree(ts1) + ) + ); +}); + +test("diffMerkleTrees", () => { + expect( + diffMerkleTrees(initialMerkleTree, initialMerkleTree) + ).toMatchSnapshot(); + + const ts = createNode1Timestamp(1656873738591); + const mt = pipe(initialMerkleTree, insertIntoMerkleTree(ts)); + + expect(diffMerkleTrees(initialMerkleTree, mt)).toMatchSnapshot(); + + expect(diffMerkleTrees(initialMerkleTree, mt)).toEqual( + diffMerkleTrees(mt, initialMerkleTree) + ); +}); + +// TODO: Add more tests. +// Check https://github.com/actualbudget/actual/blob/master/packages/loot-core/src/server/merkle.test.js diff --git a/packages/evolu/test/testUtils.ts b/packages/evolu/test/testUtils.ts new file mode 100644 index 00000000..af63539b --- /dev/null +++ b/packages/evolu/test/testUtils.ts @@ -0,0 +1,15 @@ +import { Timestamp } from "../src/types.js"; + +export const createNode1Timestamp = (millis = 0, counter = 0): Timestamp => + ({ + millis, + counter, + node: "0000000000000001", + } as Timestamp); + +export const createNode2Timestamp = (millis = 0, counter = 0): Timestamp => + ({ + millis, + counter, + node: "0000000000000002", + } as Timestamp); diff --git a/packages/evolu/test/timestamp.test.ts b/packages/evolu/test/timestamp.test.ts new file mode 100644 index 00000000..e575eea7 --- /dev/null +++ b/packages/evolu/test/timestamp.test.ts @@ -0,0 +1,152 @@ +import { either } from "fp-ts"; +import { pipe } from "fp-ts/lib/function.js"; +import { describe, expect, test } from "vitest"; +import { config } from "../src/config.js"; +import { + createInitialTimestamp, + createSyncTimestamp, + receiveTimestamp, + sendTimestamp, + timestampFromString, + timestampToHash, + timestampToString, +} from "../src/timestamp.js"; +import { + Millis, + TimeEnv, + Timestamp, + TimestampCounterOverflowError, + TimestampDriftError, +} from "../src/types.js"; +import { createNode1Timestamp, createNode2Timestamp } from "./testUtils.js"; + +test("createInitialTimestamp", () => { + const ts = createInitialTimestamp(); + expect(ts.counter).toBe(0); + expect(ts.millis).toBe(0); + expect(ts.node.length).toBe(16); +}); + +test("createSyncTimestamp", () => { + const ts = createSyncTimestamp(); + expect(ts.counter).toBe(0); + expect(ts.millis).toBe(0); + expect(ts.node).toBe("0000000000000000"); +}); + +test("timestampToString", () => { + expect(pipe(createSyncTimestamp(), timestampToString)).toMatchSnapshot(); +}); + +test("timestampFromString", () => { + const t = createSyncTimestamp(); + expect(t).toEqual(pipe(t, timestampToString, timestampFromString)); +}); + +test("timestampToHash", () => { + expect(timestampToHash(createSyncTimestamp())).toMatchSnapshot(); +}); + +const now0 = { now: 0 } as TimeEnv; +const now1 = { now: 1 } as TimeEnv; + +describe("sendTimestamp", () => { + test("should send monotonically with a monotonic clock", () => { + expect(pipe(createSyncTimestamp(), sendTimestamp)(now1)).toMatchSnapshot(); + }); + + test("should send monotonically with a stuttering clock", () => { + expect(pipe(createSyncTimestamp(), sendTimestamp)(now0)).toMatchSnapshot(); + }); + + test("should send monotonically with a regressing clock", () => { + expect( + pipe(createSyncTimestamp(1 as Millis), sendTimestamp)(now0) + ).toMatchSnapshot(); + }); + + test("should fail with counter overflow", () => { + // eslint-disable-next-line functional/no-let + let timestamp = either.right< + TimestampDriftError | TimestampCounterOverflowError, + Timestamp + >(createSyncTimestamp()); + // eslint-disable-next-line functional/no-loop-statement, functional/no-let + for (let i = 0; i < 65536; i++) { + timestamp = pipe( + timestamp, + either.chain((t) => sendTimestamp(t)(now0)) + ); + } + expect(timestamp).toMatchSnapshot(); + }); + + test("should fail with clock drift", () => { + expect( + pipe( + createSyncTimestamp((config.maxDrift + 1) as Millis), + sendTimestamp + )(now0) + ).toMatchSnapshot(); + }); +}); + +describe("receiveTimestamp", () => { + test("wall clock is later than both the local and remote timestamps", () => { + expect( + receiveTimestamp(createNode1Timestamp(), createNode2Timestamp(0, 0))(now1) + ).toMatchSnapshot(); + }); + + describe("wall clock is somehow behind", () => { + test("for the same timestamps millis, we take the bigger counter", () => { + expect( + receiveTimestamp( + createNode1Timestamp(1, 0), + createNode2Timestamp(1, 1) + )(now0) + ).toMatchSnapshot(); + + expect( + receiveTimestamp( + createNode1Timestamp(1, 1), + createNode2Timestamp(1, 0) + )(now0) + ).toMatchSnapshot(); + }); + + test("local millis is later than remote", () => { + expect( + receiveTimestamp(createNode1Timestamp(2), createNode2Timestamp(1))(now0) + ).toMatchSnapshot(); + }); + + test("remote millis is later than local", () => { + expect( + receiveTimestamp(createNode1Timestamp(1), createNode2Timestamp(2))(now0) + ).toMatchSnapshot(); + }); + }); + + test("TimestampDuplicateNodeError", () => { + expect( + receiveTimestamp(createNode1Timestamp(), createNode1Timestamp())(now1) + ).toMatchSnapshot(); + }); + + test("should fail with clock drift", () => { + expect( + receiveTimestamp( + createSyncTimestamp((config.maxDrift + 1) as Millis), + createNode2Timestamp() + )(now0) + ).toMatchSnapshot(); + + expect( + receiveTimestamp( + createNode2Timestamp(), + createSyncTimestamp((config.maxDrift + 1) as Millis) + )(now0) + ).toMatchSnapshot(); + }); +}); diff --git a/packages/evolu/tsconfig.json b/packages/evolu/tsconfig.json new file mode 100644 index 00000000..bf9c9821 --- /dev/null +++ b/packages/evolu/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@evolu/tsconfig/universal-esm.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src", "test"], + "exclude": ["dist", "node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..74c7f2de --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4498 @@ +lockfileVersion: 5.4 + +importers: + + .: + specifiers: + '@changesets/cli': ^2.24.4 + eslint: ^8.24.0 + eslint-config-evolu: workspace:0.0.0 + prettier: ^2.7.1 + turbo: ^1.5.3 + devDependencies: + '@changesets/cli': 2.24.4 + eslint: 8.24.0 + eslint-config-evolu: link:packages/eslint-config-evolu + prettier: 2.7.1 + turbo: 1.5.3 + + apps/server: + specifiers: + '@evolu/tsconfig': workspace:0.0.0 + '@types/better-sqlite3': ^7.6.0 + '@types/body-parser': ^1.19.2 + '@types/cors': ^2.8.12 + '@types/express': ^4.17.14 + '@types/node': ^16.11.60 + better-sqlite3: ^7.6.2 + body-parser: ^1.20.0 + cors: ^2.8.5 + evolu: workspace:0.0.0 + express: ^4.18.1 + fp-ts: ^2.12.3 + ts-node: ^10.9.1 + typescript: ^4.8.3 + dependencies: + better-sqlite3: 7.6.2 + body-parser: 1.20.0 + cors: 2.8.5 + evolu: link:../../packages/evolu + express: 4.18.1 + fp-ts: 2.12.3 + devDependencies: + '@evolu/tsconfig': link:../../packages/evolu-tsconfig + '@types/better-sqlite3': 7.6.0 + '@types/body-parser': 1.19.2 + '@types/cors': 2.8.12 + '@types/express': 4.17.14 + '@types/node': 16.11.60 + ts-node: 10.9.1_y2ippukchihtaxubiro7c5yd6u + typescript: 4.8.3 + + apps/web: + specifiers: + '@evolu/tsconfig': workspace:0.0.0 + '@next/bundle-analyzer': ^12.3.1 + '@types/node': ^16.11.60 + '@types/react': ^18.0.21 + '@types/react-dom': ^18.0.6 + cross-env: ^7.0.3 + eslint: ^8.24.0 + eslint-config-evolu: workspace:0.0.0 + evolu: workspace:0.0.0 + next: ^12.3.1 + next-transpile-modules: ^9.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + typescript: ^4.8.3 + dependencies: + '@next/bundle-analyzer': 12.3.1 + cross-env: 7.0.3 + evolu: link:../../packages/evolu + next: 12.3.1_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + devDependencies: + '@evolu/tsconfig': link:../../packages/evolu-tsconfig + '@types/node': 16.11.60 + '@types/react': 18.0.21 + '@types/react-dom': 18.0.6 + eslint: 8.24.0 + eslint-config-evolu: link:../../packages/eslint-config-evolu + next-transpile-modules: 9.0.0 + typescript: 4.8.3 + + packages/eslint-config-evolu: + specifiers: + '@typescript-eslint/eslint-plugin': ^5.38.0 + '@typescript-eslint/parser': ^5.38.0 + eslint: ^8.24.0 + eslint-config-next: ^12.3.1 + eslint-config-prettier: ^8.5.0 + eslint-config-turbo: ^0.0.4 + eslint-plugin-functional: ^4.4.0 + eslint-plugin-node: ^11.1.0 + typescript: ^4.8.3 + dependencies: + '@typescript-eslint/eslint-plugin': 5.38.0_4gkcvl6qsi23tqqawfqgcwtp54 + '@typescript-eslint/parser': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + eslint-config-next: 12.3.1_7ilbxdl5iguzcjriqqcg2m5cku + eslint-config-prettier: 8.5.0_eslint@8.24.0 + eslint-config-turbo: 0.0.4_eslint@8.24.0 + eslint-plugin-functional: 4.4.0_7ilbxdl5iguzcjriqqcg2m5cku + eslint-plugin-node: 11.1.0_eslint@8.24.0 + devDependencies: + eslint: 8.24.0 + typescript: 4.8.3 + + packages/evolu: + specifiers: + '@evolu/tsconfig': workspace:0.0.0 + '@protobuf-ts/plugin': ^2.8.1 + '@protobuf-ts/runtime': ^2.8.1 + '@types/node': ^16.11.60 + '@types/react': ^18.0.21 + '@types/react-dom': ^18.0.6 + '@types/web-locks-api': ^0.0.2 + cross-env: ^7.0.3 + esbuild: ^0.15.9 + esbuild-node-externals: ^1.5.0 + eslint: ^8.24.0 + eslint-config-evolu: workspace:0.0.0 + fp-ts: ^2.12.3 + immutable-json-patch: ^5.0.0 + kysely: ^0.21.6 + murmurhash: ^2.0.1 + nanoid: 4.0.0 + openpgp: ^5.5.0 + react: ^18.2.0 + rfc6902: ^5.0.1 + sha256-uint8array: ^0.10.3 + tslib: ^2.4.0 + typescript: ^4.8.3 + vitest: ^0.23.4 + wa-sqlite: github:rhashimoto/wa-sqlite#5fdc80ff1c153aeb0dab65a5e23ca22938c827a3 + zod: ^3.19.1 + dependencies: + '@protobuf-ts/runtime': 2.8.1 + immutable-json-patch: 5.0.0 + kysely: 0.21.6 + murmurhash: 2.0.1 + nanoid: 4.0.0 + rfc6902: 5.0.1 + sha256-uint8array: 0.10.3 + wa-sqlite: github.com/rhashimoto/wa-sqlite/5fdc80ff1c153aeb0dab65a5e23ca22938c827a3 + devDependencies: + '@evolu/tsconfig': link:../evolu-tsconfig + '@protobuf-ts/plugin': 2.8.1 + '@types/node': 16.11.60 + '@types/react': 18.0.21 + '@types/react-dom': 18.0.6 + '@types/web-locks-api': 0.0.2 + cross-env: 7.0.3 + esbuild: 0.15.9 + esbuild-node-externals: 1.5.0_esbuild@0.15.9 + eslint: 8.24.0 + eslint-config-evolu: link:../eslint-config-evolu + fp-ts: 2.12.3 + openpgp: 5.5.0 + react: 18.2.0 + tslib: 2.4.0 + typescript: 4.8.3 + vitest: 0.23.4 + zod: 3.19.1 + + packages/evolu-tsconfig: + specifiers: {} + +packages: + + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + dev: true + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + + /@babel/runtime-corejs3/7.19.1: + resolution: {integrity: sha512-j2vJGnkopRzH+ykJ8h68wrHnEUmtK//E723jjixiAl/PPf6FhqY/vYRcMVlNydRKQjQsTsYEjpx+DZMIvnGk/g==} + engines: {node: '>=6.9.0'} + dependencies: + core-js-pure: 3.25.2 + regenerator-runtime: 0.13.9 + dev: false + + /@babel/runtime/7.19.0: + resolution: {integrity: sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.9 + + /@changesets/apply-release-plan/6.1.0: + resolution: {integrity: sha512-fMNBUAEc013qaA4KUVjdwgYMmKrf5Mlgf6o+f97MJVNzVnikwpWY47Lc3YR1jhC874Fonn5MkjkWK9DAZsdQ5g==} + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/config': 2.1.1 + '@changesets/get-version-range-type': 0.3.2 + '@changesets/git': 1.4.1 + '@changesets/types': 5.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.7.1 + resolve-from: 5.0.0 + semver: 5.7.1 + dev: true + + /@changesets/assemble-release-plan/5.2.1: + resolution: {integrity: sha512-d6ckasOWlKF9Mzs82jhl6TKSCgVvfLoUK1ERySrTg2TQJdrVUteZue6uEIYUTA7SgMu67UOSwol6R9yj1nTdjw==} + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.3 + '@changesets/types': 5.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 5.7.1 + dev: true + + /@changesets/changelog-git/0.1.12: + resolution: {integrity: sha512-Xv2CPjTBmwjl8l4ZyQ3xrsXZMq8WafPUpEonDpTmcb24XY8keVzt7ZSCJuDz035EiqrjmDKDhODoQ6XiHudlig==} + dependencies: + '@changesets/types': 5.1.0 + dev: true + + /@changesets/cli/2.24.4: + resolution: {integrity: sha512-87JSwMv38zS3QW3062jXZYLsCNRtA08wa7vt3VnMmkGLfUMn2TTSfD+eSGVnKPJ/ycDCvAcCDnrv/B+gSX5KVA==} + hasBin: true + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/apply-release-plan': 6.1.0 + '@changesets/assemble-release-plan': 5.2.1 + '@changesets/changelog-git': 0.1.12 + '@changesets/config': 2.1.1 + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.3 + '@changesets/get-release-plan': 3.0.14 + '@changesets/git': 1.4.1 + '@changesets/logger': 0.0.5 + '@changesets/pre': 1.0.12 + '@changesets/read': 0.5.7 + '@changesets/types': 5.1.0 + '@changesets/write': 0.2.0 + '@manypkg/get-packages': 1.1.3 + '@types/is-ci': 3.0.0 + '@types/semver': 6.2.3 + ansi-colors: 4.1.3 + chalk: 2.4.2 + enquirer: 2.3.6 + external-editor: 3.1.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + is-ci: 3.0.1 + meow: 6.1.1 + outdent: 0.5.0 + p-limit: 2.3.0 + preferred-pm: 3.0.3 + resolve-from: 5.0.0 + semver: 5.7.1 + spawndamnit: 2.0.0 + term-size: 2.2.1 + tty-table: 4.1.6 + dev: true + + /@changesets/config/2.1.1: + resolution: {integrity: sha512-nSRINMqHpdtBpNVT9Eh9HtmLhOwOTAeSbaqKM5pRmGfsvyaROTBXV84ujF9UsWNlV71YxFbxTbeZnwXSGQlyTw==} + dependencies: + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.3 + '@changesets/logger': 0.0.5 + '@changesets/types': 5.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.5 + dev: true + + /@changesets/errors/0.1.4: + resolution: {integrity: sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==} + dependencies: + extendable-error: 0.1.7 + dev: true + + /@changesets/get-dependents-graph/1.3.3: + resolution: {integrity: sha512-h4fHEIt6X+zbxdcznt1e8QD7xgsXRAXd2qzLlyxoRDFSa6SxJrDAUyh7ZUNdhjBU4Byvp4+6acVWVgzmTy4UNQ==} + dependencies: + '@changesets/types': 5.1.0 + '@manypkg/get-packages': 1.1.3 + chalk: 2.4.2 + fs-extra: 7.0.1 + semver: 5.7.1 + dev: true + + /@changesets/get-release-plan/3.0.14: + resolution: {integrity: sha512-xzSfeyIOvUnbqMuQXVKTYUizreWQfICwoQpvEHoePVbERLocc1tPo5lzR7dmVCFcaA/DcnbP6mxyioeq+JuzSg==} + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/assemble-release-plan': 5.2.1 + '@changesets/config': 2.1.1 + '@changesets/pre': 1.0.12 + '@changesets/read': 0.5.7 + '@changesets/types': 5.1.0 + '@manypkg/get-packages': 1.1.3 + dev: true + + /@changesets/get-version-range-type/0.3.2: + resolution: {integrity: sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==} + dev: true + + /@changesets/git/1.4.1: + resolution: {integrity: sha512-GWwRXEqBsQ3nEYcyvY/u2xUK86EKAevSoKV/IhELoZ13caZ1A1TSak/71vyKILtzuLnFPk5mepP5HjBxr7lZ9Q==} + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/errors': 0.1.4 + '@changesets/types': 5.1.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + spawndamnit: 2.0.0 + dev: true + + /@changesets/logger/0.0.5: + resolution: {integrity: sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==} + dependencies: + chalk: 2.4.2 + dev: true + + /@changesets/parse/0.3.14: + resolution: {integrity: sha512-SWnNVyC9vz61ueTbuxvA6b4HXcSx2iaWr2VEa37lPg1Vw+cEyQp7lOB219P7uow1xFfdtIEEsxbzXnqLAAaY8w==} + dependencies: + '@changesets/types': 5.1.0 + js-yaml: 3.14.1 + dev: true + + /@changesets/pre/1.0.12: + resolution: {integrity: sha512-RFzWYBZx56MtgMesXjxx7ymyI829/rcIw/41hvz3VJPnY8mDscN7RJyYu7Xm7vts2Fcd+SRcO0T/Ws3I1/6J7g==} + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/errors': 0.1.4 + '@changesets/types': 5.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + dev: true + + /@changesets/read/0.5.7: + resolution: {integrity: sha512-Iteg0ccTPpkJ+qFzY97k7qqdVE5Kz30TqPo9GibpBk2g8tcLFUqf+Qd0iXPLcyhUZpPL1U6Hia1gINHNKIKx4g==} + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/git': 1.4.1 + '@changesets/logger': 0.0.5 + '@changesets/parse': 0.3.14 + '@changesets/types': 5.1.0 + chalk: 2.4.2 + fs-extra: 7.0.1 + p-filter: 2.1.0 + dev: true + + /@changesets/types/4.1.0: + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + dev: true + + /@changesets/types/5.1.0: + resolution: {integrity: sha512-uUByGATZCdaPkaO9JkBsgGDjEvHyY2Sb0e/J23+cwxBi5h0fxpLF/HObggO/Fw8T2nxK6zDfJbPsdQt5RwYFJA==} + dev: true + + /@changesets/write/0.2.0: + resolution: {integrity: sha512-iKHqGYXZvneRzRfvEBpPqKfpGELOEOEP63MKdM/SdSRon40rsUijkTmsGCHT1ueLi3iJPZPmYuZJvjjKrMzumA==} + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/types': 5.1.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.7.1 + dev: true + + /@cspotcode/source-map-support/0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@esbuild/android-arm/0.15.9: + resolution: {integrity: sha512-VZPy/ETF3fBG5PiinIkA0W/tlsvlEgJccyN2DzWZEl0DlVKRbu91PvY2D6Lxgluj4w9QtYHjOWjAT44C+oQ+EQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64/0.15.9: + resolution: {integrity: sha512-O+NfmkfRrb3uSsTa4jE3WApidSe3N5++fyOVGP1SmMZi4A3BZELkhUUvj5hwmMuNdlpzAZ8iAPz2vmcR7DCFQA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@eslint/eslintrc/1.3.2: + resolution: {integrity: sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.0 + globals: 13.17.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + /@humanwhocodes/config-array/0.10.5: + resolution: {integrity: sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + /@humanwhocodes/gitignore-to-minimatch/1.0.2: + resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==} + + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: true + + /@jridgewell/trace-mapping/0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /@manypkg/find-root/1.1.0: + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + dependencies: + '@babel/runtime': 7.19.0 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + dev: true + + /@manypkg/get-packages/1.1.3: + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + dependencies: + '@babel/runtime': 7.19.0 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + dev: true + + /@next/bundle-analyzer/12.3.1: + resolution: {integrity: sha512-2f/eei0YqZZBMTs4g1+HbgHyAFH5MbI/w9wLXmE8ly9SFze2D40sRH46JcC//EFVM/TIynVBh5sxn9CVO/vtxg==} + dependencies: + webpack-bundle-analyzer: 4.3.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@next/env/12.3.1: + resolution: {integrity: sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==} + dev: false + + /@next/eslint-plugin-next/12.3.1: + resolution: {integrity: sha512-sw+lTf6r6P0j+g/n9y4qdWWI2syPqZx+uc0+B/fRENqfR3KpSid6MIKqc9gNwGhJASazEQ5b3w8h4cAET213jw==} + dependencies: + glob: 7.1.7 + dev: false + + /@next/swc-android-arm-eabi/12.3.1: + resolution: {integrity: sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@next/swc-android-arm64/12.3.1: + resolution: {integrity: sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-arm64/12.3.1: + resolution: {integrity: sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-darwin-x64/12.3.1: + resolution: {integrity: sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@next/swc-freebsd-x64/12.3.1: + resolution: {integrity: sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm-gnueabihf/12.3.1: + resolution: {integrity: sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-gnu/12.3.1: + resolution: {integrity: sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-arm64-musl/12.3.1: + resolution: {integrity: sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-gnu/12.3.1: + resolution: {integrity: sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-linux-x64-musl/12.3.1: + resolution: {integrity: sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-arm64-msvc/12.3.1: + resolution: {integrity: sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-ia32-msvc/12.3.1: + resolution: {integrity: sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@next/swc-win32-x64-msvc/12.3.1: + resolution: {integrity: sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.13.0 + + /@polka/url/1.0.0-next.21: + resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} + dev: false + + /@protobuf-ts/plugin-framework/2.8.1: + resolution: {integrity: sha512-hfLoYIyxCI6Ro6LY1BltNBDcXWL+36SwnWR/hcF4ttPHLE3rMIpYbz4IwQsfeU2SKbBeGFhZv9rcnDJB22cXog==} + dependencies: + '@protobuf-ts/runtime': 2.8.1 + typescript: 3.9.10 + dev: true + + /@protobuf-ts/plugin/2.8.1: + resolution: {integrity: sha512-lacRdXJ9TkTbI28U0KApsnVqnxeq1aZftOdq6LNPQJaIzBrVRxrMkqnnGWGQFYe0Tr93OKeW4A2lLjTkn32CuA==} + hasBin: true + dependencies: + '@protobuf-ts/plugin-framework': 2.8.1 + '@protobuf-ts/protoc': 2.8.1 + '@protobuf-ts/runtime': 2.8.1 + '@protobuf-ts/runtime-rpc': 2.8.1 + typescript: 3.9.10 + dev: true + + /@protobuf-ts/protoc/2.8.1: + resolution: {integrity: sha512-6fehuL9bS22zCgPBBlESZjnoA4fxAUkOjMcaFMJSlVpN6CDN2O+c/Mo1pCXaNDO7FAidMPj5yhz48Kws4kOXEA==} + hasBin: true + dev: true + + /@protobuf-ts/runtime-rpc/2.8.1: + resolution: {integrity: sha512-hc+HJpoAu50by8aBS55UygcrzD8jAvRKWZMCRJ9XY3h9Gl2tciYysfzSH1SWtF6XOT/4b5CKnnmdMR3ad7uU5g==} + dependencies: + '@protobuf-ts/runtime': 2.8.1 + dev: true + + /@protobuf-ts/runtime/2.8.1: + resolution: {integrity: sha512-D9M5hSumYCovIfNllt7N6ODh4q+LrjiMWtNETvooaf+a2XheZJ7kgjFlsFghti0CFWwtA//of4JXQfw9hU+cCw==} + + /@rushstack/eslint-patch/1.2.0: + resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} + dev: false + + /@swc/helpers/0.4.11: + resolution: {integrity: sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==} + dependencies: + tslib: 2.4.0 + dev: false + + /@tsconfig/node10/1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12/1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14/1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16/1.0.3: + resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + dev: true + + /@types/better-sqlite3/7.6.0: + resolution: {integrity: sha512-rnSP9vY+fVsF3iJja5yRGBJV63PNBiezJlYrCkqUmQWFoB16cxAHwOkjsAYEu317miOfKaJpa65cbp0P4XJ/jw==} + dependencies: + '@types/node': 16.11.60 + dev: true + + /@types/body-parser/1.19.2: + resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + dependencies: + '@types/connect': 3.4.35 + '@types/node': 16.11.60 + dev: true + + /@types/chai-subset/1.3.3: + resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + dependencies: + '@types/chai': 4.3.3 + dev: true + + /@types/chai/4.3.3: + resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==} + dev: true + + /@types/connect/3.4.35: + resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + dependencies: + '@types/node': 16.11.60 + dev: true + + /@types/cors/2.8.12: + resolution: {integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==} + dev: true + + /@types/express-serve-static-core/4.17.31: + resolution: {integrity: sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==} + dependencies: + '@types/node': 16.11.60 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + dev: true + + /@types/express/4.17.14: + resolution: {integrity: sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==} + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.31 + '@types/qs': 6.9.7 + '@types/serve-static': 1.15.0 + dev: true + + /@types/is-ci/3.0.0: + resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} + dependencies: + ci-info: 3.4.0 + dev: true + + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + dev: false + + /@types/json5/0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: false + + /@types/mime/3.0.1: + resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} + dev: true + + /@types/minimist/1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + + /@types/node/12.20.55: + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + dev: true + + /@types/node/16.11.60: + resolution: {integrity: sha512-kYIYa1D1L+HDv5M5RXQeEu1o0FKA6yedZIoyugm/MBPROkLpX4L7HRxMrPVyo8bnvjpW/wDlqFNGzXNMb7AdRw==} + dev: true + + /@types/normalize-package-data/2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true + + /@types/prop-types/15.7.5: + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + dev: true + + /@types/qs/6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: true + + /@types/range-parser/1.2.4: + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + dev: true + + /@types/react-dom/18.0.6: + resolution: {integrity: sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==} + dependencies: + '@types/react': 18.0.21 + dev: true + + /@types/react/18.0.21: + resolution: {integrity: sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 + dev: true + + /@types/scheduler/0.16.2: + resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + dev: true + + /@types/semver/6.2.3: + resolution: {integrity: sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==} + dev: true + + /@types/serve-static/1.15.0: + resolution: {integrity: sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==} + dependencies: + '@types/mime': 3.0.1 + '@types/node': 16.11.60 + dev: true + + /@types/web-locks-api/0.0.2: + resolution: {integrity: sha512-piU8oaKWtCgK/h26dLmk4d5XqgEVZ2eoaFo/pX+l16EU/DsFmuLhsh9u2yxzMnkosw+l5VhoEJCObG77ku4aZQ==} + dev: true + + /@typescript-eslint/eslint-plugin/5.38.0_4gkcvl6qsi23tqqawfqgcwtp54: + resolution: {integrity: sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + '@typescript-eslint/scope-manager': 5.38.0 + '@typescript-eslint/type-utils': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + '@typescript-eslint/utils': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + debug: 4.3.4 + eslint: 8.24.0 + ignore: 5.2.0 + regexpp: 3.2.0 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.8.3 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript-eslint/parser/5.38.0_7ilbxdl5iguzcjriqqcg2m5cku: + resolution: {integrity: sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.38.0 + '@typescript-eslint/types': 5.38.0 + '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 + debug: 4.3.4 + eslint: 8.24.0 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript-eslint/scope-manager/5.38.0: + resolution: {integrity: sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.38.0 + '@typescript-eslint/visitor-keys': 5.38.0 + dev: false + + /@typescript-eslint/type-utils/5.38.0_7ilbxdl5iguzcjriqqcg2m5cku: + resolution: {integrity: sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 + '@typescript-eslint/utils': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + debug: 4.3.4 + eslint: 8.24.0 + tsutils: 3.21.0_typescript@4.8.3 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript-eslint/types/5.38.0: + resolution: {integrity: sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: false + + /@typescript-eslint/typescript-estree/5.38.0_typescript@4.8.3: + resolution: {integrity: sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.38.0 + '@typescript-eslint/visitor-keys': 5.38.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.8.3 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript-eslint/utils/5.38.0_7ilbxdl5iguzcjriqqcg2m5cku: + resolution: {integrity: sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@typescript-eslint/scope-manager': 5.38.0 + '@typescript-eslint/types': 5.38.0 + '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 + eslint: 8.24.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.24.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + + /@typescript-eslint/visitor-keys/5.38.0: + resolution: {integrity: sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.38.0 + eslint-visitor-keys: 3.3.0 + dev: false + + /accepts/1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + + /acorn-jsx/5.3.2_acorn@8.8.0: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.0 + + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + + /acorn/8.8.0: + resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} + engines: {node: '>=0.4.0'} + hasBin: true + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + /ansi-colors/4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /arg/4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /argparse/1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + /aria-query/4.2.2: + resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==} + engines: {node: '>=6.0'} + dependencies: + '@babel/runtime': 7.19.0 + '@babel/runtime-corejs3': 7.19.1 + dev: false + + /array-flatten/1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: false + + /array-includes/3.1.5: + resolution: {integrity: sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + get-intrinsic: 1.1.3 + is-string: 1.0.7 + dev: false + + /array-union/2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + /array.prototype.flat/1.3.0: + resolution: {integrity: sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + es-shim-unscopables: 1.0.0 + + /array.prototype.flatmap/1.3.0: + resolution: {integrity: sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + es-shim-unscopables: 1.0.0 + dev: false + + /arrify/1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + + /asn1.js/5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + dependencies: + bn.js: 4.12.0 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + dev: true + + /assertion-error/1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + + /ast-types-flow/0.0.7: + resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} + dev: false + + /axe-core/4.4.3: + resolution: {integrity: sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==} + engines: {node: '>=4'} + dev: false + + /axobject-query/2.2.0: + resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==} + dev: false + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js/1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: false + + /better-path-resolve/1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + dependencies: + is-windows: 1.0.2 + dev: true + + /better-sqlite3/7.6.2: + resolution: {integrity: sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.1 + dev: false + + /bindings/1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + + /bl/4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.0 + dev: false + + /bn.js/4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + dev: true + + /body-parser/1.20.0: + resolution: {integrity: sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.10.3 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /breakword/1.0.5: + resolution: {integrity: sha512-ex5W9DoOQ/LUEU3PMdLs9ua/CYZl1678NUkKOdUSi8Aw5F1idieaiRURCBFJCwVcrD1J8Iy3vfWSloaMwO2qFg==} + dependencies: + wcwidth: 1.0.1 + dev: true + + /buffer/5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /bytes/3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.3 + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + /camelcase-keys/6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + + /camelcase/5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true + + /caniuse-lite/1.0.30001412: + resolution: {integrity: sha512-+TeEIee1gS5bYOiuf+PS/kp2mrXic37Hl66VY6EAfxasIk5fELTktK2oOezYed12H8w7jt3s512PpulQidPjwA==} + dev: false + + /chai/4.3.6: + resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.2 + deep-eql: 3.0.1 + get-func-name: 2.0.0 + loupe: 2.3.4 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + /chardet/0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + + /check-error/1.0.2: + resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} + dev: true + + /chownr/1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + dev: false + + /ci-info/3.4.0: + resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==} + dev: true + + /cliui/6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + + /cliui/7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + + /clone/1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /commander/6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + dev: false + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + /content-disposition/0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type/1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + dev: false + + /cookie-signature/1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: false + + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + + /core-js-pure/3.25.2: + resolution: {integrity: sha512-ItD7YpW1cUB4jaqFLZXe1AXkyqIxz6GqPnsDV4uF4hVcWh/WAGIqSqw5p0/WdsILM0Xht9s3Koyw05R3K6RtiA==} + requiresBuild: true + dev: false + + /cors/2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + dev: false + + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /cross-env/7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + + /cross-spawn/5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + dev: true + + /cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + /csstype/3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + dev: true + + /csv-generate/3.4.3: + resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} + dev: true + + /csv-parse/4.16.3: + resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} + dev: true + + /csv-stringify/5.6.5: + resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} + dev: true + + /csv/5.5.3: + resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} + engines: {node: '>= 0.1.90'} + dependencies: + csv-generate: 3.4.3 + csv-parse: 4.16.3 + csv-stringify: 5.6.5 + stream-transform: 2.1.3 + dev: true + + /damerau-levenshtein/1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + dev: false + + /debug/2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /debug/3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: false + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /decamelize-keys/1.1.0: + resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize/1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /decompress-response/6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: false + + /deep-eql/3.0.1: + resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==} + engines: {node: '>=0.12'} + dependencies: + type-detect: 4.0.8 + dev: true + + /deep-extend/0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + + /deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + /deepmerge-ts/4.2.2: + resolution: {integrity: sha512-Ka3Kb21tiWjvQvS9U+1Dx+aqFAHsdTnMdYptLTmC2VAmDFMugWMY1e15aTODstipmCun8iNuqeSfcx6rsUUk0Q==} + engines: {node: '>=12.4.0'} + dev: false + + /defaults/1.0.3: + resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==} + dependencies: + clone: 1.0.4 + dev: true + + /define-properties/1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy/1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + + /detect-indent/6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /detect-libc/2.0.1: + resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} + engines: {node: '>=8'} + dev: false + + /diff/4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + + /doctrine/2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: false + + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + + /duplexer/0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: false + + /ee-first/1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /emoji-regex/9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: false + + /encodeurl/1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + + /end-of-stream/1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: false + + /enhanced-resolve/5.10.0: + resolution: {integrity: sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.10 + tapable: 2.2.1 + dev: true + + /enquirer/2.3.6: + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} + dependencies: + ansi-colors: 4.1.3 + dev: true + + /error-ex/1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + + /es-abstract/1.20.3: + resolution: {integrity: sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.1.3 + get-symbol-description: 1.0.0 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-weakref: 1.0.2 + object-inspect: 1.12.2 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + safe-regex-test: 1.0.0 + string.prototype.trimend: 1.0.5 + string.prototype.trimstart: 1.0.5 + unbox-primitive: 1.0.2 + + /es-shim-unscopables/1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 + + /es-to-primitive/1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + /esbuild-android-64/0.15.9: + resolution: {integrity: sha512-HQCX7FJn9T4kxZQkhPjNZC7tBWZqJvhlLHPU2SFzrQB/7nDXjmTIFpFTjt7Bd1uFpeXmuwf5h5fZm+x/hLnhbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-android-arm64/0.15.9: + resolution: {integrity: sha512-E6zbLfqbFVCNEKircSHnPiSTsm3fCRxeIMPfrkS33tFjIAoXtwegQfVZqMGR0FlsvVxp2NEDOUz+WW48COCjSg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64/0.15.9: + resolution: {integrity: sha512-gI7dClcDN/HHVacZhTmGjl0/TWZcGuKJ0I7/xDGJwRQQn7aafZGtvagOFNmuOq+OBFPhlPv1T6JElOXb0unkSQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64/0.15.9: + resolution: {integrity: sha512-VZIMlcRN29yg/sv7DsDwN+OeufCcoTNaTl3Vnav7dL/nvsApD7uvhVRbgyMzv0zU/PP0xRhhIpTyc7lxEzHGSw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64/0.15.9: + resolution: {integrity: sha512-uM4z5bTvuAXqPxrI204txhlsPIolQPWRMLenvGuCPZTnnGlCMF2QLs0Plcm26gcskhxewYo9LkkmYSS5Czrb5A==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64/0.15.9: + resolution: {integrity: sha512-HHDjT3O5gWzicGdgJ5yokZVN9K9KG05SnERwl9nBYZaCjcCgj/sX8Ps1jvoFSfNCO04JSsHSOWo4qvxFuj8FoA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32/0.15.9: + resolution: {integrity: sha512-AQIdE8FugGt1DkcekKi5ycI46QZpGJ/wqcMr7w6YUmOmp2ohQ8eO4sKUsOxNOvYL7hGEVwkndSyszR6HpVHLFg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64/0.15.9: + resolution: {integrity: sha512-4RXjae7g6Qs7StZyiYyXTZXBlfODhb1aBVAjd+ANuPmMhWthQilWo7rFHwJwL7DQu1Fjej2sODAVwLbcIVsAYQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm/0.15.9: + resolution: {integrity: sha512-3Zf2GVGUOI7XwChH3qrnTOSqfV1V4CAc/7zLVm4lO6JT6wbJrTgEYCCiNSzziSju+J9Jhf9YGWk/26quWPC6yQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64/0.15.9: + resolution: {integrity: sha512-a+bTtxJmYmk9d+s2W4/R1SYKDDAldOKmWjWP0BnrWtDbvUBNOm++du0ysPju4mZVoEFgS1yLNW+VXnG/4FNwdQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le/0.15.9: + resolution: {integrity: sha512-Zn9HSylDp89y+TRREMDoGrc3Z4Hs5u56ozZLQCiZAUx2+HdbbXbWdjmw3FdTJ/i7t5Cew6/Q+6kfO3KCcFGlyw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le/0.15.9: + resolution: {integrity: sha512-OEiOxNAMH9ENFYqRsWUj3CWyN3V8P3ZXyfNAtX5rlCEC/ERXrCEFCJji/1F6POzsXAzxvUJrTSTCy7G6BhA6Fw==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64/0.15.9: + resolution: {integrity: sha512-ukm4KsC3QRausEFjzTsOZ/qqazw0YvJsKmfoZZm9QW27OHjk2XKSQGGvx8gIEswft/Sadp03/VZvAaqv5AIwNA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x/0.15.9: + resolution: {integrity: sha512-uDOQEH55wQ6ahcIKzQr3VyjGc6Po/xblLGLoUk3fVL1qjlZAibtQr6XRfy5wPJLu/M2o0vQKLq4lyJ2r1tWKcw==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64/0.15.9: + resolution: {integrity: sha512-yWgxaYTQz+TqX80wXRq6xAtb7GSBAp6gqLKfOdANg9qEmAI1Bxn04IrQr0Mzm4AhxvGKoHzjHjMgXbCCSSDxcw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-node-externals/1.5.0_esbuild@0.15.9: + resolution: {integrity: sha512-9394Ne2t2Z243BWeNBRkXEYVMOVbQuzp7XSkASZTOQs0GSXDuno5aH5OmzEXc6GMuln5zJjpkZpgwUPW0uRKgw==} + peerDependencies: + esbuild: 0.12 - 0.15 + dependencies: + esbuild: 0.15.9 + find-up: 5.0.0 + tslib: 2.3.1 + dev: true + + /esbuild-openbsd-64/0.15.9: + resolution: {integrity: sha512-JmS18acQl4iSAjrEha1MfEmUMN4FcnnrtTaJ7Qg0tDCOcgpPPQRLGsZqhes0vmx8VA6IqRyScqXvaL7+Q0Uf3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-sunos-64/0.15.9: + resolution: {integrity: sha512-UKynGSWpzkPmXW3D2UMOD9BZPIuRaSqphxSCwScfEE05Be3KAmvjsBhht1fLzKpiFVJb0BYMd4jEbWMyJ/z1hQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32/0.15.9: + resolution: {integrity: sha512-aqXvu4/W9XyTVqO/hw3rNxKE1TcZiEYHPsXM9LwYmKSX9/hjvfIJzXwQBlPcJ/QOxedfoMVH0YnhhQ9Ffb0RGA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64/0.15.9: + resolution: {integrity: sha512-zm7h91WUmlS4idMtjvCrEeNhlH7+TNOmqw5dJPJZrgFaxoFyqYG6CKDpdFCQXdyKpD5yvzaQBOMVTCBVKGZDEg==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64/0.15.9: + resolution: {integrity: sha512-yQEVIv27oauAtvtuhJVfSNMztJJX47ismRS6Sv2QMVV9RM+6xjbMWuuwM2nxr5A2/gj/mu2z9YlQxiwoFRCfZA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild/0.15.9: + resolution: {integrity: sha512-OnYr1rkMVxtmMHIAKZLMcEUlJmqcbxBz9QoBU8G9v455na0fuzlT/GLu6l+SRghrk0Mm2fSSciMmzV43Q8e0Gg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.15.9 + '@esbuild/linux-loong64': 0.15.9 + esbuild-android-64: 0.15.9 + esbuild-android-arm64: 0.15.9 + esbuild-darwin-64: 0.15.9 + esbuild-darwin-arm64: 0.15.9 + esbuild-freebsd-64: 0.15.9 + esbuild-freebsd-arm64: 0.15.9 + esbuild-linux-32: 0.15.9 + esbuild-linux-64: 0.15.9 + esbuild-linux-arm: 0.15.9 + esbuild-linux-arm64: 0.15.9 + esbuild-linux-mips64le: 0.15.9 + esbuild-linux-ppc64le: 0.15.9 + esbuild-linux-riscv64: 0.15.9 + esbuild-linux-s390x: 0.15.9 + esbuild-netbsd-64: 0.15.9 + esbuild-openbsd-64: 0.15.9 + esbuild-sunos-64: 0.15.9 + esbuild-windows-32: 0.15.9 + esbuild-windows-64: 0.15.9 + esbuild-windows-arm64: 0.15.9 + dev: true + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + /eslint-config-next/12.3.1_7ilbxdl5iguzcjriqqcg2m5cku: + resolution: {integrity: sha512-EN/xwKPU6jz1G0Qi6Bd/BqMnHLyRAL0VsaQaWA7F3KkjAgZHi4f1uL1JKGWNxdQpHTW/sdGONBd0bzxUka/DJg==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@next/eslint-plugin-next': 12.3.1 + '@rushstack/eslint-patch': 1.2.0 + '@typescript-eslint/parser': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + eslint: 8.24.0 + eslint-import-resolver-node: 0.3.6 + eslint-import-resolver-typescript: 2.7.1_dg2pe6kqkrddxbf2funb723kue + eslint-plugin-import: 2.26.0_eg7qkaqlqcmvwyn7du43hqi4hi + eslint-plugin-jsx-a11y: 6.6.1_eslint@8.24.0 + eslint-plugin-react: 7.31.8_eslint@8.24.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.24.0 + typescript: 4.8.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - supports-color + dev: false + + /eslint-config-prettier/8.5.0_eslint@8.24.0: + resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.24.0 + dev: false + + /eslint-config-turbo/0.0.4_eslint@8.24.0: + resolution: {integrity: sha512-HErPS/wfWkSdV9Yd2dDkhZt3W2B78Ih/aWPFfaHmCMjzPalh+5KxRRGTf8MOBQLCebcWJX0lP1Zvc1rZIHlXGg==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + dependencies: + eslint: 8.24.0 + eslint-plugin-turbo: 0.0.4_eslint@8.24.0 + dev: false + + /eslint-import-resolver-node/0.3.6: + resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} + dependencies: + debug: 3.2.7 + resolve: 1.22.1 + transitivePeerDependencies: + - supports-color + dev: false + + /eslint-import-resolver-typescript/2.7.1_dg2pe6kqkrddxbf2funb723kue: + resolution: {integrity: sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==} + engines: {node: '>=4'} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + dependencies: + debug: 4.3.4 + eslint: 8.24.0 + eslint-plugin-import: 2.26.0_eg7qkaqlqcmvwyn7du43hqi4hi + glob: 7.2.3 + is-glob: 4.0.3 + resolve: 1.22.1 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - supports-color + dev: false + + /eslint-module-utils/2.7.4_xjmyyg3puwaol4labe76kcpdda: + resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + debug: 3.2.7 + eslint: 8.24.0 + eslint-import-resolver-node: 0.3.6 + eslint-import-resolver-typescript: 2.7.1_dg2pe6kqkrddxbf2funb723kue + transitivePeerDependencies: + - supports-color + dev: false + + /eslint-plugin-es/3.0.1_eslint@8.24.0: + resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=4.19.1' + dependencies: + eslint: 8.24.0 + eslint-utils: 2.1.0 + regexpp: 3.2.0 + dev: false + + /eslint-plugin-functional/4.4.0_7ilbxdl5iguzcjriqqcg2m5cku: + resolution: {integrity: sha512-DY/Iax9knIgzvxhVeSe+KoF9e4imLvzCvUEN1a+bWciDiBi3s1CG/1fw7AQVXoAqoNw9eYuP7C2u3OtHzMFy+A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^8.0.0 + tsutils: ^3.0.0 + typescript: ^3.4.1 || ^4.0.0 + peerDependenciesMeta: + tsutils: + optional: true + typescript: + optional: true + dependencies: + '@typescript-eslint/utils': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + deepmerge-ts: 4.2.2 + escape-string-regexp: 4.0.0 + eslint: 8.24.0 + semver: 7.3.7 + typescript: 4.8.3 + transitivePeerDependencies: + - supports-color + dev: false + + /eslint-plugin-import/2.26.0_eg7qkaqlqcmvwyn7du43hqi4hi: + resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.38.0_7ilbxdl5iguzcjriqqcg2m5cku + array-includes: 3.1.5 + array.prototype.flat: 1.3.0 + debug: 2.6.9 + doctrine: 2.1.0 + eslint: 8.24.0 + eslint-import-resolver-node: 0.3.6 + eslint-module-utils: 2.7.4_xjmyyg3puwaol4labe76kcpdda + has: 1.0.3 + is-core-module: 2.10.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.5 + resolve: 1.22.1 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: false + + /eslint-plugin-jsx-a11y/6.6.1_eslint@8.24.0: + resolution: {integrity: sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + '@babel/runtime': 7.19.0 + aria-query: 4.2.2 + array-includes: 3.1.5 + ast-types-flow: 0.0.7 + axe-core: 4.4.3 + axobject-query: 2.2.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.24.0 + has: 1.0.3 + jsx-ast-utils: 3.3.3 + language-tags: 1.0.5 + minimatch: 3.1.2 + semver: 6.3.0 + dev: false + + /eslint-plugin-node/11.1.0_eslint@8.24.0: + resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=5.16.0' + dependencies: + eslint: 8.24.0 + eslint-plugin-es: 3.0.1_eslint@8.24.0 + eslint-utils: 2.1.0 + ignore: 5.2.0 + minimatch: 3.1.2 + resolve: 1.22.1 + semver: 6.3.0 + dev: false + + /eslint-plugin-react-hooks/4.6.0_eslint@8.24.0: + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.24.0 + dev: false + + /eslint-plugin-react/7.31.8_eslint@8.24.0: + resolution: {integrity: sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.5 + array.prototype.flatmap: 1.3.0 + doctrine: 2.1.0 + eslint: 8.24.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.3 + minimatch: 3.1.2 + object.entries: 1.1.5 + object.fromentries: 2.0.5 + object.hasown: 1.1.1 + object.values: 1.1.5 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.0 + string.prototype.matchall: 4.0.7 + dev: false + + /eslint-plugin-turbo/0.0.4_eslint@8.24.0: + resolution: {integrity: sha512-dfmYE/iPvoJInQq+5E/0mj140y/rYwKtzZkn3uVK8+nvwC5zmWKQ6ehMWrL4bYBkGzSgpOndZM+jOXhPQ2m8Cg==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + dependencies: + eslint: 8.24.0 + dev: false + + /eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: false + + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + /eslint-utils/2.1.0: + resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} + engines: {node: '>=6'} + dependencies: + eslint-visitor-keys: 1.3.0 + dev: false + + /eslint-utils/3.0.0_eslint@8.24.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.24.0 + eslint-visitor-keys: 2.1.0 + + /eslint-visitor-keys/1.3.0: + resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} + engines: {node: '>=4'} + dev: false + + /eslint-visitor-keys/2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + /eslint/8.24.0: + resolution: {integrity: sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.3.2 + '@humanwhocodes/config-array': 0.10.5 + '@humanwhocodes/gitignore-to-minimatch': 1.0.2 + '@humanwhocodes/module-importer': 1.0.1 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.24.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.0 + esquery: 1.4.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.17.0 + globby: 11.1.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.0 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + js-sdsl: 4.1.4 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + /espree/9.4.0: + resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.0 + acorn-jsx: 5.3.2_acorn@8.8.0 + eslint-visitor-keys: 3.3.0 + + /esprima/4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /esquery/1.4.0: + resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + + /estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: false + + /estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + /etag/1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + + /expand-template/2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: false + + /express/4.18.1: + resolution: {integrity: sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.0 + content-disposition: 0.5.4 + content-type: 1.0.4 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.10.3 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /extendable-error/0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + dev: true + + /external-editor/3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + /fast-glob/3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + /fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + /fastq/1.13.0: + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + dependencies: + reusify: 1.0.4 + + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + + /file-uri-to-path/1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + + /finalhandler/1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /find-up/4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + dev: true + + /find-up/5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + /find-yarn-workspace-root2/1.2.16: + resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} + dependencies: + micromatch: 4.0.5 + pkg-dir: 4.2.0 + dev: true + + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + + /flatted/3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + + /forwarded/0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fp-ts/2.12.3: + resolution: {integrity: sha512-8m0XvW8kZbfnJOA4NvSVXu95mLbPf4LQGwQyqVukIYS4KzSNJiyKSmuZUmbVHteUi6MGkAJGPb0goPZqI+Tsqg==} + + /fresh/0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + + /fs-constants/1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: false + + /fs-extra/7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + + /fs-extra/8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name/1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + functions-have-names: 1.2.3 + + /functions-have-names/1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-func-name/2.0.0: + resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} + dev: true + + /get-intrinsic/1.1.3: + resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + + /github-from-package/0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: false + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + + /glob/7.1.7: + resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: false + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /globals/13.17.0: + resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + + /globby/11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.12 + ignore: 5.2.0 + merge2: 1.4.1 + slash: 3.0.0 + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + + /gzip-size/6.0.0: + resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} + engines: {node: '>=10'} + dependencies: + duplexer: 0.1.2 + dev: false + + /hard-rejection/2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + + /has-bigints/1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + /has-property-descriptors/1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.1.3 + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hosted-git-info/2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /http-errors/2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /human-id/1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + dev: true + + /iconv-lite/0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + + /ieee754/1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + + /ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} + + /immutable-json-patch/5.0.0: + resolution: {integrity: sha512-mlr7VHNQSyUjZecTA8tCXjsfM8J5nUmnQ4YuahGETTHx0/inOHFTIUr880wUSV7hLXcQ3UhUJvCpXpdnLYKjfQ==} + dev: false + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + /imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + /indent-string/4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ini/1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: false + + /internal-slot/1.0.3: + resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.3 + has: 1.0.3 + side-channel: 1.0.4 + + /ipaddr.js/1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + + /is-arrayish/0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-callable/1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + /is-ci/3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + dependencies: + ci-info: 3.4.0 + dev: true + + /is-core-module/2.10.0: + resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} + dependencies: + has: 1.0.3 + + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + + /is-negative-zero/2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-plain-obj/1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-subdir/1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + dependencies: + better-path-resolve: 1.0.0 + dev: true + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + + /is-weakref/1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + + /is-windows/1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /js-sdsl/4.1.4: + resolution: {integrity: sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==} + + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + /js-yaml/3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + + /json-parse-even-better-errors/2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + /json5/1.0.1: + resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==} + hasBin: true + dependencies: + minimist: 1.2.6 + dev: false + + /jsonfile/4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.10 + dev: true + + /jsx-ast-utils/3.3.3: + resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} + engines: {node: '>=4.0'} + dependencies: + array-includes: 3.1.5 + object.assign: 4.1.4 + dev: false + + /kind-of/6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true + + /kleur/4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /kysely/0.21.6: + resolution: {integrity: sha512-DNecGKzzYtx2OumPJ8inrVFsSfq1lNHLFZDJvXMQxqbrTFElqq70VLR3DiK0P9fw4pB+xXTYvLiLurWiYqgk3w==} + engines: {node: '>=14.0.0'} + dev: false + + /language-subtag-registry/0.3.22: + resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} + dev: false + + /language-tags/1.0.5: + resolution: {integrity: sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==} + dependencies: + language-subtag-registry: 0.3.22 + dev: false + + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + /lines-and-columns/1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + + /load-yaml-file/0.2.0: + resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} + engines: {node: '>=6'} + dependencies: + graceful-fs: 4.2.10 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + dev: true + + /local-pkg/0.4.2: + resolution: {integrity: sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==} + engines: {node: '>=14'} + dev: true + + /locate-path/5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + dev: true + + /locate-path/6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + /lodash.startcase/4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + dev: true + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /loose-envify/1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + + /loupe/2.3.4: + resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} + dependencies: + get-func-name: 2.0.0 + dev: true + + /lru-cache/4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + dev: true + + /lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: false + + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /map-obj/1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj/4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + + /media-typer/0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /meow/6.1.1: + resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} + engines: {node: '>=8'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.0 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 2.5.0 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.13.1 + yargs-parser: 18.1.3 + dev: true + + /merge-descriptors/1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + /methods/1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /mime/1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /mimic-response/3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: false + + /min-indent/1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /minimalistic-assert/1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + dev: true + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /minimist-options/4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + dev: true + + /minimist/1.2.6: + resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + dev: false + + /mixme/0.5.4: + resolution: {integrity: sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw==} + engines: {node: '>= 8.0.0'} + dev: true + + /mkdirp-classic/0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: false + + /mrmime/1.0.1: + resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} + engines: {node: '>=10'} + dev: false + + /ms/2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /ms/2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /murmurhash/2.0.1: + resolution: {integrity: sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==} + dev: false + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /nanoid/4.0.0: + resolution: {integrity: sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==} + engines: {node: ^14 || ^16 || >=18} + hasBin: true + dev: false + + /napi-build-utils/1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: false + + /natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + /negotiator/0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /next-transpile-modules/9.0.0: + resolution: {integrity: sha512-VCNFOazIAnXn1hvgYYSTYMnoWgKgwlYh4lm1pKbSfiB3kj5ZYLcKVhfh3jkPOg1cnd9DP+pte9yCUocdPEUBTQ==} + dependencies: + enhanced-resolve: 5.10.0 + escalade: 3.1.1 + dev: true + + /next/12.3.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==} + engines: {node: '>=12.22.0'} + hasBin: true + peerDependencies: + fibers: '>= 3.1.0' + node-sass: ^6.0.0 || ^7.0.0 + react: ^17.0.2 || ^18.0.0-0 + react-dom: ^17.0.2 || ^18.0.0-0 + sass: ^1.3.0 + peerDependenciesMeta: + fibers: + optional: true + node-sass: + optional: true + sass: + optional: true + dependencies: + '@next/env': 12.3.1 + '@swc/helpers': 0.4.11 + caniuse-lite: 1.0.30001412 + postcss: 8.4.14 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + styled-jsx: 5.0.7_react@18.2.0 + use-sync-external-store: 1.2.0_react@18.2.0 + optionalDependencies: + '@next/swc-android-arm-eabi': 12.3.1 + '@next/swc-android-arm64': 12.3.1 + '@next/swc-darwin-arm64': 12.3.1 + '@next/swc-darwin-x64': 12.3.1 + '@next/swc-freebsd-x64': 12.3.1 + '@next/swc-linux-arm-gnueabihf': 12.3.1 + '@next/swc-linux-arm64-gnu': 12.3.1 + '@next/swc-linux-arm64-musl': 12.3.1 + '@next/swc-linux-x64-gnu': 12.3.1 + '@next/swc-linux-x64-musl': 12.3.1 + '@next/swc-win32-arm64-msvc': 12.3.1 + '@next/swc-win32-ia32-msvc': 12.3.1 + '@next/swc-win32-x64-msvc': 12.3.1 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + + /node-abi/3.24.0: + resolution: {integrity: sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==} + engines: {node: '>=10'} + dependencies: + semver: 7.3.7 + dev: false + + /normalize-package-data/2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: true + + /object-assign/4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: false + + /object-inspect/1.12.2: + resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + /object.assign/4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + /object.entries/1.1.5: + resolution: {integrity: sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + dev: false + + /object.fromentries/2.0.5: + resolution: {integrity: sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + dev: false + + /object.hasown/1.1.1: + resolution: {integrity: sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==} + dependencies: + define-properties: 1.1.4 + es-abstract: 1.20.3 + dev: false + + /object.values/1.1.5: + resolution: {integrity: sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + dev: false + + /on-finished/2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /opener/1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} + hasBin: true + dev: false + + /openpgp/5.5.0: + resolution: {integrity: sha512-SpwcJnxrK9Y0HRM6KxSFqkAEOSWEabCH/c8dII/+y2e5f6KvuDG5ZE7JXaPBaVJNE4VUZZeTphxXDoZD0KOHrw==} + engines: {node: '>= 8.0.0'} + dependencies: + asn1.js: 5.4.1 + dev: true + + /optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + + /os-tmpdir/1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true + + /outdent/0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + dev: true + + /p-filter/2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + dependencies: + p-map: 2.1.0 + dev: true + + /p-limit/2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: true + + /p-limit/3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + + /p-locate/4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + dev: true + + /p-locate/5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + + /p-map/2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + dev: true + + /p-try/2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + + /parse-json/5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.18.6 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: true + + /parseurl/1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-to-regexp/0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false + + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + /pathval/1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + /pify/4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + dev: true + + /pkg-dir/4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + dev: true + + /postcss/8.4.14: + resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + + /postcss/8.4.16: + resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /prebuild-install/7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.1 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.6 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.24.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: false + + /preferred-pm/3.0.3: + resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} + engines: {node: '>=10'} + dependencies: + find-up: 5.0.0 + find-yarn-workspace-root2: 1.2.16 + path-exists: 4.0.0 + which-pm: 2.0.0 + dev: true + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + /prettier/2.7.1: + resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /prop-types/15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: false + + /proxy-addr/2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + + /pseudomap/1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + dev: true + + /pump/3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: false + + /punycode/2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + + /qs/6.10.3: + resolution: {integrity: sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + /quick-lru/4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + + /range-parser/1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body/2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /rc/1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.6 + strip-json-comments: 2.0.1 + dev: false + + /react-dom/18.2.0_react@18.2.0: + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + + /react-is/16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: false + + /react/18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + + /read-pkg-up/7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg/5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + + /read-yaml-file/1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + dependencies: + graceful-fs: 4.2.10 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + dev: true + + /readable-stream/3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + + /redent/3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + dev: true + + /regenerator-runtime/0.13.9: + resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} + + /regexp.prototype.flags/1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.3 + + /regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + + /require-directory/2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /require-main-filename/2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: true + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + /resolve-from/5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.10.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /resolve/2.0.0-next.4: + resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} + hasBin: true + dependencies: + is-core-module: 2.10.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: false + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + /rfc6902/5.0.1: + resolution: {integrity: sha512-tYGfLpKIq9X7lrt4o3IkD9w9bpeAtsejfAqWNR98AoxfTsZqCepKa8eDlRiX8QMiCOD9vMx0/YbKLx0G1nPi5w==} + dev: false + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + + /rollup/2.78.1: + resolution: {integrity: sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /safe-regex-test/1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + is-regex: 1.1.4 + + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + /scheduler/0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: false + + /semver/5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + + /semver/6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + dev: false + + /semver/7.3.7: + resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: false + + /send/0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /serve-static/1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: false + + /set-blocking/2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: true + + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /sha256-uint8array/0.10.3: + resolution: {integrity: sha512-SFTs87RfXVulKrhhP6B5/qcFruOKQZaKf6jY9V4PJ7NOG0qIlQP6XL4pQq5xagsuP/Wd55S7tUBJpRajEsDUEQ==} + dev: false + + /shebang-command/1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: true + + /shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + + /shebang-regex/1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: true + + /shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + object-inspect: 1.12.2 + + /signal-exit/3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + + /simple-concat/1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get/4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + + /sirv/1.0.19: + resolution: {integrity: sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==} + engines: {node: '>= 10'} + dependencies: + '@polka/url': 1.0.0-next.21 + mrmime: 1.0.1 + totalist: 1.1.0 + dev: false + + /slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + /smartwrap/2.0.2: + resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} + engines: {node: '>=6'} + hasBin: true + dependencies: + array.prototype.flat: 1.3.0 + breakword: 1.0.5 + grapheme-splitter: 1.0.4 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + yargs: 15.4.1 + dev: true + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + + /spawndamnit/2.0.0: + resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + dependencies: + cross-spawn: 5.1.0 + signal-exit: 3.0.7 + dev: true + + /spdx-correct/3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-exceptions/2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse/3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-license-ids/3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + dev: true + + /sprintf-js/1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true + + /statuses/2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + + /stream-transform/2.1.3: + resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} + dependencies: + mixme: 0.5.4 + dev: true + + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string.prototype.matchall/4.0.7: + resolution: {integrity: sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + get-intrinsic: 1.1.3 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + dev: false + + /string.prototype.trimend/1.0.5: + resolution: {integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + + /string.prototype.trimstart/1.0.5: + resolution: {integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.20.3 + + /string_decoder/1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + + /strip-bom/3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + /strip-indent/3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /strip-json-comments/2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + /strip-literal/0.4.2: + resolution: {integrity: sha512-pv48ybn4iE1O9RLgCAN0iU4Xv7RlBTiit6DKmMiErbs9x1wH6vXBs45tWc0H5wUIF6TLTrKweqkmYF/iraQKNw==} + dependencies: + acorn: 8.8.0 + dev: true + + /styled-jsx/5.0.7_react@18.2.0: + resolution: {integrity: sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + react: 18.2.0 + dev: false + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /tapable/2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + + /tar-fs/2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: false + + /tar-stream/2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.0 + dev: false + + /term-size/2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + dev: true + + /text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + /tinybench/2.1.5: + resolution: {integrity: sha512-ak+PZZEuH3mw6CCFOgf5S90YH0MARnZNhxjhjguAmoJimEMAJuNip/rJRd6/wyylHItomVpKTzZk9zrhTrQCoQ==} + dev: true + + /tinypool/0.3.0: + resolution: {integrity: sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy/1.0.2: + resolution: {integrity: sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==} + engines: {node: '>=14.0.0'} + dev: true + + /tmp/0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /totalist/1.1.0: + resolution: {integrity: sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==} + engines: {node: '>=6'} + dev: false + + /trim-newlines/3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + dev: true + + /ts-node/10.9.1_y2ippukchihtaxubiro7c5yd6u: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 16.11.60 + acorn: 8.8.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.8.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tsconfig-paths/3.14.1: + resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.1 + minimist: 1.2.6 + strip-bom: 3.0.0 + dev: false + + /tslib/1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: false + + /tslib/2.3.1: + resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} + dev: true + + /tslib/2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + + /tsutils/3.21.0_typescript@4.8.3: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.8.3 + dev: false + + /tty-table/4.1.6: + resolution: {integrity: sha512-kRj5CBzOrakV4VRRY5kUWbNYvo/FpOsz65DzI5op9P+cHov3+IqPbo1JE1ZnQGkHdZgNFDsrEjrfqqy/Ply9fw==} + engines: {node: '>=8.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + csv: 5.5.3 + kleur: 4.1.5 + smartwrap: 2.0.2 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + yargs: 17.5.1 + dev: true + + /tunnel-agent/0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /turbo-darwin-64/1.5.3: + resolution: {integrity: sha512-MBS8b/3DuMY6v3ljEX9qssHGQXnI4VDWLqvQ6FGfZFMp8lqa7mfoXv1U/MNR9OhSczaftsIS1e9mnD9m/qv7TQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /turbo-darwin-arm64/1.5.3: + resolution: {integrity: sha512-XNSV6SaxS8dAvGx2BF3H7MsKZ4zchj2kP/eXTss/vUcSRsS+zx0urZcEgxeGUeMk7V7fJq/5Ow7thApNojpwpw==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /turbo-linux-64/1.5.3: + resolution: {integrity: sha512-YhYu50CUvy5m80l6dEXEgC3CvjjTelQb14fknAzIXoKztbWhBZqUCucR6jLRZpKgODwP4Fo4LUzz478EMwqy/Q==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /turbo-linux-arm64/1.5.3: + resolution: {integrity: sha512-JjJjxy0kkr/xAWAAE8t7UaTBc3GUc5Tz/Bupbve2VzG0w75md0LqXUV34WpyxMiNTNLmK8Dq7bIczG6OkJ29xQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /turbo-windows-64/1.5.3: + resolution: {integrity: sha512-PS7+Isy7OX9xWWvtg2FKmh/eN4fTNR2r6RW5m+b+zR7t04QLWYOya1R8CeqgA6GyeFpn4KUxC+AeZ0wOi8RSPw==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /turbo-windows-arm64/1.5.3: + resolution: {integrity: sha512-DQzjFbPRd/Db5kkJtCER5DNcbY4ez5Bh8usemNlPZQ7I/5XwEUl9Rn3ss2LJsiv/pR7PkY92TUvmyFtBnqao8Q==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /turbo/1.5.3: + resolution: {integrity: sha512-/94cswfhXr6wWD6CFyF7E8bjEdjar4O+gzCJ3d49X9t9u9aDYFoQH/TlYTSNbAwXYiGqzJoodhf3kXRyrAiqSg==} + hasBin: true + requiresBuild: true + optionalDependencies: + turbo-darwin-64: 1.5.3 + turbo-darwin-arm64: 1.5.3 + turbo-linux-64: 1.5.3 + turbo-linux-arm64: 1.5.3 + turbo-windows-64: 1.5.3 + turbo-windows-arm64: 1.5.3 + dev: true + + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + + /type-detect/4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest/0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + dev: true + + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + /type-fest/0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest/0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + + /type-is/1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + + /typescript/3.9.10: + resolution: {integrity: sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /typescript/4.8.3: + resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==} + engines: {node: '>=4.2.0'} + hasBin: true + + /unbox-primitive/1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + /universalify/0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: true + + /unpipe/1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + + /use-sync-external-store/1.2.0_react@18.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + + /util-deprecate/1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /utils-merge/1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + dev: false + + /v8-compile-cache-lib/3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /validate-npm-package-license/3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: true + + /vary/1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + + /vite/3.1.3: + resolution: {integrity: sha512-/3XWiktaopByM5bd8dqvHxRt5EEgRikevnnrpND0gRfNkrMrPaGGexhtLCzv15RcCMtV2CLw+BPas8YFeSG0KA==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + less: '*' + sass: '*' + stylus: '*' + terser: ^5.4.0 + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.15.9 + postcss: 8.4.16 + resolve: 1.22.1 + rollup: 2.78.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /vitest/0.23.4: + resolution: {integrity: sha512-iukBNWqQAv8EKDBUNntspLp9SfpaVFbmzmM0sNcnTxASQZMzRw3PsM6DMlsHiI+I6GeO5/sYDg3ecpC+SNFLrQ==} + engines: {node: '>=v14.16.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/chai': 4.3.3 + '@types/chai-subset': 1.3.3 + '@types/node': 16.11.60 + chai: 4.3.6 + debug: 4.3.4 + local-pkg: 0.4.2 + strip-literal: 0.4.2 + tinybench: 2.1.5 + tinypool: 0.3.0 + tinyspy: 1.0.2 + vite: 3.1.3 + transitivePeerDependencies: + - less + - sass + - stylus + - supports-color + - terser + dev: true + + /wcwidth/1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.3 + dev: true + + /webpack-bundle-analyzer/4.3.0: + resolution: {integrity: sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==} + engines: {node: '>= 10.13.0'} + hasBin: true + dependencies: + acorn: 8.8.0 + acorn-walk: 8.2.0 + chalk: 4.1.2 + commander: 6.2.1 + gzip-size: 6.0.0 + lodash: 4.17.21 + opener: 1.5.2 + sirv: 1.0.19 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + /which-module/2.0.0: + resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} + dev: true + + /which-pm/2.0.0: + resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} + engines: {node: '>=8.15'} + dependencies: + load-yaml-file: 0.2.0 + path-exists: 4.0.0 + dev: true + + /which/1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + + /word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + + /wrap-ansi/6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /ws/7.5.9: + resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + + /y18n/4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: true + + /y18n/5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + + /yallist/2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + dev: true + + /yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: false + + /yargs-parser/18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: true + + /yargs-parser/21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs/15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.0 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: true + + /yargs/17.5.1: + resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} + engines: {node: '>=12'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + + /yocto-queue/0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + /zod/3.19.1: + resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==} + dev: true + + github.com/rhashimoto/wa-sqlite/5fdc80ff1c153aeb0dab65a5e23ca22938c827a3: + resolution: {tarball: https://codeload.github.com/rhashimoto/wa-sqlite/tar.gz/5fdc80ff1c153aeb0dab65a5e23ca22938c827a3} + name: wa-sqlite + version: 0.8.2 + dev: false diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..3ff5faaa --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "apps/*" + - "packages/*" diff --git a/turbo.json b/turbo.json new file mode 100644 index 00000000..b3080aca --- /dev/null +++ b/turbo.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://turborepo.org/schema.json", + "pipeline": { + "build": { + "outputs": ["dist/**", ".next/**"], + "dependsOn": ["^build"] + }, + "test": { + "outputs": ["coverage/**"], + "dependsOn": [] + }, + "lint": { + "outputs": [] + }, + "dev": { + "cache": false + }, + "start": { + "cache": false + }, + "clean": { + "cache": false + } + } +}