├── app-todos
├── .npmrc
├── app
│ ├── tailwind.css
│ ├── map-sqlite-resultset.ts
│ ├── utils.test.ts
│ ├── singleton.server.ts
│ ├── entry.client.tsx
│ ├── routes
│ │ ├── healthcheck.tsx
│ │ └── todos.$todosId.tsx
│ ├── root.tsx
│ ├── db.server.ts
│ ├── utils.ts
│ └── entry.server.tsx
├── .dockerignore
├── .env.example
├── remix.env.d.ts
├── .prettierignore
├── public
│ └── favicon.ico
├── postcss.config.js
├── test
│ └── setup-test-env.ts
├── .gitignore
├── tailwind.config.ts
├── remix.config.js
├── .gitpod.Dockerfile
├── vitest.config.ts
├── .eslintrc.js
├── tsconfig.json
├── fly.toml
├── Dockerfile
├── .gitpod.yml
├── package.json
└── .github
│ └── workflows
│ └── deploy.yml
├── app-admin
├── src
│ ├── vite-env.d.ts
│ ├── main.tsx
│ ├── index.css
│ ├── App.css
│ ├── assets
│ │ └── react.svg
│ └── App.tsx
├── vite.config.ts
├── tsconfig.node.json
├── .gitignore
├── index.html
├── .eslintrc.cjs
├── tsconfig.json
├── package.json
└── public
│ └── vite.svg
├── .prettierrc
├── .dockerignore
├── server
├── map-sqlite-resultset.ts
├── tsconfig.json
├── package.json
├── __snapshots__
│ └── mutators.test.ts.snap
├── .gitignore
├── index.ts
├── mutators.test.ts
├── mutators.ts
├── trpc.ts
└── pnpm-lock.yaml
├── machines
├── package.json
├── __snapshots__
│ └── machines.test.ts.snap
├── ping-pong.ts
├── client.ts
├── server.ts
├── machines.test.ts
└── pnpm-lock.yaml
├── package.json
├── README.md
├── fly.toml
├── .eslintrc.js
├── LICENSE
├── Dockerfile
└── .gitignore
/app-todos/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
2 |
--------------------------------------------------------------------------------
/app-admin/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "semi": false,
4 | "tabWidth": 2
5 | }
6 |
--------------------------------------------------------------------------------
/app-todos/app/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/app-todos/.dockerignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | *.log
3 | .DS_Store
4 | .env
5 | /.cache
6 | /public/build
7 | /build
8 |
--------------------------------------------------------------------------------
/app-todos/.env.example:
--------------------------------------------------------------------------------
1 | DATABASE_URL="file:./data.db?connection_limit=1"
2 | SESSION_SECRET="super-duper-s3cret"
3 |
--------------------------------------------------------------------------------
/app-todos/remix.env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/app-todos/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /build
4 | /public/build
5 | .env
6 |
7 | /app/styles/tailwind.css
8 |
--------------------------------------------------------------------------------
/app-todos/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KyleAMathews/multi-tenancy-saas-demo/HEAD/app-todos/public/favicon.ico
--------------------------------------------------------------------------------
/app-todos/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/app-todos/test/setup-test-env.ts:
--------------------------------------------------------------------------------
1 | import { installGlobals } from "@remix-run/node";
2 | import "@testing-library/jest-dom/extend-expect";
3 |
4 | installGlobals();
5 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /server/node_modules
3 | /server/.cache
4 | /app-admin/node_modules
5 | *.log
6 | .DS_Store
7 | .env
8 | /.cache
9 | /public/build
10 | /build
11 |
--------------------------------------------------------------------------------
/app-todos/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /build
4 | /public/build
5 | .env
6 |
7 | /cypress/screenshots
8 | /cypress/videos
9 | /prisma/data.db
10 | /prisma/data.db-journal
11 |
--------------------------------------------------------------------------------
/app-admin/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/app-todos/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | export default {
4 | content: ["./app/**/*.{js,jsx,ts,tsx}"],
5 | theme: {
6 | extend: {},
7 | },
8 | plugins: [],
9 | } satisfies Config;
10 |
--------------------------------------------------------------------------------
/app-todos/remix.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@remix-run/dev').AppConfig} */
2 | module.exports = {
3 | cacheDirectory: "./node_modules/.cache/remix",
4 | ignoredRouteFiles: ["**/.*", "**/*.test.{ts,tsx}"],
5 | serverModuleFormat: "cjs",
6 | };
7 |
--------------------------------------------------------------------------------
/server/map-sqlite-resultset.ts:
--------------------------------------------------------------------------------
1 | export function mapResultSet(rs) {
2 | return rs.rows.map((row) => {
3 | const obj = {}
4 |
5 | rs.columns.forEach((name, i) => {
6 | obj[name] = row[i]
7 | })
8 |
9 | return obj
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/app-todos/app/map-sqlite-resultset.ts:
--------------------------------------------------------------------------------
1 | export function mapResultSet(rs) {
2 | return rs.rows.map((row) => {
3 | const obj = {}
4 |
5 | rs.columns.forEach((name, i) => {
6 | obj[name] = row[i]
7 | })
8 |
9 | return obj
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/app-todos/.gitpod.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM gitpod/workspace-full
2 |
3 | # Install Fly
4 | RUN curl -L https://fly.io/install.sh | sh
5 | ENV FLYCTL_INSTALL="/home/gitpod/.fly"
6 | ENV PATH="$FLYCTL_INSTALL/bin:$PATH"
7 |
8 | # Install GitHub CLI
9 | RUN brew install gh
10 |
--------------------------------------------------------------------------------
/app-admin/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/machines/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@libsql/client": "^0.3.5",
4 | "ejs": "^3.1.9",
5 | "node-sql-parser": "^4.11.0",
6 | "uuid": "^9.0.1",
7 | "yjs": "^13.6.8"
8 | },
9 | "devDependencies": {
10 | "vitest": "^0.34.5"
11 | },
12 | "scripts": {
13 | "test": "vitest"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app-admin/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/app-admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app-admin/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { SituatedProvider, loader } from 'situated'
4 | import App from './App.tsx'
5 | import './index.css'
6 |
7 | async function main() {
8 | await loader()
9 | ReactDOM.createRoot(document.getElementById(`root`) as HTMLElement).render(
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | main()
17 |
--------------------------------------------------------------------------------
/app-todos/vitest.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import react from "@vitejs/plugin-react";
5 | import tsconfigPaths from "vite-tsconfig-paths";
6 | import { defineConfig } from "vitest/config";
7 |
8 | export default defineConfig({
9 | plugins: [react(), tsconfigPaths()],
10 | test: {
11 | globals: true,
12 | environment: "happy-dom",
13 | setupFiles: ["./test/setup-test-env.ts"],
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/app-todos/app/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { validateEmail } from "./utils";
2 |
3 | test("validateEmail returns false for non-emails", () => {
4 | expect(validateEmail(undefined)).toBe(false);
5 | expect(validateEmail(null)).toBe(false);
6 | expect(validateEmail("")).toBe(false);
7 | expect(validateEmail("not-an-email")).toBe(false);
8 | expect(validateEmail("n@")).toBe(false);
9 | });
10 |
11 | test("validateEmail returns true for emails", () => {
12 | expect(validateEmail("kody@example.com")).toBe(true);
13 | });
14 |
--------------------------------------------------------------------------------
/app-admin/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/app-todos/app/singleton.server.ts:
--------------------------------------------------------------------------------
1 | // Since the dev server re-requires the bundle, do some shenanigans to make
2 | // certain things persist across that 😆
3 | // Borrowed/modified from https://github.com/jenseng/abuse-the-platform/blob/2993a7e846c95ace693ce61626fa072174c8d9c7/app/utils/singleton.ts
4 |
5 | export const singleton = (
6 | name: string,
7 | valueFactory: () => Value,
8 | ): Value => {
9 | const g = global as any;
10 | g.__singletons ??= {};
11 | g.__singletons[name] ??= valueFactory();
12 | return g.__singletons[name];
13 | };
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multi-tenancy-saas-demo",
3 | "dependencies": {
4 | "@trpc/server": "^10.38.5",
5 | "trpc-yjs": "^0.0.1"
6 | },
7 | "devDependencies": {
8 | "@typescript-eslint/eslint-plugin": "^6.7.0",
9 | "@typescript-eslint/parser": "^6.7.0",
10 | "eslint": "^8.49.0",
11 | "eslint-config-prettier": "^9.0.0",
12 | "eslint-config-react": "^1.1.7",
13 | "eslint-plugin-prettier": "^5.0.0",
14 | "eslint-plugin-react": "^7.33.2",
15 | "prettier": "^3.0.3",
16 | "typescript": "^5.2.2"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # multi-tenancy-saas-demo
2 |
3 | Demo of building a multi-tenancy saas app w/ Turso.
4 |
5 | - app-admin: client code for an internal-only admin app. Something every SaaS tool needs. It shows information about each app instance and lets you create new ones or clone existing ones (useful for setting up demos). It communicates with Turso to manage db instances and reads/writes to an admin database.
6 | - server: API for the admin app
7 | - app-todos: the app-server. It uses an embedded replica to pull down the admin database so it can correctly serve each app instance
8 |
--------------------------------------------------------------------------------
/fly.toml:
--------------------------------------------------------------------------------
1 | app = "admin-todos-saas"
2 | primary_region = "sea"
3 | kill_signal = "SIGINT"
4 | kill_timeout = "5s"
5 |
6 | [[services]]
7 | protocol = "tcp"
8 | internal_port = 3000
9 | processes = ["app"]
10 |
11 | [[services.ports]]
12 | port = 80
13 | handlers = ["http"]
14 | force_https = true
15 |
16 | [[services.ports]]
17 | port = 443
18 | handlers = ["tls", "http"]
19 | [services.concurrency]
20 | type = "connections"
21 | hard_limit = 25
22 | soft_limit = 20
23 |
24 | [[services.tcp_checks]]
25 | interval = "15s"
26 | timeout = "2s"
27 | grace_period = "1s"
28 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["ESNext"],
4 | "module": "esnext",
5 | "target": "esnext",
6 | "moduleResolution": "bundler",
7 | "moduleDetection": "force",
8 | "allowImportingTsExtensions": true,
9 | "noEmit": true,
10 | "composite": true,
11 | "strict": true,
12 | "downlevelIteration": true,
13 | "skipLibCheck": true,
14 | "jsx": "preserve",
15 | "allowSyntheticDefaultImports": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "allowJs": true,
18 | "types": [
19 | "bun-types" // add Bun global
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app-todos/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import('eslint').Linter.Config} */
2 | module.exports = {
3 | root: true,
4 | extends: [
5 | "@remix-run/eslint-config",
6 | "@remix-run/eslint-config/node",
7 | "@remix-run/eslint-config/jest-testing-library",
8 | "prettier",
9 | ],
10 | env: {
11 | "cypress/globals": true,
12 | },
13 | plugins: ["cypress"],
14 | // we're using vitest which has a very similar API to jest
15 | // (so the linting plugins work nicely), but it means we have to explicitly
16 | // set the jest version.
17 | settings: {
18 | jest: {
19 | version: 28,
20 | },
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/machines/__snapshots__/machines.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`create/delete dbs 1`] = `
4 | {
5 | "foo": true,
6 | }
7 | `;
8 |
9 | exports[`create/delete dbs 2`] = `
10 | [
11 | {
12 | "email": "alice@example.org",
13 | "id": 1,
14 | "name": "Alice",
15 | },
16 | ]
17 | `;
18 |
19 | exports[`update a robot name 1`] = `
20 | {
21 | "done": true,
22 | "error": false,
23 | "mutator": "updateRobotName",
24 | "request": {
25 | "id": "123",
26 | "name": "beep",
27 | },
28 | "response": {
29 | "ok": true,
30 | },
31 | "value": "responded",
32 | }
33 | `;
34 |
--------------------------------------------------------------------------------
/machines/ping-pong.ts:
--------------------------------------------------------------------------------
1 | import { createMachine } from "xstate";
2 |
3 | export const machine = createMachine(
4 | {
5 | id: `Ping/Pong`,
6 | initial: `Pinged`,
7 | states: {
8 | Pinged: {
9 | on: {
10 | ON_SERVER: {
11 | target: `Ponged`,
12 | },
13 | },
14 | },
15 | Ponged: {
16 | type: `final`,
17 | },
18 | },
19 | schema: { events: {} as { type: `ON_SERVER` } },
20 | predictableActionArguments: true,
21 | preserveActionOrder: true,
22 | },
23 | {
24 | actions: {},
25 | services: {},
26 | guards: {},
27 | delays: {},
28 | },
29 | );
30 |
--------------------------------------------------------------------------------
/app-todos/app/entry.client.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * By default, Remix will handle hydrating your app on the client for you.
3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4 | * For more information, see https://remix.run/docs/en/main/file-conventions/entry.client
5 | */
6 |
7 | import { RemixBrowser } from "@remix-run/react";
8 | import { startTransition, StrictMode } from "react";
9 | import { hydrateRoot } from "react-dom/client";
10 |
11 | startTransition(() => {
12 | hydrateRoot(
13 | document,
14 |
15 |
16 | ,
17 | );
18 | });
19 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | node: true,
6 | },
7 | extends: [
8 | `eslint:recommended`,
9 | `plugin:@typescript-eslint/recommended`,
10 | `plugin:react/recommended`,
11 | `plugin:prettier/recommended`,
12 | ],
13 | parserOptions: {
14 | ecmaVersion: 2022,
15 | requireConfigFile: false,
16 | sourceType: `module`,
17 | ecmaFeatures: {
18 | jsx: true,
19 | },
20 | },
21 | settings: {
22 | react: {
23 | version: `detect`,
24 | },
25 | },
26 | parser: '@typescript-eslint/parser',
27 | plugins: [`react`, `prettier`],
28 | rules: {
29 | quotes: [`error`, `backtick`],
30 | },
31 | }
32 |
--------------------------------------------------------------------------------
/app-admin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/app-todos/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["./cypress", "./cypress.config.ts"],
3 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
4 | "compilerOptions": {
5 | "lib": ["DOM", "DOM.Iterable", "ES2020"],
6 | "types": ["vitest/globals"],
7 | "isolatedModules": true,
8 | "esModuleInterop": true,
9 | "jsx": "react-jsx",
10 | "module": "CommonJS",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "target": "ES2020",
14 | "strict": true,
15 | "allowJs": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "baseUrl": ".",
18 | "paths": {
19 | "~/*": ["./app/*"]
20 | },
21 | "skipLibCheck": true,
22 |
23 | // Remix takes care of building everything in `remix build`.
24 | "noEmit": true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "module": "index.ts",
4 | "type": "module",
5 | "devDependencies": {
6 | "bun-types": "latest",
7 | "vitest": "^0.34.5"
8 | },
9 | "scripts": {
10 | "dev": "tsx index.ts",
11 | "build": "cd ../app-admin && npm run build && cp -r dist ../server/",
12 | "start": "NODE_ENV=production tsx index.ts",
13 | "test": "vitest"
14 | },
15 | "peerDependencies": {
16 | "typescript": "^5.0.0"
17 | },
18 | "dependencies": {
19 | "@coffeeandfun/google-profanity-words": "^2.1.0",
20 | "@libsql/client": "^0.3.5",
21 | "@trpc/server": "^10.38.5",
22 | "cors": "^2.8.5",
23 | "express": "^4.18.2",
24 | "fs-extra": "^11.1.1",
25 | "node-sql-parser": "^4.11.0",
26 | "situated": "^0.0.1",
27 | "trpc-yjs": "^0.0.6",
28 | "tsx": "^3.13.0",
29 | "ws": "^8.14.2",
30 | "yjs": "^13.6.8",
31 | "zod": "^3.22.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app-admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@trpc/client": "^10.38.5",
14 | "react": "^18.2.0",
15 | "react-aria-components": "1.0.0-beta.0",
16 | "react-dom": "^18.2.0",
17 | "situated": "^0.0.6",
18 | "timeago.js": "^4.0.2",
19 | "trpc-yjs": "^0.0.6"
20 | },
21 | "devDependencies": {
22 | "@types/react": "^18.2.15",
23 | "@types/react-dom": "^18.2.7",
24 | "@typescript-eslint/eslint-plugin": "^6.0.0",
25 | "@typescript-eslint/parser": "^6.0.0",
26 | "@vitejs/plugin-react-swc": "^3.3.2",
27 | "eslint": "^8.45.0",
28 | "eslint-plugin-react-hooks": "^4.6.0",
29 | "eslint-plugin-react-refresh": "^0.4.3",
30 | "typescript": "^5.0.2",
31 | "vite": "^4.4.5"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app-todos/app/routes/healthcheck.tsx:
--------------------------------------------------------------------------------
1 | // learn more: https://fly.io/docs/reference/configuration/#services-http_checks
2 | import type { LoaderFunctionArgs } from "@remix-run/node";
3 |
4 | import { getDb } from "~/db.server";
5 |
6 | export const loader = async ({ request }: LoaderFunctionArgs) => {
7 | const host =
8 | request.headers.get("X-Forwarded-Host") ?? request.headers.get("host");
9 |
10 | try {
11 | const db = getDb(`do-not-delete`)
12 | const url = new URL("/", `http://${host}`);
13 | // if we can connect to the database and make a simple query
14 | // and make a HEAD request to ourselves, then we're good.
15 | await Promise.all([
16 | db.execute(`select * from todo`),
17 | fetch(url.toString(), { method: "HEAD" }).then((r) => {
18 | if (!r.ok) return Promise.reject(r);
19 | }),
20 | ]);
21 | return new Response("OK");
22 | } catch (error: unknown) {
23 | console.log("healthcheck ❌", { error });
24 | return new Response("ERROR", { status: 500 });
25 | }
26 | };
27 |
28 |
--------------------------------------------------------------------------------
/app-todos/app/root.tsx:
--------------------------------------------------------------------------------
1 | import { cssBundleHref } from "@remix-run/css-bundle";
2 | import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node";
3 | import { json } from "@remix-run/node";
4 | import {
5 | Links,
6 | LiveReload,
7 | Meta,
8 | Outlet,
9 | Scripts,
10 | ScrollRestoration,
11 | } from "@remix-run/react";
12 |
13 | import stylesheet from "~/tailwind.css";
14 |
15 | export const links: LinksFunction = () => [
16 | { rel: "stylesheet", href: stylesheet },
17 | ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
18 | ];
19 |
20 | export default function App() {
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/app-todos/fly.toml:
--------------------------------------------------------------------------------
1 | # fly.toml app configuration file generated for app-server-todos-saas on 2023-09-26T15:51:05-07:00
2 | #
3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
4 | #
5 |
6 | app = "app-server-todos-saas"
7 | primary_region = "sea"
8 | kill_signal = "SIGINT"
9 | kill_timeout = "5s"
10 |
11 | [build]
12 |
13 | [[services]]
14 | protocol = "tcp"
15 | internal_port = 8080
16 | processes = ["app"]
17 |
18 | [[services.ports]]
19 | port = 80
20 | handlers = ["http"]
21 | force_https = true
22 |
23 | [[services.ports]]
24 | port = 443
25 | handlers = ["tls", "http"]
26 | [services.concurrency]
27 | type = "connections"
28 | hard_limit = 25
29 | soft_limit = 20
30 |
31 | [[services.tcp_checks]]
32 | interval = "15s"
33 | timeout = "2s"
34 | grace_period = "1s"
35 |
36 | [[services.http_checks]]
37 | interval = "10s"
38 | timeout = "2s"
39 | grace_period = "5s"
40 | method = "get"
41 | path = "/healthcheck"
42 | protocol = "http"
43 | tls_skip_verify = false
44 |
--------------------------------------------------------------------------------
/server/__snapshots__/mutators.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`serverConfig > clone 1`] = `
4 | [
5 | "url",
6 | "name",
7 | ]
8 | `;
9 |
10 | exports[`serverConfig > clone 2`] = `
11 | [
12 | "url",
13 | "authToken",
14 | "state",
15 | "name",
16 | "total",
17 | "completed",
18 | "updatedAt",
19 | ]
20 | `;
21 |
22 | exports[`serverConfig > createDb 1`] = `
23 | [
24 | "url",
25 | "name",
26 | ]
27 | `;
28 |
29 | exports[`serverConfig > createDb 2`] = `
30 | [
31 | "url",
32 | "authToken",
33 | "state",
34 | "name",
35 | "total",
36 | "completed",
37 | "updatedAt",
38 | ]
39 | `;
40 |
41 | exports[`serverConfig > deleteDb 1`] = `
42 | [
43 | "name",
44 | ]
45 | `;
46 |
47 | exports[`serverConfig > deleteDb 2`] = `
48 | [
49 | "name",
50 | ]
51 | `;
52 |
53 | exports[`update a robot name 1`] = `
54 | {
55 | "done": true,
56 | "error": false,
57 | "mutator": "updateRobotName",
58 | "request": {
59 | "id": "123",
60 | "name": "beep",
61 | },
62 | "response": {
63 | "ok": true,
64 | },
65 | "value": "responded",
66 | }
67 | `;
68 |
--------------------------------------------------------------------------------
/app-todos/Dockerfile:
--------------------------------------------------------------------------------
1 | # base node image
2 | FROM node:20-bullseye-slim as base
3 |
4 | # set for base and all layer that inherit from it
5 | ENV NODE_ENV production
6 |
7 | # Install all node_modules, including dev dependencies
8 | FROM base as deps
9 |
10 | WORKDIR /myapp
11 |
12 | ADD package.json package-lock.json .npmrc ./
13 | RUN npm install --include=dev
14 |
15 | # Setup production node_modules
16 | FROM base as production-deps
17 |
18 | WORKDIR /myapp
19 |
20 | COPY --from=deps /myapp/node_modules /myapp/node_modules
21 | ADD package.json package-lock.json .npmrc ./
22 | RUN npm prune --omit=dev
23 |
24 | # Build the app
25 | FROM base as build
26 |
27 | WORKDIR /myapp
28 |
29 | COPY --from=deps /myapp/node_modules /myapp/node_modules
30 |
31 | ADD . .
32 | RUN npm run build
33 |
34 | # Finally, build the production image with minimal footprint
35 | FROM base
36 |
37 | ENV PORT="8080"
38 | ENV NODE_ENV="production"
39 |
40 | WORKDIR /myapp
41 |
42 | COPY --from=production-deps /myapp/node_modules /myapp/node_modules
43 |
44 | COPY --from=build /myapp/build /myapp/build
45 | COPY --from=build /myapp/public /myapp/public
46 | COPY --from=build /myapp/package.json /myapp/package.json
47 |
48 | CMD [ "npm", "run", "start" ]
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/app-admin/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 | a:hover {
23 | color: #535bf2;
24 | }
25 |
26 | h1 {
27 | font-size: 3.2em;
28 | line-height: 1.1;
29 | }
30 |
31 | button {
32 | border-radius: 8px;
33 | border: 1px solid transparent;
34 | padding: 0.6em 1.2em;
35 | font-size: 1em;
36 | font-weight: 500;
37 | font-family: inherit;
38 | background-color: #1a1a1a;
39 | cursor: pointer;
40 | transition: border-color 0.25s;
41 | }
42 | button:hover {
43 | border-color: #646cff;
44 | }
45 | button:focus,
46 | button:focus-visible {
47 | outline: 4px auto -webkit-focus-ring-color;
48 | }
49 |
50 | @media (prefers-color-scheme: light) {
51 | :root {
52 | color: #213547;
53 | background-color: #ffffff;
54 | }
55 | a:hover {
56 | color: #747bff;
57 | }
58 | button {
59 | background-color: #f9f9f9;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/machines/client.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid"
2 |
3 | export async function createRequest({ doc, mutator, request }) {
4 | const requests = doc.getArray(`requests`)
5 | const requestId = uuidv4()
6 | let resolveFunc, rejectFunc
7 |
8 | const promise = new Promise((resolve, reject) => {
9 | // Storing the resolve and reject functions for later use.
10 | resolveFunc = resolve
11 | rejectFunc = reject
12 | })
13 | function observe(event) {
14 | const inserted = event.changes.delta.find(
15 | (item) => Object.keys(item)[0] === `insert`
16 | )
17 | const state = inserted.insert[0]
18 | // TODO only do this in case of user-directed timeout or network
19 | // disconnect errors e.g. normally we're fine just waiting to go online
20 | // but the app might want to error immediately if we disconnect or are offline.
21 | // if (state.error) {
22 | // requests.unobserve(observe)
23 | // return rejectFunc(state)
24 | // }
25 | if (state.done) {
26 | requests.unobserve(observe)
27 | return resolveFunc(state)
28 | }
29 | }
30 |
31 | requests.observe(observe)
32 | requests.push([
33 | {
34 | mutator,
35 | value: `requested`,
36 | id: requestId,
37 | done: false,
38 | clientCreate: new Date().toJSON(),
39 | request,
40 | response: {},
41 | },
42 | ])
43 |
44 | return promise
45 | }
46 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # base node image
2 | FROM node:20-bullseye-slim as base
3 | SHELL ["/bin/bash", "-c"]
4 | ENV PNPM_HOME="/pnpm"
5 | ENV PATH="$PNPM_HOME:$PATH"
6 | RUN corepack enable
7 | ENV TURSO_AUTH_TOKEN=$TURSO_AUTH_TOKEN
8 |
9 |
10 | # set for base and all layer that inherit from it
11 | ENV NODE_ENV production
12 |
13 | # Setup production node_modules
14 | FROM base as app
15 |
16 | WORKDIR /root-app/app
17 |
18 | ADD app-admin/ .
19 | ADD machines ../machines
20 | ENV NODE_ENV=development
21 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
22 | ENV NODE_ENV=production
23 | RUN pnpm build
24 |
25 | # Install all node_modules, including dev dependencies
26 | FROM base as serverDeps
27 |
28 | RUN apt-get update \
29 | && apt-get install -y curl \
30 | && rm -rf /var/lib/apt/lists/*
31 |
32 | RUN curl -sSfL https://gist.githubusercontent.com/KyleAMathews/e7678b4d13adb24e6b5331a89e3b30a8/raw/d3a1b1ee70313b30e92fa092ce1b330fad8be711/install-turso.sh | bash
33 | RUN source /root/.bashrc
34 | RUN /root/.turso/turso --version
35 |
36 | WORKDIR /root-app/server
37 |
38 | ADD server/ .
39 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
40 | ADD machines ../machines
41 | add server/map-sqlite-resultset.ts package.json ..
42 |
43 | COPY --from=app /root-app/app/dist dist
44 |
45 | ENTRYPOINT ["/bin/bash", "-c", "source /root/.bashrc && npm run start"]
46 | # CMD [ "npm", "run", "start" ]
47 |
--------------------------------------------------------------------------------
/app-todos/app/db.server.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from "@libsql/client";
2 | import { singleton } from "./singleton.server";
3 | import * as Y from "yjs";
4 | import { WebsocketProvider } from "y-websocket";
5 | import { mapResultSet } from "./map-sqlite-resultset";
6 |
7 | console.log(`getting ydoc`);
8 |
9 | console.log({
10 | url: `file:admin.db`,
11 | syncUrl: process.env.TURSO_URL,
12 | authToken: process.env.TURSO_AUTH_TOKEN,
13 | })
14 |
15 | const adminDb = createClient({
16 | url: `file:admin.db`,
17 | syncUrl: process.env.TURSO_URL,
18 | authToken: process.env.TURSO_AUTH_TOKEN,
19 | });
20 |
21 | const syncedForDbs = new Set();
22 | const dbs = new Map();
23 | async function syncAdminDb(dbName) {
24 | if (!syncedForDbs.has(dbName)) {
25 | console.time(`sync db from turso ${dbName}`);
26 | await adminDb.sync();
27 | console.timeEnd(`sync db from turso ${dbName}`);
28 | syncedForDbs.add(dbName);
29 | }
30 |
31 | const results = await adminDb.execute(`SELECT * from dbs`);
32 | const dbRows = mapResultSet(results);
33 | dbRows.forEach((dbInfo) => {
34 | dbs.set(dbInfo.name, dbInfo);
35 | });
36 | }
37 |
38 | syncAdminDb(`initial`);
39 |
40 | const getDb = (dbName) =>
41 | singleton(`getDb${dbName}`, () => {
42 | console.log(`get db singleton ${dbName}`);
43 | const db = dbs.get(dbName);
44 | return createClient({ url: db.url, authToken: db.authToken });
45 | });
46 |
47 | export { getDb, syncAdminDb };
48 |
--------------------------------------------------------------------------------
/app-admin/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app-todos/app/utils.ts:
--------------------------------------------------------------------------------
1 | import { useMatches } from "@remix-run/react";
2 | import { useMemo } from "react";
3 |
4 | import type { User } from "~/models/user.server";
5 |
6 | const DEFAULT_REDIRECT = "/";
7 |
8 | /**
9 | * This should be used any time the redirect path is user-provided
10 | * (Like the query string on our login/signup pages). This avoids
11 | * open-redirect vulnerabilities.
12 | * @param {string} to The redirect destination
13 | * @param {string} defaultRedirect The redirect to use if the to is unsafe.
14 | */
15 | export function safeRedirect(
16 | to: FormDataEntryValue | string | null | undefined,
17 | defaultRedirect: string = DEFAULT_REDIRECT,
18 | ) {
19 | if (!to || typeof to !== "string") {
20 | return defaultRedirect;
21 | }
22 |
23 | if (!to.startsWith("/") || to.startsWith("//")) {
24 | return defaultRedirect;
25 | }
26 |
27 | return to;
28 | }
29 |
30 | /**
31 | * This base hook is used in other hooks to quickly search for specific data
32 | * across all loader data using useMatches.
33 | * @param {string} id The route id
34 | * @returns {JSON|undefined} The router data or undefined if not found
35 | */
36 | export function useMatchesData(
37 | id: string,
38 | ): Record | undefined {
39 | const matchingRoutes = useMatches();
40 | const route = useMemo(
41 | () => matchingRoutes.find((route) => route.id === id),
42 | [matchingRoutes, id],
43 | );
44 | return route?.data as Record;
45 | }
46 |
--------------------------------------------------------------------------------
/app-todos/.gitpod.yml:
--------------------------------------------------------------------------------
1 | # https://www.gitpod.io/docs/config-gitpod-file
2 |
3 | image:
4 | file: .gitpod.Dockerfile
5 |
6 | ports:
7 | - port: 3000
8 | onOpen: notify
9 |
10 | tasks:
11 | - name: Restore .env file
12 | command: |
13 | if [ -f .env ]; then
14 | # If this workspace already has a .env, don't override it
15 | # Local changes survive a workspace being opened and closed
16 | # but they will not persist between separate workspaces for the same repo
17 |
18 | echo "Found .env in workspace"
19 | else
20 | # There is no .env
21 | if [ ! -n "${ENV}" ]; then
22 | # There is no $ENV from a previous workspace
23 | # Default to the example .env
24 | echo "Setting example .env"
25 |
26 | cp .env.example .env
27 | else
28 | # After making changes to .env, run this line to persist it to $ENV
29 | # eval $(gp env -e ENV="$(base64 .env | tr -d '\n')")
30 | #
31 | # Environment variables set this way are shared between all your workspaces for this repo
32 | # The lines below will read $ENV and print a .env file
33 |
34 | echo "Restoring .env from Gitpod"
35 |
36 | echo "${ENV}" | base64 -d | tee .env > /dev/null
37 | fi
38 | fi
39 |
40 | - init: npm install
41 | command: npm run setup && npm run dev
42 |
43 | vscode:
44 | extensions:
45 | - ms-azuretools.vscode-docker
46 | - esbenp.prettier-vscode
47 | - dbaeumer.vscode-eslint
48 | - bradlc.vscode-tailwindcss
49 |
--------------------------------------------------------------------------------
/machines/server.ts:
--------------------------------------------------------------------------------
1 | import * as Y from "yjs"
2 |
3 | const isServerSync = true
4 | export function listen({ doc, serverConfig }) {
5 | const requests = doc.getArray(`requests`)
6 | requests.observe(async (event) => {
7 | const inserted = event.changes.delta.find(
8 | (item) => Object.keys(item)[0] === `insert`
9 | )
10 | const retain = event.changes.delta.find(
11 | (item) => Object.keys(item)[0] === `retain`
12 | )
13 | const itemArray = retain?.retain || 0
14 | const state = event.target.get(itemArray)
15 | console.log({ state })
16 | if (state.done !== true) {
17 | if (
18 | serverConfig.mutators.hasOwnProperty(state.mutator) &&
19 | serverConfig.mutators[state.mutator] instanceof Function
20 | ) {
21 | const mutatorFunc = await serverConfig.mutators[state.mutator]({
22 | state,
23 | doc,
24 | })
25 | doc.transact(() => {
26 | state.response = mutatorFunc()
27 |
28 | if (typeof state.response?.error !== `undefined`) {
29 | state.error = true
30 | } else {
31 | state.error = false
32 | }
33 |
34 | state.value = `responded`
35 | state.done = true
36 | state.serverResponded = new Date().toJSON()
37 | requests.delete(itemArray, 1)
38 | requests.insert(itemArray, [state])
39 | })
40 | } else {
41 | console.log(`bad request`)
42 | doc.transact(() => {
43 | state.error = true
44 | state.response = {
45 | error: `A mutator by that name does not exist`,
46 | }
47 | })
48 | }
49 | }
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 | admin.db
132 | admin.db-shm
133 | admin.db-wal
134 |
--------------------------------------------------------------------------------
/app-todos/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app-todos2-d042",
3 | "private": true,
4 | "sideEffects": false,
5 | "scripts": {
6 | "build": "remix build",
7 | "dev": "PORT=10000 remix dev -c \"npm run dev:serve\"",
8 | "dev:serve": "binode -- @remix-run/serve:remix-serve ./build/index.js",
9 | "format": "prettier --write .",
10 | "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
11 | "setup": "prisma generate && prisma migrate deploy && prisma db seed",
12 | "start": "remix-serve ./build/index.js",
13 | "test": "vitest",
14 | "test:e2e:dev": "start-server-and-test dev http://localhost:3000 \"npx cypress open\"",
15 | "pretest:e2e:run": "npm run build",
16 | "test:e2e:run": "cross-env PORT=8811 start-server-and-test http://localhost:8811 \"npx cypress run\"",
17 | "typecheck": "tsc && tsc -p cypress",
18 | "validate": "run-p \"test -- --run\" lint typecheck test:e2e:run"
19 | },
20 | "prettier": {},
21 | "eslintIgnore": [
22 | "/node_modules",
23 | "/build",
24 | "/public/build"
25 | ],
26 | "dependencies": {
27 | "@libsql/client": "^0.3.5",
28 | "@prisma/client": "^4.16.2",
29 | "@remix-run/css-bundle": "^2.0.0",
30 | "@remix-run/node": "^2.0.0",
31 | "@remix-run/react": "^2.0.0",
32 | "@remix-run/serve": "^2.0.0",
33 | "bcryptjs": "^2.4.3",
34 | "isbot": "^3.6.13",
35 | "react": "^18.2.0",
36 | "react-dom": "^18.2.0",
37 | "tiny-invariant": "^1.3.1",
38 | "ws": "^8.14.2",
39 | "y-websocket": "^1.5.0",
40 | "yjs": "^13.6.8"
41 | },
42 | "devDependencies": {
43 | "@faker-js/faker": "^8.0.2",
44 | "@remix-run/dev": "^2.0.0",
45 | "@remix-run/eslint-config": "^2.0.0",
46 | "@testing-library/cypress": "^9.0.0",
47 | "@testing-library/jest-dom": "^5.17.0",
48 | "@types/bcryptjs": "^2.4.2",
49 | "@types/eslint": "^8.44.2",
50 | "@types/node": "^18.17.6",
51 | "@types/react": "^18.2.20",
52 | "@types/react-dom": "^18.2.7",
53 | "@vitejs/plugin-react": "^4.0.4",
54 | "@vitest/coverage-v8": "^0.34.2",
55 | "autoprefixer": "^10.4.15",
56 | "binode": "^1.0.5",
57 | "cookie": "^0.5.0",
58 | "cross-env": "^7.0.3",
59 | "cypress": "12.17.3",
60 | "eslint": "^8.47.0",
61 | "eslint-config-prettier": "^9.0.0",
62 | "eslint-plugin-cypress": "^2.14.0",
63 | "happy-dom": "^10.10.4",
64 | "msw": "^1.2.3",
65 | "npm-run-all": "^4.1.5",
66 | "postcss": "^8.4.28",
67 | "prettier": "3.0.2",
68 | "prettier-plugin-tailwindcss": "^0.5.3",
69 | "start-server-and-test": "^2.0.0",
70 | "tailwindcss": "^3.3.3",
71 | "ts-node": "^10.9.1",
72 | "tsconfig-paths": "^4.2.0",
73 | "typescript": "^5.1.6",
74 | "vite": "^4.4.9",
75 | "vite-tsconfig-paths": "^3.6.0",
76 | "vitest": "^0.34.2"
77 | },
78 | "engines": {
79 | "node": ">=18.0.0"
80 | },
81 | "prisma": {
82 | "seed": "ts-node --require tsconfig-paths/register prisma/seed.ts"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | # Logs
4 |
5 | logs
6 | _.log
7 | npm-debug.log_
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | .pnpm-debug.log*
12 |
13 | # Diagnostic reports (https://nodejs.org/api/report.html)
14 |
15 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
16 |
17 | # Runtime data
18 |
19 | pids
20 | _.pid
21 | _.seed
22 | \*.pid.lock
23 |
24 | # Directory for instrumented libs generated by jscoverage/JSCover
25 |
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 |
30 | coverage
31 | \*.lcov
32 |
33 | # nyc test coverage
34 |
35 | .nyc_output
36 |
37 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
38 |
39 | .grunt
40 |
41 | # Bower dependency directory (https://bower.io/)
42 |
43 | bower_components
44 |
45 | # node-waf configuration
46 |
47 | .lock-wscript
48 |
49 | # Compiled binary addons (https://nodejs.org/api/addons.html)
50 |
51 | build/Release
52 |
53 | # Dependency directories
54 |
55 | node_modules/
56 | jspm_packages/
57 |
58 | # Snowpack dependency directory (https://snowpack.dev/)
59 |
60 | web_modules/
61 |
62 | # TypeScript cache
63 |
64 | \*.tsbuildinfo
65 |
66 | # Optional npm cache directory
67 |
68 | .npm
69 |
70 | # Optional eslint cache
71 |
72 | .eslintcache
73 |
74 | # Optional stylelint cache
75 |
76 | .stylelintcache
77 |
78 | # Microbundle cache
79 |
80 | .rpt2_cache/
81 | .rts2_cache_cjs/
82 | .rts2_cache_es/
83 | .rts2_cache_umd/
84 |
85 | # Optional REPL history
86 |
87 | .node_repl_history
88 |
89 | # Output of 'npm pack'
90 |
91 | \*.tgz
92 |
93 | # Yarn Integrity file
94 |
95 | .yarn-integrity
96 |
97 | # dotenv environment variable files
98 |
99 | .env
100 | .env.development.local
101 | .env.test.local
102 | .env.production.local
103 | .env.local
104 |
105 | # parcel-bundler cache (https://parceljs.org/)
106 |
107 | .cache
108 | .parcel-cache
109 |
110 | # Next.js build output
111 |
112 | .next
113 | out
114 |
115 | # Nuxt.js build / generate output
116 |
117 | .nuxt
118 | dist
119 |
120 | # Gatsby files
121 |
122 | .cache/
123 |
124 | # Comment in the public line in if your project uses Gatsby and not Next.js
125 |
126 | # https://nextjs.org/blog/next-9-1#public-directory-support
127 |
128 | # public
129 |
130 | # vuepress build output
131 |
132 | .vuepress/dist
133 |
134 | # vuepress v2.x temp and cache directory
135 |
136 | .temp
137 | .cache
138 |
139 | # Docusaurus cache and generated files
140 |
141 | .docusaurus
142 |
143 | # Serverless directories
144 |
145 | .serverless/
146 |
147 | # FuseBox cache
148 |
149 | .fusebox/
150 |
151 | # DynamoDB Local files
152 |
153 | .dynamodb/
154 |
155 | # TernJS port file
156 |
157 | .tern-port
158 |
159 | # Stores VSCode versions used for testing VSCode extensions
160 |
161 | .vscode-test
162 |
163 | # yarn v2
164 |
165 | .yarn/cache
166 | .yarn/unplugged
167 | .yarn/build-state.yml
168 | .yarn/install-state.gz
169 | .pnp.\*
170 |
171 | # IntelliJ based IDEs
172 | .idea
173 |
--------------------------------------------------------------------------------
/app-admin/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 1rem;
5 | }
6 |
7 | .logo {
8 | height: 6em;
9 | padding: 1.5em;
10 | will-change: filter;
11 | transition: filter 300ms;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.react:hover {
17 | filter: drop-shadow(0 0 2em #61dafbaa);
18 | }
19 |
20 | @keyframes logo-spin {
21 | from {
22 | transform: rotate(0deg);
23 | }
24 | to {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @media (prefers-reduced-motion: no-preference) {
30 | a:nth-of-type(2) .logo {
31 | animation: logo-spin infinite 20s linear;
32 | }
33 | }
34 |
35 | .card {
36 | padding: 2em;
37 | }
38 |
39 | .read-the-docs {
40 | color: #888;
41 | }
42 |
43 | .react-aria-ModalOverlay {
44 | position: fixed;
45 | top: 0;
46 | left: 0;
47 | width: 100vw;
48 | height: var(--visual-viewport-height);
49 | background: rgba(0 0 0 / 0.5);
50 | display: flex;
51 | align-items: center;
52 | justify-content: center;
53 |
54 | &[data-entering] {
55 | animation: fade 200ms;
56 | }
57 |
58 | &[data-exiting] {
59 | animation: fade 150ms reverse ease-in;
60 | }
61 | }
62 |
63 | .react-aria-Modal {
64 | box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
65 | border-radius: 6px;
66 | background: white;
67 | border: 1px solid var(--spectrum-global-color-gray-300);
68 | outline: none;
69 | padding: 30px;
70 | width: 90vw;
71 |
72 | &[data-entering] {
73 | animation: zoom 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
74 | }
75 | }
76 |
77 | @keyframes fade {
78 | from {
79 | opacity: 0;
80 | }
81 |
82 | to {
83 | opacity: 1;
84 | }
85 | }
86 |
87 | @keyframes zoom {
88 | from {
89 | transform: scale(0.8);
90 | }
91 |
92 | to {
93 | transform: scale(1);
94 | }
95 | }
96 |
97 | .react-aria-Dialog {
98 | outline: none;
99 |
100 | .react-aria-Heading {
101 | line-height: 1em;
102 | margin-top: 0;
103 | }
104 |
105 | .react-aria-Button {
106 | margin-top: 20px;
107 | }
108 | }
109 |
110 | .react-aria-Button {
111 | background: var(--spectrum-global-color-gray-50);
112 | border: 1px solid var(--spectrum-global-color-gray-400);
113 | border-radius: 4px;
114 | color: var(--spectrum-alias-text-color);
115 | appearance: none;
116 | vertical-align: middle;
117 | font-size: 1rem;
118 | text-align: center;
119 | margin: 0;
120 | outline: none;
121 | padding: 6px 10px;
122 | transition: border-color 200ms;
123 |
124 | &[data-hovered] {
125 | border-color: var(--spectrum-global-color-gray-500);
126 | }
127 |
128 | &[data-pressed] {
129 | box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
130 | background: var(--spectrum-global-color-gray-100);
131 | border-color: var(--spectrum-global-color-gray-600);
132 | }
133 |
134 | &[data-focus-visible] {
135 | border-color: slateblue;
136 | box-shadow: 0 0 0 1px slateblue;
137 | }
138 | }
139 |
140 | .react-aria-TextField {
141 | margin-bottom: 8px;
142 |
143 | .react-aria-Label {
144 | display: inline-block;
145 | width: 5.357rem;
146 | }
147 |
148 | .react-aria-Input {
149 | font-size: 16px;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/app-todos/app/entry.server.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * By default, Remix will handle generating the HTTP Response for you.
3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4 | * For more information, see https://remix.run/docs/en/main/file-conventions/entry.server
5 | */
6 |
7 | import { PassThrough } from "node:stream";
8 |
9 | import type { EntryContext } from "@remix-run/node";
10 | import { createReadableStreamFromReadable } from "@remix-run/node";
11 | import { RemixServer } from "@remix-run/react";
12 | import isbot from "isbot";
13 | import { renderToPipeableStream } from "react-dom/server";
14 |
15 | const ABORT_DELAY = 5_000;
16 |
17 | export default function handleRequest(
18 | request: Request,
19 | responseStatusCode: number,
20 | responseHeaders: Headers,
21 | remixContext: EntryContext,
22 | ) {
23 | return isbot(request.headers.get("user-agent"))
24 | ? handleBotRequest(
25 | request,
26 | responseStatusCode,
27 | responseHeaders,
28 | remixContext,
29 | )
30 | : handleBrowserRequest(
31 | request,
32 | responseStatusCode,
33 | responseHeaders,
34 | remixContext,
35 | );
36 | }
37 |
38 | function handleBotRequest(
39 | request: Request,
40 | responseStatusCode: number,
41 | responseHeaders: Headers,
42 | remixContext: EntryContext,
43 | ) {
44 | return new Promise((resolve, reject) => {
45 | const { abort, pipe } = renderToPipeableStream(
46 | ,
51 | {
52 | onAllReady() {
53 | const body = new PassThrough();
54 |
55 | responseHeaders.set("Content-Type", "text/html");
56 |
57 | resolve(
58 | new Response(createReadableStreamFromReadable(body), {
59 | headers: responseHeaders,
60 | status: responseStatusCode,
61 | }),
62 | );
63 |
64 | pipe(body);
65 | },
66 | onShellError(error: unknown) {
67 | reject(error);
68 | },
69 | onError(error: unknown) {
70 | responseStatusCode = 500;
71 | console.error(error);
72 | },
73 | },
74 | );
75 |
76 | setTimeout(abort, ABORT_DELAY);
77 | });
78 | }
79 |
80 | function handleBrowserRequest(
81 | request: Request,
82 | responseStatusCode: number,
83 | responseHeaders: Headers,
84 | remixContext: EntryContext,
85 | ) {
86 | return new Promise((resolve, reject) => {
87 | const { abort, pipe } = renderToPipeableStream(
88 | ,
93 | {
94 | onShellReady() {
95 | const body = new PassThrough();
96 |
97 | responseHeaders.set("Content-Type", "text/html");
98 |
99 | resolve(
100 | new Response(createReadableStreamFromReadable(body), {
101 | headers: responseHeaders,
102 | status: responseStatusCode,
103 | }),
104 | );
105 |
106 | pipe(body);
107 | },
108 | onShellError(error: unknown) {
109 | reject(error);
110 | },
111 | onError(error: unknown) {
112 | console.error(error);
113 | responseStatusCode = 500;
114 | },
115 | },
116 | );
117 |
118 | setTimeout(abort, ABORT_DELAY);
119 | });
120 | }
121 |
--------------------------------------------------------------------------------
/server/index.ts:
--------------------------------------------------------------------------------
1 | import cors from "cors"
2 | import path from "path"
3 | import express from "express"
4 | import { WebSocketServer } from "ws"
5 | import { fileURLToPath } from "url"
6 | import fs from "fs-extra"
7 | import { setupWSConnection, getYDoc } from "situated"
8 | import listen from "../machines/server"
9 | import { mapResultSet } from "./map-sqlite-resultset"
10 | import Parser from "node-sql-parser"
11 | import { serverConfig } from "./mutators"
12 | import { createClient } from "@libsql/client"
13 | import { adapter } from "trpc-yjs/adapter"
14 | import { appRouter } from "./trpc"
15 |
16 | const __filename = fileURLToPath(import.meta.url)
17 | const __dirname = path.dirname(__filename)
18 |
19 | const baseDir =
20 | process.env.BASE_DATA_DIR || path.resolve(process.cwd(), `.cache`)
21 | const file = path.join(baseDir, `db.json`)
22 | const dbsDir = path.join(baseDir, `dbs`)
23 | fs.ensureDirSync(path.dirname(file))
24 | fs.ensureDirSync(dbsDir)
25 |
26 | const app = express()
27 | app.use(express.json())
28 | app.use(
29 | cors({
30 | origin: `http://localhost:5174`,
31 | credentials: true,
32 | })
33 | )
34 |
35 | // Serve static assets.
36 | app.use(`/`, express.static(path.join(__dirname, `./dist`)))
37 |
38 | // handle every other route with index.html, which will contain
39 | // a script tag to your application's JavaScript file(s).
40 | app.get(`*`, function (request, response) {
41 | response.sendFile(path.resolve(__dirname, `../dist/index.html`))
42 | })
43 |
44 | app.post(`/invalidate/:dbName`, async (req, res) => {
45 | const dbName = req.params.dbName
46 |
47 | const doc = getYDoc(`app-doc`)
48 | const dbs = doc.getMap(`dbs`)
49 |
50 | if (dbs.has(dbName)) {
51 | const ydocDb = dbs.get(dbName)
52 | // Query for total + completed and set.
53 | const db = createClient({ url: ydocDb.url, authToken: ydocDb.authToken })
54 | const totals = mapResultSet(
55 | await db.execute(
56 | `select completed, count(*) as count from todo group by completed`
57 | )
58 | )
59 | doc.transact(() => {
60 | ydocDb.total = totals.map((row) => row.count).reduce((a, b) => a + b, 0)
61 | ydocDb.completed = totals.find((row) => row.completed === 1)?.count || 0
62 | ydocDb.updatedAt = new Date().toJSON()
63 | dbs.set(dbName, ydocDb)
64 | })
65 | res.send(`ok`)
66 | }
67 | })
68 |
69 | const wsServer = new WebSocketServer({ noServer: true })
70 | wsServer.on(`connection`, setupWSConnection)
71 |
72 | const port = 3000
73 |
74 | const server = app.listen(port, async () => {
75 | console.log(`API listening on port ${port}`)
76 | const doc = getYDoc(`app-doc`)
77 | console.log(`got doc`)
78 | // const { context, mutators } = serverConfig({
79 | // adminUrl: process.env.TURSO_URL,
80 | // adminAuthToken: process.env.TURSO_ADMIN_DB_AUTH_TOKEN,
81 | // })
82 |
83 | // console.log({ db: context.adminDb })
84 |
85 | const adminDb = createClient({
86 | url: process.env.TURSO_URL,
87 | authToken: process.env.TURSO_ADMIN_DB_AUTH_TOKEN,
88 | })
89 | const dbsResult = mapResultSet(await adminDb.execute(`select * from dbs`))
90 |
91 | const dbs = doc.getMap(`dbs`)
92 | dbsResult.map((db) => {
93 | const yjsDb = dbs.has(db.name) ? dbs.get(db.name) : {}
94 | const combined = { ...db, ...yjsDb }
95 | dbs.set(combined.name, combined)
96 | })
97 |
98 | // Start adapter
99 | console.log(`starting the adapter`)
100 | adapter({
101 | doc,
102 | appRouter,
103 | context: { doc, adminDb },
104 | onError: (e) => console.log(`error`, e),
105 | })
106 |
107 | // listen.listen({
108 | // doc,
109 | // serverConfig: { context, mutators },
110 | // })
111 | })
112 |
113 | server.on(`upgrade`, (request, socket, head) => {
114 | wsServer.handleUpgrade(request, socket, head, (socket) => {
115 | wsServer.emit(`connection`, socket, request)
116 | })
117 | })
118 |
--------------------------------------------------------------------------------
/app-admin/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app-todos/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: 🚀 Deploy
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - dev
8 | pull_request:
9 |
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.ref }}
12 | cancel-in-progress: true
13 |
14 | permissions:
15 | actions: write
16 | contents: read
17 |
18 | jobs:
19 | lint:
20 | name: ⬣ ESLint
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: ⬇️ Checkout repo
24 | uses: actions/checkout@v4
25 |
26 | - name: ⎔ Setup node
27 | uses: actions/setup-node@v3
28 | with:
29 | cache: npm
30 | cache-dependency-path: ./package.json
31 | node-version: 18
32 |
33 | - name: 📥 Install deps
34 | run: npm install
35 |
36 | - name: 🔬 Lint
37 | run: npm run lint
38 |
39 | typecheck:
40 | name: ʦ TypeScript
41 | runs-on: ubuntu-latest
42 | steps:
43 | - name: ⬇️ Checkout repo
44 | uses: actions/checkout@v4
45 |
46 | - name: ⎔ Setup node
47 | uses: actions/setup-node@v3
48 | with:
49 | cache: npm
50 | cache-dependency-path: ./package.json
51 | node-version: 18
52 |
53 | - name: 📥 Install deps
54 | run: npm install
55 |
56 | - name: 🔎 Type check
57 | run: npm run typecheck --if-present
58 |
59 | vitest:
60 | name: ⚡ Vitest
61 | runs-on: ubuntu-latest
62 | steps:
63 | - name: ⬇️ Checkout repo
64 | uses: actions/checkout@v4
65 |
66 | - name: ⎔ Setup node
67 | uses: actions/setup-node@v3
68 | with:
69 | cache: npm
70 | cache-dependency-path: ./package.json
71 | node-version: 18
72 |
73 | - name: 📥 Install deps
74 | run: npm install
75 |
76 | - name: ⚡ Run vitest
77 | run: npm run test -- --coverage
78 |
79 | cypress:
80 | name: ⚫️ Cypress
81 | runs-on: ubuntu-latest
82 | steps:
83 | - name: ⬇️ Checkout repo
84 | uses: actions/checkout@v4
85 |
86 | - name: 🏄 Copy test env vars
87 | run: cp .env.example .env
88 |
89 | - name: ⎔ Setup node
90 | uses: actions/setup-node@v3
91 | with:
92 | cache: npm
93 | cache-dependency-path: ./package.json
94 | node-version: 18
95 |
96 | - name: 📥 Install deps
97 | run: npm install
98 |
99 | - name: 🛠 Setup Database
100 | run: npx prisma migrate reset --force
101 |
102 | - name: ⚙️ Build
103 | run: npm run build
104 |
105 | - name: 🌳 Cypress run
106 | uses: cypress-io/github-action@v6
107 | with:
108 | start: npm run start:mocks
109 | wait-on: http://localhost:8811
110 | env:
111 | PORT: 8811
112 |
113 | deploy:
114 | name: 🚀 Deploy
115 | runs-on: ubuntu-latest
116 | needs: [lint, typecheck, vitest, cypress]
117 | # only deploy main/dev branch on pushes
118 | if: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && github.event_name == 'push' }}
119 |
120 | steps:
121 | - name: ⬇️ Checkout repo
122 | uses: actions/checkout@v4
123 |
124 | - name: 👀 Read app name
125 | uses: SebRollen/toml-action@v1.0.2
126 | id: app_name
127 | with:
128 | file: fly.toml
129 | field: app
130 |
131 | - name: 🎈 Setup Fly
132 | uses: superfly/flyctl-actions/setup-flyctl@v1.4
133 |
134 | - name: 🚀 Deploy Staging
135 | if: ${{ github.ref == 'refs/heads/dev' }}
136 | run: flyctl deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }} --app ${{ steps.app_name.outputs.value }}-staging
137 | env:
138 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
139 |
140 | - name: 🚀 Deploy Production
141 | if: ${{ github.ref == 'refs/heads/main' }}
142 | run: flyctl deploy --remote-only --build-arg COMMIT_SHA=${{ github.sha }} --app ${{ steps.app_name.outputs.value }}
143 | env:
144 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
145 |
--------------------------------------------------------------------------------
/app-todos/app/routes/todos.$todosId.tsx:
--------------------------------------------------------------------------------
1 | import type { LoaderFunctionArgs } from "@remix-run/node";
2 | import { json } from "@remix-run/node";
3 | import {
4 | Form,
5 | Link,
6 | NavLink,
7 | Outlet,
8 | useParams,
9 | useLoaderData,
10 | useNavigation,
11 | } from "@remix-run/react";
12 | import { useRef, useEffect } from "react";
13 | import { getDb, syncAdminDb } from "~/db.server";
14 | import { mapResultSet } from "../map-sqlite-resultset";
15 |
16 | export const loader = async ({ params, request }: LoaderFunctionArgs) => {
17 | // This will only sync the first time.
18 | await syncAdminDb(params.todosId)
19 |
20 | const db = getDb(params.todosId);
21 | const todos: Todo[] = mapResultSet(await db.execute(`select * from todo`));
22 | console.log({ todos });
23 | return json({
24 | todos: todos.map((todo) => {
25 | return { ...todo, completed: todo.completed === 1 };
26 | }),
27 | });
28 | };
29 |
30 | const urlBase = process.env.NODE_ENV === `production` ? `https://admin-todos-saas.fly.dev/` : `http://localhost:3000/`
31 |
32 | export let action: V2_ActionFunction = async ({ params, request }) => {
33 | const dbName = params.todosId;
34 | const db = getDb(params.todosId);
35 | if (request.method === "POST") {
36 | const data = new URLSearchParams(await request.text());
37 | const title = data.get("title") ?? "";
38 | const res = await db.execute({
39 | sql: `INSERT INTO Todo (title, completed) VALUES (?, 0)`,
40 | args: [title],
41 | });
42 | console.log({ res });
43 | await fetch(`${urlBase}invalidate/${params.todosId}`, {
44 | method: `post`,
45 | });
46 | return json(res, {
47 | status: 201,
48 | });
49 | }
50 | if (request.method === "PUT") {
51 | const data = new URLSearchParams(await request.text());
52 | const todoId = data.get("completed");
53 | console.log(todoId);
54 | if (!todoId)
55 | return json(
56 | { error: "Todo id must be defined" },
57 | {
58 | status: 400,
59 | }
60 | );
61 | const todo = mapResultSet(await db.execute({
62 | sql: `SELECT * from Todo where id = ?`,
63 | args: [todoId],
64 | }))[0]
65 | console.log(todo);
66 | if (!todo) {
67 | return json(
68 | { error: "Todo does not exist" },
69 | {
70 | status: 400,
71 | }
72 | );
73 | }
74 | await db.execute({
75 | sql: `UPDATE TODO set completed=? where id = ?`,
76 | args: [todo.completed === 1 ? 0 : 1, todoId],
77 | });
78 |
79 |
80 | await fetch(`${urlBase}invalidate/${params.todosId}`, {
81 | method: `post`,
82 | });
83 | return json(`ok`, { status: 200 });
84 | }
85 | if (request.method === "DELETE") {
86 | const data = new URLSearchParams(await request.text());
87 | const todoId = data.get("delete");
88 | console.log(todoId);
89 | if (!todoId)
90 | return json(
91 | { error: "Todo id must be defined" },
92 | {
93 | status: 400,
94 | }
95 | );
96 | await db.execute({ sql: `DELETE from TODO where id = ?`, args: [todoId] });
97 | await fetch(`${urlBase}invalidate/${params.todosId}`, {
98 | method: `post`,
99 | });
100 | return json(`ok`, { status: 200 });
101 | }
102 |
103 | return null;
104 | };
105 |
106 | type LoaderData = {
107 | todos: Todo[];
108 | };
109 |
110 | export default function Index() {
111 | let data = useLoaderData();
112 | let params = useParams();
113 | let formRef = useRef(null);
114 | const transition = useNavigation();
115 | console.log({ data, formRef, transition });
116 |
117 | // data.todos = []
118 |
119 | useEffect(() => {
120 | if (transition.state === "loading") {
121 | formRef.current?.reset();
122 | }
123 | }, [transition.state]);
124 |
125 | return (
126 |
127 |
{params.todosId} todos
128 |
129 | {data.todos.map((todo) => (
130 | -
131 |
139 |
145 | {todo.title}
146 |
147 |
148 |
152 |
156 |
157 |
158 |
159 | ))}
160 |
161 |
178 |
179 | );
180 | }
181 |
--------------------------------------------------------------------------------
/server/mutators.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, afterAll, describe, expect, test } from "vitest"
2 | import * as Y from "yjs"
3 | import fs from "fs"
4 | import path from "path"
5 | import { listen } from "../machines/server"
6 | import { createRequest } from "../machines/client"
7 | import { serverConfig } from "./mutators"
8 | import * as util from "node:util"
9 | import * as child_process from "node:child_process"
10 |
11 | const execAsync = util.promisify(child_process.exec)
12 |
13 | let tmpDir: string = ``
14 | let doc
15 | let adminDb
16 | beforeAll(async () => {
17 | const dirName =
18 | `test-` + Date.now() + `-` + Math.random().toString(36).substring(2, 7)
19 | tmpDir = path.join(`/tmp`, dirName)
20 |
21 | try {
22 | // Create the directory synchronously
23 | fs.mkdirSync(tmpDir)
24 | } catch (err) {
25 | console.error(`Failed to create directory:`, err)
26 | }
27 |
28 | doc = new Y.Doc()
29 |
30 | const adminDbCredentials = {
31 | url: `libsql://todos-saas-admin-kyleamathews.turso.io`,
32 | authToken: `eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2OTU2Nzg1MjksImlkIjoiM2JjODcxMDgtNWJlZC0xMWVlLTkwOWItNTIxZDZkZmM4M2VmIn0.ARL4cjEOTRD3J-aQET9nOILCgNr9TE6diAV-Rxv-eWWl0P1cmIuuPVxoBFH32pGJKyM03yL3H-Tnq52VXpQwDA`,
33 | }
34 |
35 | const { context, mutators } = serverConfig({
36 | adminUrl: adminDbCredentials.url,
37 | adminAuthToken: adminDbCredentials.authToken,
38 | })
39 | console.log({ mutators })
40 |
41 | adminDb = context.adminDb
42 | // await adminDb.execute(`CREATE TABLE dbs (
43 | // url TEXT PRIMARY KEY NOT NULL,
44 | // authToken TEXT NOT NULL,
45 | // state TEXT NOT NULL,
46 | // name TEXT NOT NULL,
47 | // updatedAt TEXT NOT NULL
48 | // );
49 | // `)
50 | listen({
51 | doc,
52 | serverConfig: { mutators },
53 | })
54 | })
55 |
56 | // Cleanup
57 | afterAll(async () => {
58 | let hasDb = true
59 | try {
60 | await execAsync(`turso db list | grep foo`)
61 | } catch (e) {
62 | console.log(e)
63 | hasDb = false
64 | }
65 | if (hasDb) {
66 | const destroyOutput = await execAsync(`turso db destroy foo --yes`)
67 | console.log({ destroyOutput })
68 | }
69 |
70 | fs.rmSync(tmpDir, { recursive: true })
71 | })
72 |
73 | test(`update a robot name`, async () => {
74 | const doc = new Y.Doc()
75 |
76 | const robots = doc.getMap(`robots`)
77 | const robotId = `123`
78 | robots.set(robotId, { id: robotId, name: `boop` })
79 |
80 | // Config that gets new requests & calls right function.
81 | const serverConfig = {
82 | mutators: {
83 | updateRobotName: async function ({ state, doc }) {
84 | await new Promise((resolve) => setTimeout(resolve, 3))
85 | // Async work first and then return func w/ any sync changes.
86 | return function () {
87 | const robots = doc.getMap(`robots`)
88 | const robot = robots.get(state.request.id)
89 | robot.name = state.request.name
90 | robots.set(state.request.id, robot)
91 | return { ok: true }
92 | }
93 | },
94 | },
95 | }
96 |
97 | listen({ doc, serverConfig })
98 |
99 | let newName = `beep`
100 | const requestObject = await createRequest({
101 | doc,
102 | mutator: `updateRobotName`,
103 | request: { id: robotId, name: newName },
104 | })
105 |
106 | const { id, clientCreate, serverResponded, ...toSnapshot } = requestObject
107 | expect(toSnapshot).toMatchSnapshot()
108 | expect(requestObject.done).toBeTruthy()
109 | expect(robots.get(requestObject.request.id).name).toEqual(newName)
110 |
111 | newName = `boop`
112 | const requestObject2 = await createRequest({
113 | doc,
114 | mutator: `updateRobotName`,
115 | request: { id: robotId, name: newName },
116 | })
117 |
118 | expect(robots.get(requestObject2.request.id).name).toEqual(newName)
119 | })
120 |
121 | function makeid(length) {
122 | let result = ``
123 | const characters = `abcdefghijklmnopqrstuvwxyz0123456789`
124 | const charactersLength = characters.length
125 | let counter = 0
126 | while (counter < length) {
127 | result += characters.charAt(Math.floor(Math.random() * charactersLength))
128 | counter += 1
129 | }
130 | return result
131 | }
132 | describe(`serverConfig`, () => {
133 | const dbName = `foo-${makeid(3)}`
134 | const cloneDbName = `${dbName}-clone`
135 | test(`createDb`, async () => {
136 | const requestObject = await createRequest({
137 | doc,
138 | mutator: `createDb`,
139 | request: { name: dbName },
140 | })
141 |
142 | console.log({ requestObject })
143 | expect(requestObject.response.name).toEqual(dbName)
144 |
145 | expect(Object.keys(requestObject.response)).toMatchSnapshot()
146 | expect(Object.keys(doc.getMap(`dbs`).get(dbName))).toMatchSnapshot()
147 | expect(doc.getMap(`dbs`).get(dbName).completed).toEqual(0)
148 | expect(doc.getMap(`dbs`).get(dbName).total).toEqual(1)
149 |
150 | const result = await adminDb.execute({
151 | sql: `SELECT * from dbs where name=:name`,
152 | args: { name: dbName },
153 | })
154 | expect(result.rows.length).toEqual(1)
155 | expect(result.rows[0][3]).toEqual(dbName)
156 | }, 10000)
157 | test(`clone`, async () => {
158 | const requestObject = await createRequest({
159 | doc,
160 | mutator: `createDb`,
161 | request: { name: cloneDbName, fromDb: dbName },
162 | })
163 |
164 | console.log({ requestObject })
165 | expect(requestObject.response.name).toEqual(cloneDbName)
166 |
167 | expect(Object.keys(requestObject.response)).toMatchSnapshot()
168 | expect(Object.keys(doc.getMap(`dbs`).get(cloneDbName))).toMatchSnapshot()
169 |
170 | const result = await adminDb.execute({
171 | sql: `SELECT * from dbs where name=:name`,
172 | args: { name: dbName },
173 | })
174 | expect(result.rows.length).toEqual(1)
175 | expect(result.rows[0][3]).toEqual(dbName)
176 | }, 10000)
177 | test(`deleteDb`, async () => {
178 | const requestObject = await createRequest({
179 | doc,
180 | mutator: `deleteDb`,
181 | request: { name: dbName },
182 | })
183 | const requestObject2 = await createRequest({
184 | doc,
185 | mutator: `deleteDb`,
186 | request: { name: cloneDbName },
187 | })
188 | console.log({
189 | requestObject: requestObject.response,
190 | requestObject2: requestObject2.response,
191 | })
192 |
193 | expect(requestObject.response.name).toEqual(dbName)
194 | expect(Object.keys(requestObject.response)).toMatchSnapshot()
195 | expect(fs.existsSync(requestObject.response.dbPath)).toBeFalsy()
196 |
197 | const result = await adminDb.execute({
198 | sql: `SELECT * from dbs where name=:name`,
199 | args: { name: dbName },
200 | })
201 | expect(result.rows.length).toEqual(0)
202 | })
203 | })
204 |
--------------------------------------------------------------------------------
/app-admin/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import {
3 | Button,
4 | Dialog,
5 | DialogTrigger,
6 | Heading,
7 | TextField,
8 | Label,
9 | Modal,
10 | TextArea,
11 | } from "react-aria-components"
12 | import { useYjs, useSubscribeYjs, useAwarenessStates } from "situated"
13 | import "./App.css"
14 | import { format } from "timeago.js"
15 | import { createTRPCProxyClient, loggerLink } from "@trpc/client"
16 | import { link as yjsLink } from "trpc-yjs/link"
17 | import { AppRouter } from "../../server/trpc"
18 |
19 | const trpc = createTRPCProxyClient({
20 | links: [
21 | loggerLink(),
22 | yjsLink({
23 | doc: window.rootDoc,
24 | }),
25 | ],
26 | })
27 |
28 | window.trpc = trpc
29 |
30 | function makeid(length) {
31 | let result = ""
32 | const characters = "abcdefghijklmnopqrstuvwxyz0123456789"
33 | const charactersLength = characters.length
34 | let counter = 0
35 | while (counter < length) {
36 | result += characters.charAt(Math.floor(Math.random() * charactersLength))
37 | counter += 1
38 | }
39 | return result
40 | }
41 |
42 | const appServerBase =
43 | process.env.NODE_ENV === `production`
44 | ? `https://app-server-todos-saas.fly.dev/todos/`
45 | : `http://localhost:10000/todos/`
46 |
47 | function SelectModal({ dbName, requests }) {
48 | const selects = requests
49 | .filter((request) => {
50 | return (
51 | request.path == `selectDb` &&
52 | request.input.name === dbName &&
53 | request.error !== true
54 | )
55 | })
56 | .reverse()
57 | .slice(0, 5)
58 |
59 | return (
60 |
61 |
69 |
70 |
110 |
111 |
112 | )
113 | }
114 |
115 | function App() {
116 | const { rootDoc } = useYjs()
117 | const [createDbError, setDbError] = useState(``)
118 | const requestsArrayYjs = rootDoc.getArray(`trpc-calls`)
119 | const dbsYjs = rootDoc.getMap(`dbs`)
120 |
121 | // Subscribe to updates.
122 | const requests = useSubscribeYjs(requestsArrayYjs) || []
123 | const dbs = useSubscribeYjs(dbsYjs) || {}
124 |
125 | return (
126 |
127 |
TODOs SaaS Admin
128 |
153 |
154 | {Object.values(dbs)
155 | .reverse()
156 | .filter(db => db.name !== `do-not-delete`)
157 | .sort((a, b) => {
158 | return a.updatedAt > b.updatedAt
159 | ? -1
160 | : a.updatedAt < b.updatedAt
161 | ? 1
162 | : 0
163 | })
164 | .map((db) => {
165 | return (
166 |
174 |
175 | {db.name}
176 |
177 |
Last updated {format(db.updatedAt)}
178 |
{db.state}
179 | {db.state === `READY` && (
180 |
181 |
Open Instance
182 |
183 | {db.total} TODOs with {db.completed} completed
184 |
185 |
186 | db: {db.url}
187 |
188 |
189 |
208 |
219 |
220 | )}
221 |
222 | )
223 | })}
224 |
225 |
226 | )
227 | }
228 |
229 | export default App
230 |
--------------------------------------------------------------------------------
/server/mutators.ts:
--------------------------------------------------------------------------------
1 | import path from "path"
2 | import Parser from "node-sql-parser"
3 | import fs from "fs-extra"
4 | import { createClient } from "@libsql/client"
5 | const parser = new Parser.Parser()
6 | import * as util from "node:util"
7 | import * as child_process from "node:child_process"
8 | import { mapResultSet } from "./map-sqlite-resultset"
9 | import { ProfanityEngine } from "@coffeeandfun/google-profanity-words"
10 | const profanity = new ProfanityEngine()
11 |
12 | const execAsync = util.promisify(child_process.exec)
13 |
14 | async function setupDb(db) {
15 | await db.execute(`CREATE TABLE Todo (
16 | id INTEGER PRIMARY KEY AUTOINCREMENT,
17 | title TEXT NOT NULL,
18 | completed BOOLEAN NOT NULL CHECK (completed IN (0, 1))
19 | );`)
20 | await db.execute(
21 | `INSERT INTO Todo (title, completed) VALUES ('Go to Grocery Store', 0)`
22 | )
23 | }
24 |
25 | export const serverConfig = ({ adminUrl, adminAuthToken }) => {
26 | // Create the admin db
27 | const adminDb = createClient({
28 | url: adminUrl,
29 | authToken: adminAuthToken,
30 | })
31 | return {
32 | context: {
33 | adminDb,
34 | },
35 | mutators: {
36 | ping: async function ({ state, doc }) {
37 | return function () {
38 | return { ok: true, request: state.request }
39 | }
40 | },
41 | createDb: async function ({ state, doc }) {
42 | // Validate app/db name
43 | const regex = /^[a-zA-Z0-9-_]{1,27}$/
44 | if (!regex.test(state.request.name)) {
45 | console.log({
46 | name: state.request.name,
47 | test: regex.test(state.request.name),
48 | })
49 | return function () {
50 | return {
51 | error: `App names must not contain spaces or special characters and be less than 28 characters`,
52 | }
53 | }
54 | }
55 |
56 | const dbs = doc.getMap(`dbs`)
57 | if (dbs.has(state.request.name)) {
58 | return function () {
59 | return { error: `DB Already exists` }
60 | }
61 | }
62 |
63 | const isProfane = await profanity.hasCurseWords(
64 | state.request.name.split(`-`).join(` `).split(`_`).join(` `)
65 | )
66 | if (isProfane) {
67 | return function () {
68 | return { error: `Profane db names are not allowed.` }
69 | }
70 | }
71 |
72 | let createOutput
73 | let urlOutput
74 | let tokenOutput
75 | dbs.set(state.request.name, {
76 | name: state.request.name,
77 | state: `INITIALIZING`,
78 | updatedAt: new Date().toJSON(),
79 | })
80 | let command = `turso db create --group demo-multi-tenant-saas ${state.request.name}`
81 | if (state.request.fromDb) {
82 | command += ` --from-db=${state.request.fromDb}`
83 | }
84 | try {
85 | createOutput = await execAsync(command)
86 | dbs.set(state.request.name, {
87 | name: state.request.name,
88 | state: `CREATED`,
89 | updatedAt: new Date().toJSON(),
90 | })
91 | urlOutput = await execAsync(
92 | `turso db show ${state.request.name} --url`
93 | )
94 | tokenOutput = await execAsync(
95 | `turso db tokens create ${state.request.name}`
96 | )
97 | dbs.set(state.request.name, {
98 | name: state.request.name,
99 | state: `CREATING TABLES`,
100 | updatedAt: new Date().toJSON(),
101 | })
102 | } catch (e) {
103 | console.log(e)
104 | return function () {
105 | return { error: e }
106 | }
107 | }
108 | console.log({ createOutput, urlOutput, tokenOutput })
109 |
110 | const url = urlOutput.stdout.trim()
111 | const authToken = tokenOutput.stdout.trim()
112 | console.log({ url, authToken })
113 |
114 | const db = createClient({ url, authToken })
115 |
116 | // Don't need to do setup for cloned dbs.
117 | if (!state.request.fromDb) {
118 | await setupDb(db)
119 | }
120 | // Async work first and then return func w/ any sync changes.
121 | // Validate db doesn't exist in both yjs and on disk.
122 | // Then create db and create table.
123 | //
124 | // TODO also a test to run queries.
125 | const updatedAt = new Date().toJSON()
126 | await adminDb.execute({
127 | sql: `INSERT INTO dbs values (:url, :authToken, :state, :name, :updatedAt)`,
128 | args: {
129 | url,
130 | authToken,
131 | state: `READY`,
132 | name: state.request.name,
133 | updatedAt,
134 | },
135 | })
136 |
137 | const totals = mapResultSet(
138 | await db.execute(
139 | `select completed, count(*) as count from Todo group by completed`
140 | )
141 | )
142 | console.log({ totals })
143 |
144 | return function () {
145 | dbs.set(state.request.name, {
146 | url,
147 | authToken,
148 | state: `READY`,
149 | name: state.request.name,
150 | total: totals.map((row) => row.count).reduce((a, b) => a + b, 0),
151 | completed: totals.find((row) => row.completed === 1)?.count || 0,
152 | updatedAt,
153 | })
154 | return { url, name: state.request.name }
155 | }
156 | },
157 | deleteDb: async function ({ state, doc }) {
158 | const dbs = doc.getMap(`dbs`)
159 | if (!dbs.has(state.request.name)) {
160 | return function () {
161 | return {
162 | error: `DB with the name ${state.request.name} doesn't exists`,
163 | }
164 | }
165 | }
166 |
167 | let destroyOutput
168 | try {
169 | destroyOutput = await execAsync(
170 | `turso db destroy ${state.request.name} --yes`
171 | )
172 | } catch (e) {
173 | console.log(e)
174 | return function () {
175 | return { error: e }
176 | }
177 | }
178 |
179 | await adminDb.execute({
180 | sql: `DELETE FROM dbs WHERE name=:name`,
181 | args: {
182 | name: state.request.name,
183 | },
184 | })
185 |
186 | return function () {
187 | dbs.delete(state.request.name)
188 | return { name: state.request.name }
189 | }
190 | },
191 | selectDb: async function ({ state, doc }) {
192 | try {
193 | const dbInfo = doc.getMap(`dbs`).get(state.request.name)
194 | const db = createClient({
195 | url: dbInfo.url,
196 | authToken: dbInfo.authToken,
197 | })
198 | const ast = parser.astify(state.request.sql)
199 | if (ast.type === `select`) {
200 | console.log(`query`, state.request.sql)
201 | const results = mapResultSet(await db.execute(state.request.sql))
202 | // Async work first and then return func w/ any sync changes.
203 | return function () {
204 | return {
205 | ok: true,
206 | results,
207 | }
208 | }
209 | } else {
210 | return function () {
211 | return { error: `Only select operations are allowed` }
212 | }
213 | }
214 | } catch (e) {
215 | console.log(e)
216 | return function () {
217 | return { error: e }
218 | }
219 | }
220 | },
221 | },
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/machines/machines.test.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from "vitest"
2 | import * as Y from "yjs"
3 | import fs from "fs"
4 | import path from "path"
5 | import { createClient } from "@libsql/client"
6 | const { Parser } = require(`node-sql-parser`)
7 | const parser = new Parser()
8 | import { mapResultSet } from "./map-sqlite-resultset"
9 |
10 | import { listen } from "./server"
11 | import { createRequest } from "./client"
12 |
13 | test(`update a robot name`, async () => {
14 | // const { machine } = require(`./ping-pong`)
15 |
16 | const doc = new Y.Doc()
17 |
18 | const robots = doc.getMap(`robots`)
19 | const robotId = `123`
20 | robots.set(robotId, { id: robotId, name: `boop` })
21 |
22 | // Config that gets new requests & calls right function.
23 | const serverConfig = {
24 | mutators: {
25 | updateRobotName: async function ({ state, doc }) {
26 | await new Promise((resolve) => setTimeout(resolve, 3))
27 | // Async work first and then return func w/ any sync changes.
28 | return function () {
29 | const robots = doc.getMap(`robots`)
30 | const robot = robots.get(state.request.id)
31 | robot.name = state.request.name
32 | robots.set(state.request.id, robot)
33 | return { ok: true }
34 | }
35 | },
36 | },
37 | }
38 |
39 | listen({ doc, serverConfig })
40 |
41 | // Server instantiation
42 | // listen(doc, serverConfig)
43 | //
44 | // client instantiation
45 | // hmm just call createRequest({doc, mutator, request})
46 |
47 | // Client code
48 | // TODO
49 | // - create an id so can await it coming back — haha there's a generic
50 | // state machine for a client to run a client/server state machine. You pass in
51 | // machine and the initial state and then off it goes and you just await
52 | // it finishing. That handles creating a uuid and awaiting it to come back
53 | // updated.
54 | //
55 | // Or I guess it's a sort of abstraction for "I advanced this state machine as far as I can
56 | // and now I'm tossing it to the network for the next node to change". So await a change to the machine.
57 | //
58 | // Run a state machine until you hit a point that you want to serialize and it for
59 | // running on the server and then pick it back up again when it comes back.
60 | //
61 | // Basically a job though I guess mutation is more generic.
62 | //
63 | // I'm not sure xstate is needed here — just do what I did in the post really...
64 | //
65 | // xstate would be great for more complex stuff but probably just an object w/ some
66 | // validation is enough.
67 | //
68 | //
69 | // TODO add helper function to generate the request & await the response.
70 | // Server function which does the mutation on the doc.
71 | // switch to updateName
72 | let newName = `beep`
73 | const requestObject = await createRequest({
74 | doc,
75 | mutator: `updateRobotName`,
76 | request: { id: robotId, name: newName },
77 | })
78 |
79 | const { id, clientCreate, serverResponded, ...toSnapshot } = requestObject
80 | expect(toSnapshot).toMatchSnapshot()
81 | expect(requestObject.done).toBeTruthy()
82 | expect(robots.get(requestObject.request.id).name).toEqual(newName)
83 |
84 | newName = `boop`
85 | const requestObject2 = await createRequest({
86 | doc,
87 | mutator: `updateRobotName`,
88 | request: { id: robotId, name: newName },
89 | })
90 |
91 | expect(robots.get(requestObject2.request.id).name).toEqual(newName)
92 | })
93 |
94 | test(`create/delete dbs`, async () => {
95 | const doc = new Y.Doc()
96 |
97 | const dbs = doc.getMap(`dbs`)
98 |
99 | // Generate a random directory name
100 | const dirName =
101 | `test-` + Date.now() + `-` + Math.random().toString(36).substring(2, 7)
102 | const dirPath = path.join(`/tmp`, dirName)
103 |
104 | try {
105 | // Create the directory synchronously
106 | fs.mkdirSync(dirPath)
107 | } catch (err) {
108 | console.error(`Failed to create directory:`, err)
109 | }
110 |
111 | async function setupDb(dbPath) {
112 | const db = createClient({ url: `file:${dbPath}` })
113 | await db.execute(
114 | `CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)`
115 | )
116 | await db.execute(
117 | `INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.org')`
118 | )
119 | }
120 |
121 | // Config that gets new requests & calls right function.
122 | const serverConfig = {
123 | mutators: {
124 | createDb: async function ({ state, doc }) {
125 | const dbs = doc.getMap(`dbs`)
126 | if (dbs.has(state.request.name)) {
127 | return function () {
128 | return { error: `DB Already exists` }
129 | }
130 | }
131 | const dbPath = path.join(dirPath, `${state.request.name}.db`)
132 | if (fs.existsSync(dbPath)) {
133 | return function () {
134 | return {
135 | error: `DB Already exists on disk (though oddly not in the map)`,
136 | }
137 | }
138 | }
139 |
140 | await setupDb(dbPath)
141 | // Async work first and then return func w/ any sync changes.
142 | // Validate db doesn't exist in both yjs and on disk.
143 | // Then create db and create table.
144 | //
145 | // TODO also a test to run queries.
146 | return function () {
147 | dbs.set(state.request.name, { foo: true })
148 | return { dbPath }
149 | }
150 | },
151 | deleteDb: async function ({ state, doc }) {
152 | const dbPath = path.join(dirPath, `${state.request.name}.db`)
153 | fs.unlinkSync(dbPath)
154 | // Async work first and then return func w/ any sync changes.
155 | return function () {
156 | const dbs = doc.getMap(`dbs`)
157 | dbs.delete(state.request.name)
158 | return { ok: true }
159 | }
160 | },
161 | selectDb: async function ({ state, doc }) {
162 | const dbPath = path.join(dirPath, `${state.request.name}.db`)
163 | const db = createClient({ url: `file:${dbPath}` })
164 | const ast = parser.astify(state.request.sql)
165 | if (ast.type === `select`) {
166 | const results = await db.execute(state.request.sql)
167 | // Async work first and then return func w/ any sync changes.
168 | return function () {
169 | return {
170 | ok: true,
171 | results: mapResultSet(results),
172 | }
173 | }
174 | } else {
175 | return function () {
176 | return { error: `Only select operations are allowed` }
177 | }
178 | }
179 | },
180 | },
181 | }
182 |
183 | listen({ doc, serverConfig })
184 |
185 | // Test creating a db.
186 | const requestObject = await createRequest({
187 | doc,
188 | mutator: `createDb`,
189 | request: { name: `foo` },
190 | })
191 |
192 | expect(dbs.get(`foo`)).toMatchSnapshot()
193 |
194 | // Test duplicate returns an error.
195 | const dupRequestObject = await createRequest({
196 | doc,
197 | mutator: `createDb`,
198 | request: { name: `foo` },
199 | })
200 |
201 | expect(dupRequestObject.error).toBeTruthy()
202 |
203 | // Test running a valid and then invalid query.
204 | const selectReq = await createRequest({
205 | doc,
206 | mutator: `selectDb`,
207 | request: { name: `foo`, sql: `select * from users` },
208 | })
209 |
210 | expect(selectReq.response.results).toMatchSnapshot()
211 |
212 | const insertReq = await createRequest({
213 | doc,
214 | mutator: `selectDb`,
215 | request: {
216 | name: `foo`,
217 | sql: `INSERT INTO users (id, name, email) VALUES (2, 'Alice', 'alice@example.org')`,
218 | },
219 | })
220 |
221 | expect(insertReq.error).toBeTruthy()
222 |
223 | // Test deleting a db.
224 | await createRequest({
225 | doc,
226 | mutator: `deleteDb`,
227 | request: { name: `foo` },
228 | })
229 |
230 | expect(dbs.has(`foo`)).toBeFalsy()
231 | expect(fs.existsSync(requestObject.response.dbPath)).toBeFalsy()
232 | })
233 |
--------------------------------------------------------------------------------
/server/trpc.ts:
--------------------------------------------------------------------------------
1 | import { initTRPC, TRPCError } from "@trpc/server"
2 | import { z } from "zod"
3 | import Parser from "node-sql-parser"
4 | import { createClient } from "@libsql/client"
5 | import * as util from "node:util"
6 | import * as child_process from "node:child_process"
7 | import { mapResultSet } from "./map-sqlite-resultset"
8 | import { ProfanityEngine } from "@coffeeandfun/google-profanity-words"
9 | const profanity = new ProfanityEngine()
10 | const parser = new Parser.Parser()
11 |
12 | const execAsync = util.promisify(child_process.exec)
13 |
14 | /**
15 | * Initialization of tRPC backend
16 | * Should be done only once per backend!
17 | */
18 | const t = initTRPC.create()
19 | /**
20 | * Export reusable router and procedure helpers
21 | * that can be used throughout the router
22 | */
23 | const router = t.router
24 | const publicProcedure = t.procedure
25 |
26 | async function setupDb(db) {
27 | await db.execute(`CREATE TABLE Todo (
28 | id INTEGER PRIMARY KEY AUTOINCREMENT,
29 | title TEXT NOT NULL,
30 | completed BOOLEAN NOT NULL CHECK (completed IN (0, 1))
31 | );`)
32 | await db.execute(
33 | `INSERT INTO Todo (title, completed) VALUES ('Go to Grocery Store', 0)`
34 | )
35 | }
36 |
37 | export const appRouter = router({
38 | ping: publicProcedure.mutation(async () => {
39 | return {
40 | transact: () => {
41 | true
42 | },
43 | }
44 | }),
45 | createDb: publicProcedure
46 | .input(
47 | z.object({
48 | name: z
49 | .string()
50 | .regex(/^[a-zA-Z0-9-_]{1,27}$/, {
51 | message: `App name must not contain spaces or special characters`,
52 | })
53 | .max(28, { message: `App name must be 28 characters or less` })
54 | .min(3),
55 | fromDb: z.string().min(3).optional(),
56 | })
57 | )
58 | .mutation(async function ({ input, ctx: { doc, adminDb, transact } }) {
59 | const dbs = doc.getMap(`dbs`)
60 | if (dbs.has(input.name)) {
61 | throw new TRPCError({
62 | code: `CONFLICT`,
63 | message: `A db by this name already exists`,
64 | })
65 | }
66 |
67 | const isProfane = await profanity.hasCurseWords(
68 | input.name.split(`-`).join(` `).split(`_`).join(` `)
69 | )
70 | if (isProfane) {
71 | throw new TRPCError({
72 | code: `BAD_REQUEST`,
73 | message: `Profane db names are not allowed.`,
74 | })
75 | }
76 |
77 | let createOutput
78 | let urlOutput
79 | let tokenOutput
80 | dbs.set(input.name, {
81 | name: input.name,
82 | state: `INITIALIZING`,
83 | updatedAt: new Date().toJSON(),
84 | })
85 | let command = `turso db create --group demo-multi-tenant-saas ${input.name}`
86 | if (input.fromDb) {
87 | command += ` --from-db=${input.fromDb}`
88 | }
89 | try {
90 | createOutput = await execAsync(command)
91 | dbs.set(input.name, {
92 | name: input.name,
93 | state: `CREATED`,
94 | updatedAt: new Date().toJSON(),
95 | })
96 | urlOutput = await execAsync(`turso db show ${input.name} --url`)
97 | tokenOutput = await execAsync(`turso db tokens create ${input.name}`)
98 | dbs.set(input.name, {
99 | name: input.name,
100 | state: `CREATING TABLES`,
101 | updatedAt: new Date().toJSON(),
102 | })
103 | } catch (e) {
104 | console.log(e)
105 | dbs.delete(input.name)
106 | throw new TRPCError({
107 | code: `INTERNAL_SERVER_ERROR`,
108 | message: `Error creating database or tables`,
109 | cause: e,
110 | })
111 | }
112 | console.log({ createOutput, urlOutput, tokenOutput })
113 |
114 | const url = urlOutput.stdout.trim()
115 | const authToken = tokenOutput.stdout.trim()
116 | console.log({ url, authToken })
117 |
118 | const db = createClient({ url, authToken })
119 |
120 | // Don't need to do setup for cloned dbs.
121 | if (!input.fromDb) {
122 | await setupDb(db)
123 | }
124 | // Async work first and then return func w/ any sync changes.
125 | // Validate db doesn't exist in both yjs and on disk.
126 | // Then create db and create table.
127 | //
128 | // TODO also a test to run queries.
129 | const updatedAt = new Date().toJSON()
130 | await adminDb.execute({
131 | sql: `INSERT INTO dbs values (:url, :authToken, :state, :name, :updatedAt)`,
132 | args: {
133 | url,
134 | authToken,
135 | state: `READY`,
136 | name: input.name,
137 | updatedAt,
138 | },
139 | })
140 |
141 | const totals = mapResultSet(
142 | await db.execute(
143 | `select completed, count(*) as count from Todo group by completed`
144 | )
145 | )
146 | console.log({ totals })
147 |
148 | transact(() => {
149 | dbs.set(input.name, {
150 | url,
151 | authToken,
152 | state: `READY`,
153 | name: input.name,
154 | total: totals.map((row) => row.count).reduce((a, b) => a + b, 0),
155 | completed: totals.find((row) => row.completed === 1)?.count || 0,
156 | updatedAt,
157 | })
158 | })
159 |
160 | return { url, name: input.name }
161 | // return function () {
162 | // dbs.set(input.name, {
163 | // url,
164 | // authToken,
165 | // state: `READY`,
166 | // name: input.name,
167 | // total: totals.map((row) => row.count).reduce((a, b) => a + b, 0),
168 | // completed: totals.find((row) => row.completed === 1)?.count || 0,
169 | // updatedAt,
170 | // })
171 | // return { url, name: input.name }
172 | // }
173 | }),
174 | deleteDb: publicProcedure
175 | .input(z.object({ name: z.string() }))
176 | .mutation(async function ({ input, ctx: { doc, adminDb, transact } }) {
177 | const dbs = doc.getMap(`dbs`)
178 | if (!dbs.has(input.name)) {
179 | return function () {
180 | return {
181 | error: `DB with the name ${input.name} doesn't exists`,
182 | }
183 | }
184 | }
185 |
186 | let destroyOutput
187 | try {
188 | destroyOutput = await execAsync(`turso db destroy ${input.name} --yes`)
189 | } catch (e) {
190 | console.log(e)
191 | return function () {
192 | return { error: e }
193 | }
194 | }
195 |
196 | await adminDb.execute({
197 | sql: `DELETE FROM dbs WHERE name=:name`,
198 | args: {
199 | name: input.name,
200 | },
201 | })
202 |
203 | transact(() => {
204 | dbs.delete(input.name)
205 | })
206 |
207 | return input.name
208 | }),
209 | selectDb: publicProcedure
210 | .input(z.object({ name: z.string(), sql: z.string() }))
211 | .query(async function ({ input, ctx: { doc } }) {
212 | try {
213 | const dbInfo = doc.getMap(`dbs`).get(input.name)
214 | const db = createClient({
215 | url: dbInfo.url,
216 | authToken: dbInfo.authToken,
217 | })
218 | const ast = parser.astify(input.sql)
219 | if (ast.type === `select`) {
220 | const results = mapResultSet(await db.execute(input.sql))
221 | // Async work first and then return func w/ any sync changes.
222 | return {
223 | ok: true,
224 | results,
225 | }
226 | } else {
227 | throw new TRPCError({
228 | code: `BAD_REQUEST`,
229 | message: `Only select operations are allowed`,
230 | })
231 | }
232 | } catch (e) {
233 | console.log(e)
234 | throw new TRPCError({
235 | code: `INTERNAL_SERVER_ERROR`,
236 | cause: e,
237 | })
238 | }
239 | }),
240 | })
241 | // userUpdateName: publicProcedure
242 | // .input(z.object({ id: z.string(), name: z.string() }))
243 | // .mutation(async (opts) => {
244 | // const {
245 | // input,
246 | // ctx: { users },
247 | // } = opts
248 | // let user
249 | // let id
250 | // users.forEach((u, i) => {
251 | // if (u.id === input.id) {
252 | // user = u
253 | // id = i
254 | // }
255 | // })
256 | // const newUser = { ...user, name: input.name }
257 | // return {
258 | // mutations: () => {
259 | // users.delete(id, 1)
260 | // users.insert(id, [newUser])
261 | // },
262 | // response: newUser,
263 | // }
264 | // }),
265 | // })
266 |
267 | export type AppRouter = typeof appRouter
268 |
--------------------------------------------------------------------------------
/machines/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '6.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | dependencies:
8 | '@libsql/client':
9 | specifier: ^0.3.5
10 | version: 0.3.5
11 | ejs:
12 | specifier: ^3.1.9
13 | version: 3.1.9
14 | node-sql-parser:
15 | specifier: ^4.11.0
16 | version: 4.11.0
17 | uuid:
18 | specifier: ^9.0.1
19 | version: 9.0.1
20 | yjs:
21 | specifier: ^13.6.8
22 | version: 13.6.8
23 |
24 | devDependencies:
25 | vitest:
26 | specifier: ^0.34.5
27 | version: 0.34.5
28 |
29 | packages:
30 |
31 | /@esbuild/android-arm64@0.18.20:
32 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
33 | engines: {node: '>=12'}
34 | cpu: [arm64]
35 | os: [android]
36 | requiresBuild: true
37 | dev: true
38 | optional: true
39 |
40 | /@esbuild/android-arm@0.18.20:
41 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
42 | engines: {node: '>=12'}
43 | cpu: [arm]
44 | os: [android]
45 | requiresBuild: true
46 | dev: true
47 | optional: true
48 |
49 | /@esbuild/android-x64@0.18.20:
50 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
51 | engines: {node: '>=12'}
52 | cpu: [x64]
53 | os: [android]
54 | requiresBuild: true
55 | dev: true
56 | optional: true
57 |
58 | /@esbuild/darwin-arm64@0.18.20:
59 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
60 | engines: {node: '>=12'}
61 | cpu: [arm64]
62 | os: [darwin]
63 | requiresBuild: true
64 | dev: true
65 | optional: true
66 |
67 | /@esbuild/darwin-x64@0.18.20:
68 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
69 | engines: {node: '>=12'}
70 | cpu: [x64]
71 | os: [darwin]
72 | requiresBuild: true
73 | dev: true
74 | optional: true
75 |
76 | /@esbuild/freebsd-arm64@0.18.20:
77 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
78 | engines: {node: '>=12'}
79 | cpu: [arm64]
80 | os: [freebsd]
81 | requiresBuild: true
82 | dev: true
83 | optional: true
84 |
85 | /@esbuild/freebsd-x64@0.18.20:
86 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
87 | engines: {node: '>=12'}
88 | cpu: [x64]
89 | os: [freebsd]
90 | requiresBuild: true
91 | dev: true
92 | optional: true
93 |
94 | /@esbuild/linux-arm64@0.18.20:
95 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
96 | engines: {node: '>=12'}
97 | cpu: [arm64]
98 | os: [linux]
99 | requiresBuild: true
100 | dev: true
101 | optional: true
102 |
103 | /@esbuild/linux-arm@0.18.20:
104 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
105 | engines: {node: '>=12'}
106 | cpu: [arm]
107 | os: [linux]
108 | requiresBuild: true
109 | dev: true
110 | optional: true
111 |
112 | /@esbuild/linux-ia32@0.18.20:
113 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
114 | engines: {node: '>=12'}
115 | cpu: [ia32]
116 | os: [linux]
117 | requiresBuild: true
118 | dev: true
119 | optional: true
120 |
121 | /@esbuild/linux-loong64@0.18.20:
122 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
123 | engines: {node: '>=12'}
124 | cpu: [loong64]
125 | os: [linux]
126 | requiresBuild: true
127 | dev: true
128 | optional: true
129 |
130 | /@esbuild/linux-mips64el@0.18.20:
131 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
132 | engines: {node: '>=12'}
133 | cpu: [mips64el]
134 | os: [linux]
135 | requiresBuild: true
136 | dev: true
137 | optional: true
138 |
139 | /@esbuild/linux-ppc64@0.18.20:
140 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
141 | engines: {node: '>=12'}
142 | cpu: [ppc64]
143 | os: [linux]
144 | requiresBuild: true
145 | dev: true
146 | optional: true
147 |
148 | /@esbuild/linux-riscv64@0.18.20:
149 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
150 | engines: {node: '>=12'}
151 | cpu: [riscv64]
152 | os: [linux]
153 | requiresBuild: true
154 | dev: true
155 | optional: true
156 |
157 | /@esbuild/linux-s390x@0.18.20:
158 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
159 | engines: {node: '>=12'}
160 | cpu: [s390x]
161 | os: [linux]
162 | requiresBuild: true
163 | dev: true
164 | optional: true
165 |
166 | /@esbuild/linux-x64@0.18.20:
167 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
168 | engines: {node: '>=12'}
169 | cpu: [x64]
170 | os: [linux]
171 | requiresBuild: true
172 | dev: true
173 | optional: true
174 |
175 | /@esbuild/netbsd-x64@0.18.20:
176 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
177 | engines: {node: '>=12'}
178 | cpu: [x64]
179 | os: [netbsd]
180 | requiresBuild: true
181 | dev: true
182 | optional: true
183 |
184 | /@esbuild/openbsd-x64@0.18.20:
185 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
186 | engines: {node: '>=12'}
187 | cpu: [x64]
188 | os: [openbsd]
189 | requiresBuild: true
190 | dev: true
191 | optional: true
192 |
193 | /@esbuild/sunos-x64@0.18.20:
194 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
195 | engines: {node: '>=12'}
196 | cpu: [x64]
197 | os: [sunos]
198 | requiresBuild: true
199 | dev: true
200 | optional: true
201 |
202 | /@esbuild/win32-arm64@0.18.20:
203 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
204 | engines: {node: '>=12'}
205 | cpu: [arm64]
206 | os: [win32]
207 | requiresBuild: true
208 | dev: true
209 | optional: true
210 |
211 | /@esbuild/win32-ia32@0.18.20:
212 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
213 | engines: {node: '>=12'}
214 | cpu: [ia32]
215 | os: [win32]
216 | requiresBuild: true
217 | dev: true
218 | optional: true
219 |
220 | /@esbuild/win32-x64@0.18.20:
221 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
222 | engines: {node: '>=12'}
223 | cpu: [x64]
224 | os: [win32]
225 | requiresBuild: true
226 | dev: true
227 | optional: true
228 |
229 | /@jest/schemas@29.6.3:
230 | resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
231 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
232 | dependencies:
233 | '@sinclair/typebox': 0.27.8
234 | dev: true
235 |
236 | /@jridgewell/sourcemap-codec@1.4.15:
237 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
238 | dev: true
239 |
240 | /@libsql/client@0.3.5:
241 | resolution: {integrity: sha512-4fZxGh0qKW5dtp1yuQLRvRAtbt02V4jzjM9sHSmz5k25xZTLg7/GlNudKdqKZrjJXEV5PvDNsczupBtedZZovw==}
242 | dependencies:
243 | '@libsql/hrana-client': 0.5.5
244 | js-base64: 3.7.5
245 | libsql: 0.1.23
246 | transitivePeerDependencies:
247 | - bufferutil
248 | - encoding
249 | - utf-8-validate
250 | dev: false
251 |
252 | /@libsql/darwin-arm64@0.1.23:
253 | resolution: {integrity: sha512-+V9aoOrZ47iYbY5NrcS0F2bDOCH407QI0wxAtss0CLOcFxlz/T6Nw0ryLK31GabklJQAmOXIyqkumLfz5HT64w==}
254 | cpu: [arm64]
255 | os: [darwin]
256 | requiresBuild: true
257 | dev: false
258 | optional: true
259 |
260 | /@libsql/darwin-x64@0.1.23:
261 | resolution: {integrity: sha512-toHo7s0HiMl4VCIfjhGXDe9bGWWo78eP8fxIbwU6RlaLO6MNV9fjHY/GjTWccWOwyxcT+q6X/kUc957HnoW3bg==}
262 | cpu: [x64]
263 | os: [darwin]
264 | requiresBuild: true
265 | dev: false
266 | optional: true
267 |
268 | /@libsql/hrana-client@0.5.5:
269 | resolution: {integrity: sha512-i+hDBpiV719poqEiHupUUZYKJ9YSbCRFe5Q2PQ0v3mHIftePH6gayLjp2u6TXbqbO/Dv6y8yyvYlBXf/kFfRZA==}
270 | dependencies:
271 | '@libsql/isomorphic-fetch': 0.1.10
272 | '@libsql/isomorphic-ws': 0.1.5
273 | js-base64: 3.7.5
274 | node-fetch: 3.3.2
275 | transitivePeerDependencies:
276 | - bufferutil
277 | - encoding
278 | - utf-8-validate
279 | dev: false
280 |
281 | /@libsql/isomorphic-fetch@0.1.10:
282 | resolution: {integrity: sha512-dH0lMk50gKSvEKD78xWMu60SY1sjp1sY//iFLO0XMmBwfVfG136P9KOk06R4maBdlb8KMXOzJ1D28FR5ZKnHTA==}
283 | dependencies:
284 | '@types/node-fetch': 2.6.6
285 | node-fetch: 2.7.0
286 | transitivePeerDependencies:
287 | - encoding
288 | dev: false
289 |
290 | /@libsql/isomorphic-ws@0.1.5:
291 | resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==}
292 | dependencies:
293 | '@types/ws': 8.5.6
294 | ws: 8.14.2
295 | transitivePeerDependencies:
296 | - bufferutil
297 | - utf-8-validate
298 | dev: false
299 |
300 | /@libsql/linux-x64-gnu@0.1.23:
301 | resolution: {integrity: sha512-U11LdjayakOj0lQCHDYkTgUfe4Q+7AjZZh8MzgEDF/9l0bmKNI3eFLWA3JD2Xm98yz65lUx95om0WKOKu5VW/w==}
302 | cpu: [x64]
303 | os: [linux]
304 | requiresBuild: true
305 | dev: false
306 | optional: true
307 |
308 | /@libsql/linux-x64-musl@0.1.23:
309 | resolution: {integrity: sha512-8UcCK2sPVzcafHsEmcU5IDp/NxjD6F6JFS5giijsMX5iGgxYQiiwTUMOmSxW0AWBeT4VY5U7G6rG5PC8JSFtfg==}
310 | cpu: [x64]
311 | os: [linux]
312 | requiresBuild: true
313 | dev: false
314 | optional: true
315 |
316 | /@libsql/win32-x64-msvc@0.1.23:
317 | resolution: {integrity: sha512-HAugD66jTmRRRGNMLKRiaFeMOC3mgUsAiuO6NRdRz3nM6saf9e5QqN/Ppuu9yqHHcZfv7VhQ9UGlAvzVK64Itg==}
318 | cpu: [x64]
319 | os: [win32]
320 | requiresBuild: true
321 | dev: false
322 | optional: true
323 |
324 | /@neon-rs/load@0.0.4:
325 | resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==}
326 | dev: false
327 |
328 | /@sinclair/typebox@0.27.8:
329 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
330 | dev: true
331 |
332 | /@types/chai-subset@1.3.3:
333 | resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
334 | dependencies:
335 | '@types/chai': 4.3.6
336 | dev: true
337 |
338 | /@types/chai@4.3.6:
339 | resolution: {integrity: sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==}
340 | dev: true
341 |
342 | /@types/node-fetch@2.6.6:
343 | resolution: {integrity: sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==}
344 | dependencies:
345 | '@types/node': 20.7.0
346 | form-data: 4.0.0
347 | dev: false
348 |
349 | /@types/node@20.7.0:
350 | resolution: {integrity: sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==}
351 |
352 | /@types/ws@8.5.6:
353 | resolution: {integrity: sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==}
354 | dependencies:
355 | '@types/node': 20.7.0
356 | dev: false
357 |
358 | /@vitest/expect@0.34.5:
359 | resolution: {integrity: sha512-/3RBIV9XEH+nRpRMqDJBufKIOQaYUH2X6bt0rKSCW0MfKhXFLYsR5ivHifeajRSTsln0FwJbitxLKHSQz/Xwkw==}
360 | dependencies:
361 | '@vitest/spy': 0.34.5
362 | '@vitest/utils': 0.34.5
363 | chai: 4.3.8
364 | dev: true
365 |
366 | /@vitest/runner@0.34.5:
367 | resolution: {integrity: sha512-RDEE3ViVvl7jFSCbnBRyYuu23XxmvRTSZWW6W4M7eC5dOsK75d5LIf6uhE5Fqf809DQ1+9ICZZNxhIolWHU4og==}
368 | dependencies:
369 | '@vitest/utils': 0.34.5
370 | p-limit: 4.0.0
371 | pathe: 1.1.1
372 | dev: true
373 |
374 | /@vitest/snapshot@0.34.5:
375 | resolution: {integrity: sha512-+ikwSbhu6z2yOdtKmk/aeoDZ9QPm2g/ZO5rXT58RR9Vmu/kB2MamyDSx77dctqdZfP3Diqv4mbc/yw2kPT8rmA==}
376 | dependencies:
377 | magic-string: 0.30.3
378 | pathe: 1.1.1
379 | pretty-format: 29.7.0
380 | dev: true
381 |
382 | /@vitest/spy@0.34.5:
383 | resolution: {integrity: sha512-epsicsfhvBjRjCMOC/3k00mP/TBGQy8/P0DxOFiWyLt55gnZ99dqCfCiAsKO17BWVjn4eZRIjKvcqNmSz8gvmg==}
384 | dependencies:
385 | tinyspy: 2.1.1
386 | dev: true
387 |
388 | /@vitest/utils@0.34.5:
389 | resolution: {integrity: sha512-ur6CmmYQoeHMwmGb0v+qwkwN3yopZuZyf4xt1DBBSGBed8Hf9Gmbm/5dEWqgpLPdRx6Av6jcWXrjcKfkTzg/pw==}
390 | dependencies:
391 | diff-sequences: 29.6.3
392 | loupe: 2.3.6
393 | pretty-format: 29.7.0
394 | dev: true
395 |
396 | /acorn-walk@8.2.0:
397 | resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
398 | engines: {node: '>=0.4.0'}
399 | dev: true
400 |
401 | /acorn@8.10.0:
402 | resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
403 | engines: {node: '>=0.4.0'}
404 | hasBin: true
405 | dev: true
406 |
407 | /ansi-styles@4.3.0:
408 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
409 | engines: {node: '>=8'}
410 | dependencies:
411 | color-convert: 2.0.1
412 | dev: false
413 |
414 | /ansi-styles@5.2.0:
415 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
416 | engines: {node: '>=10'}
417 | dev: true
418 |
419 | /assertion-error@1.1.0:
420 | resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
421 | dev: true
422 |
423 | /async@3.2.4:
424 | resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
425 | dev: false
426 |
427 | /asynckit@0.4.0:
428 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
429 | dev: false
430 |
431 | /balanced-match@1.0.2:
432 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
433 | dev: false
434 |
435 | /big-integer@1.6.51:
436 | resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
437 | engines: {node: '>=0.6'}
438 | dev: false
439 |
440 | /brace-expansion@1.1.11:
441 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
442 | dependencies:
443 | balanced-match: 1.0.2
444 | concat-map: 0.0.1
445 | dev: false
446 |
447 | /brace-expansion@2.0.1:
448 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
449 | dependencies:
450 | balanced-match: 1.0.2
451 | dev: false
452 |
453 | /cac@6.7.14:
454 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
455 | engines: {node: '>=8'}
456 | dev: true
457 |
458 | /chai@4.3.8:
459 | resolution: {integrity: sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==}
460 | engines: {node: '>=4'}
461 | dependencies:
462 | assertion-error: 1.1.0
463 | check-error: 1.0.2
464 | deep-eql: 4.1.3
465 | get-func-name: 2.0.2
466 | loupe: 2.3.6
467 | pathval: 1.1.1
468 | type-detect: 4.0.8
469 | dev: true
470 |
471 | /chalk@4.1.2:
472 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
473 | engines: {node: '>=10'}
474 | dependencies:
475 | ansi-styles: 4.3.0
476 | supports-color: 7.2.0
477 | dev: false
478 |
479 | /check-error@1.0.2:
480 | resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
481 | dev: true
482 |
483 | /color-convert@2.0.1:
484 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
485 | engines: {node: '>=7.0.0'}
486 | dependencies:
487 | color-name: 1.1.4
488 | dev: false
489 |
490 | /color-name@1.1.4:
491 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
492 | dev: false
493 |
494 | /combined-stream@1.0.8:
495 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
496 | engines: {node: '>= 0.8'}
497 | dependencies:
498 | delayed-stream: 1.0.0
499 | dev: false
500 |
501 | /concat-map@0.0.1:
502 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
503 | dev: false
504 |
505 | /data-uri-to-buffer@4.0.1:
506 | resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
507 | engines: {node: '>= 12'}
508 | dev: false
509 |
510 | /debug@4.3.4:
511 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
512 | engines: {node: '>=6.0'}
513 | peerDependencies:
514 | supports-color: '*'
515 | peerDependenciesMeta:
516 | supports-color:
517 | optional: true
518 | dependencies:
519 | ms: 2.1.2
520 | dev: true
521 |
522 | /deep-eql@4.1.3:
523 | resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
524 | engines: {node: '>=6'}
525 | dependencies:
526 | type-detect: 4.0.8
527 | dev: true
528 |
529 | /delayed-stream@1.0.0:
530 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
531 | engines: {node: '>=0.4.0'}
532 | dev: false
533 |
534 | /detect-libc@2.0.2:
535 | resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
536 | engines: {node: '>=8'}
537 | dev: false
538 |
539 | /diff-sequences@29.6.3:
540 | resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
541 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
542 | dev: true
543 |
544 | /ejs@3.1.9:
545 | resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==}
546 | engines: {node: '>=0.10.0'}
547 | hasBin: true
548 | dependencies:
549 | jake: 10.8.7
550 | dev: false
551 |
552 | /esbuild@0.18.20:
553 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
554 | engines: {node: '>=12'}
555 | hasBin: true
556 | requiresBuild: true
557 | optionalDependencies:
558 | '@esbuild/android-arm': 0.18.20
559 | '@esbuild/android-arm64': 0.18.20
560 | '@esbuild/android-x64': 0.18.20
561 | '@esbuild/darwin-arm64': 0.18.20
562 | '@esbuild/darwin-x64': 0.18.20
563 | '@esbuild/freebsd-arm64': 0.18.20
564 | '@esbuild/freebsd-x64': 0.18.20
565 | '@esbuild/linux-arm': 0.18.20
566 | '@esbuild/linux-arm64': 0.18.20
567 | '@esbuild/linux-ia32': 0.18.20
568 | '@esbuild/linux-loong64': 0.18.20
569 | '@esbuild/linux-mips64el': 0.18.20
570 | '@esbuild/linux-ppc64': 0.18.20
571 | '@esbuild/linux-riscv64': 0.18.20
572 | '@esbuild/linux-s390x': 0.18.20
573 | '@esbuild/linux-x64': 0.18.20
574 | '@esbuild/netbsd-x64': 0.18.20
575 | '@esbuild/openbsd-x64': 0.18.20
576 | '@esbuild/sunos-x64': 0.18.20
577 | '@esbuild/win32-arm64': 0.18.20
578 | '@esbuild/win32-ia32': 0.18.20
579 | '@esbuild/win32-x64': 0.18.20
580 | dev: true
581 |
582 | /fetch-blob@3.2.0:
583 | resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
584 | engines: {node: ^12.20 || >= 14.13}
585 | dependencies:
586 | node-domexception: 1.0.0
587 | web-streams-polyfill: 3.2.1
588 | dev: false
589 |
590 | /filelist@1.0.4:
591 | resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
592 | dependencies:
593 | minimatch: 5.1.6
594 | dev: false
595 |
596 | /form-data@4.0.0:
597 | resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
598 | engines: {node: '>= 6'}
599 | dependencies:
600 | asynckit: 0.4.0
601 | combined-stream: 1.0.8
602 | mime-types: 2.1.35
603 | dev: false
604 |
605 | /formdata-polyfill@4.0.10:
606 | resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
607 | engines: {node: '>=12.20.0'}
608 | dependencies:
609 | fetch-blob: 3.2.0
610 | dev: false
611 |
612 | /fsevents@2.3.3:
613 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
614 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
615 | os: [darwin]
616 | requiresBuild: true
617 | dev: true
618 | optional: true
619 |
620 | /get-func-name@2.0.2:
621 | resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
622 | dev: true
623 |
624 | /has-flag@4.0.0:
625 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
626 | engines: {node: '>=8'}
627 | dev: false
628 |
629 | /isomorphic.js@0.2.5:
630 | resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
631 | dev: false
632 |
633 | /jake@10.8.7:
634 | resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==}
635 | engines: {node: '>=10'}
636 | hasBin: true
637 | dependencies:
638 | async: 3.2.4
639 | chalk: 4.1.2
640 | filelist: 1.0.4
641 | minimatch: 3.1.2
642 | dev: false
643 |
644 | /js-base64@3.7.5:
645 | resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==}
646 | dev: false
647 |
648 | /jsonc-parser@3.2.0:
649 | resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
650 | dev: true
651 |
652 | /lib0@0.2.86:
653 | resolution: {integrity: sha512-kxigQTM4Q7NwJkEgdqQvU21qiR37twcqqLmh+/SbiGbRLfPlLVbHyY9sWp7PwXh0Xus9ELDSjsUOwcrdt5yZ4w==}
654 | engines: {node: '>=16'}
655 | hasBin: true
656 | dependencies:
657 | isomorphic.js: 0.2.5
658 | dev: false
659 |
660 | /libsql@0.1.23:
661 | resolution: {integrity: sha512-Nf/1B2Glxvcnba4jYFhXcaYmicyBA3RRm0LVwBkTl8UWCIDbX+Ad7c1ecrQwixPLPffWOVxKIqyCNTuUHUkVgA==}
662 | cpu: [x64, arm64]
663 | os: [darwin, linux, win32]
664 | dependencies:
665 | '@neon-rs/load': 0.0.4
666 | detect-libc: 2.0.2
667 | optionalDependencies:
668 | '@libsql/darwin-arm64': 0.1.23
669 | '@libsql/darwin-x64': 0.1.23
670 | '@libsql/linux-x64-gnu': 0.1.23
671 | '@libsql/linux-x64-musl': 0.1.23
672 | '@libsql/win32-x64-msvc': 0.1.23
673 | dev: false
674 |
675 | /local-pkg@0.4.3:
676 | resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
677 | engines: {node: '>=14'}
678 | dev: true
679 |
680 | /loupe@2.3.6:
681 | resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
682 | dependencies:
683 | get-func-name: 2.0.2
684 | dev: true
685 |
686 | /magic-string@0.30.3:
687 | resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==}
688 | engines: {node: '>=12'}
689 | dependencies:
690 | '@jridgewell/sourcemap-codec': 1.4.15
691 | dev: true
692 |
693 | /mime-db@1.52.0:
694 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
695 | engines: {node: '>= 0.6'}
696 | dev: false
697 |
698 | /mime-types@2.1.35:
699 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
700 | engines: {node: '>= 0.6'}
701 | dependencies:
702 | mime-db: 1.52.0
703 | dev: false
704 |
705 | /minimatch@3.1.2:
706 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
707 | dependencies:
708 | brace-expansion: 1.1.11
709 | dev: false
710 |
711 | /minimatch@5.1.6:
712 | resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
713 | engines: {node: '>=10'}
714 | dependencies:
715 | brace-expansion: 2.0.1
716 | dev: false
717 |
718 | /mlly@1.4.2:
719 | resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
720 | dependencies:
721 | acorn: 8.10.0
722 | pathe: 1.1.1
723 | pkg-types: 1.0.3
724 | ufo: 1.3.0
725 | dev: true
726 |
727 | /ms@2.1.2:
728 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
729 | dev: true
730 |
731 | /nanoid@3.3.6:
732 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
733 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
734 | hasBin: true
735 | dev: true
736 |
737 | /node-domexception@1.0.0:
738 | resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
739 | engines: {node: '>=10.5.0'}
740 | dev: false
741 |
742 | /node-fetch@2.7.0:
743 | resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
744 | engines: {node: 4.x || >=6.0.0}
745 | peerDependencies:
746 | encoding: ^0.1.0
747 | peerDependenciesMeta:
748 | encoding:
749 | optional: true
750 | dependencies:
751 | whatwg-url: 5.0.0
752 | dev: false
753 |
754 | /node-fetch@3.3.2:
755 | resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
756 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
757 | dependencies:
758 | data-uri-to-buffer: 4.0.1
759 | fetch-blob: 3.2.0
760 | formdata-polyfill: 4.0.10
761 | dev: false
762 |
763 | /node-sql-parser@4.11.0:
764 | resolution: {integrity: sha512-ElheoPibjc7IVyRdsORgkzJi0DWm3f0LYSsm/eJIeUt3M/csDLTblLDR4zl5Qi7jmVjJ1KpEkPKSbgVGEzU5Xw==}
765 | engines: {node: '>=8'}
766 | dependencies:
767 | big-integer: 1.6.51
768 | dev: false
769 |
770 | /p-limit@4.0.0:
771 | resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
772 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
773 | dependencies:
774 | yocto-queue: 1.0.0
775 | dev: true
776 |
777 | /pathe@1.1.1:
778 | resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
779 | dev: true
780 |
781 | /pathval@1.1.1:
782 | resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
783 | dev: true
784 |
785 | /picocolors@1.0.0:
786 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
787 | dev: true
788 |
789 | /pkg-types@1.0.3:
790 | resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
791 | dependencies:
792 | jsonc-parser: 3.2.0
793 | mlly: 1.4.2
794 | pathe: 1.1.1
795 | dev: true
796 |
797 | /postcss@8.4.30:
798 | resolution: {integrity: sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==}
799 | engines: {node: ^10 || ^12 || >=14}
800 | dependencies:
801 | nanoid: 3.3.6
802 | picocolors: 1.0.0
803 | source-map-js: 1.0.2
804 | dev: true
805 |
806 | /pretty-format@29.7.0:
807 | resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
808 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
809 | dependencies:
810 | '@jest/schemas': 29.6.3
811 | ansi-styles: 5.2.0
812 | react-is: 18.2.0
813 | dev: true
814 |
815 | /react-is@18.2.0:
816 | resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
817 | dev: true
818 |
819 | /rollup@3.29.3:
820 | resolution: {integrity: sha512-T7du6Hum8jOkSWetjRgbwpM6Sy0nECYrYRSmZjayFcOddtKJWU4d17AC3HNUk7HRuqy4p+G7aEZclSHytqUmEg==}
821 | engines: {node: '>=14.18.0', npm: '>=8.0.0'}
822 | hasBin: true
823 | optionalDependencies:
824 | fsevents: 2.3.3
825 | dev: true
826 |
827 | /siginfo@2.0.0:
828 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
829 | dev: true
830 |
831 | /source-map-js@1.0.2:
832 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
833 | engines: {node: '>=0.10.0'}
834 | dev: true
835 |
836 | /stackback@0.0.2:
837 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
838 | dev: true
839 |
840 | /std-env@3.4.3:
841 | resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==}
842 | dev: true
843 |
844 | /strip-literal@1.3.0:
845 | resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==}
846 | dependencies:
847 | acorn: 8.10.0
848 | dev: true
849 |
850 | /supports-color@7.2.0:
851 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
852 | engines: {node: '>=8'}
853 | dependencies:
854 | has-flag: 4.0.0
855 | dev: false
856 |
857 | /tinybench@2.5.1:
858 | resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==}
859 | dev: true
860 |
861 | /tinypool@0.7.0:
862 | resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==}
863 | engines: {node: '>=14.0.0'}
864 | dev: true
865 |
866 | /tinyspy@2.1.1:
867 | resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==}
868 | engines: {node: '>=14.0.0'}
869 | dev: true
870 |
871 | /tr46@0.0.3:
872 | resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
873 | dev: false
874 |
875 | /type-detect@4.0.8:
876 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
877 | engines: {node: '>=4'}
878 | dev: true
879 |
880 | /ufo@1.3.0:
881 | resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==}
882 | dev: true
883 |
884 | /uuid@9.0.1:
885 | resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
886 | hasBin: true
887 | dev: false
888 |
889 | /vite-node@0.34.5(@types/node@20.7.0):
890 | resolution: {integrity: sha512-RNZ+DwbCvDoI5CbCSQSyRyzDTfFvFauvMs6Yq4ObJROKlIKuat1KgSX/Ako5rlDMfVCyMcpMRMTkJBxd6z8YRA==}
891 | engines: {node: '>=v14.18.0'}
892 | hasBin: true
893 | dependencies:
894 | cac: 6.7.14
895 | debug: 4.3.4
896 | mlly: 1.4.2
897 | pathe: 1.1.1
898 | picocolors: 1.0.0
899 | vite: 4.4.9(@types/node@20.7.0)
900 | transitivePeerDependencies:
901 | - '@types/node'
902 | - less
903 | - lightningcss
904 | - sass
905 | - stylus
906 | - sugarss
907 | - supports-color
908 | - terser
909 | dev: true
910 |
911 | /vite@4.4.9(@types/node@20.7.0):
912 | resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
913 | engines: {node: ^14.18.0 || >=16.0.0}
914 | hasBin: true
915 | peerDependencies:
916 | '@types/node': '>= 14'
917 | less: '*'
918 | lightningcss: ^1.21.0
919 | sass: '*'
920 | stylus: '*'
921 | sugarss: '*'
922 | terser: ^5.4.0
923 | peerDependenciesMeta:
924 | '@types/node':
925 | optional: true
926 | less:
927 | optional: true
928 | lightningcss:
929 | optional: true
930 | sass:
931 | optional: true
932 | stylus:
933 | optional: true
934 | sugarss:
935 | optional: true
936 | terser:
937 | optional: true
938 | dependencies:
939 | '@types/node': 20.7.0
940 | esbuild: 0.18.20
941 | postcss: 8.4.30
942 | rollup: 3.29.3
943 | optionalDependencies:
944 | fsevents: 2.3.3
945 | dev: true
946 |
947 | /vitest@0.34.5:
948 | resolution: {integrity: sha512-CPI68mmnr2DThSB3frSuE5RLm9wo5wU4fbDrDwWQQB1CWgq9jQVoQwnQSzYAjdoBOPoH2UtXpOgHVge/uScfZg==}
949 | engines: {node: '>=v14.18.0'}
950 | hasBin: true
951 | peerDependencies:
952 | '@edge-runtime/vm': '*'
953 | '@vitest/browser': '*'
954 | '@vitest/ui': '*'
955 | happy-dom: '*'
956 | jsdom: '*'
957 | playwright: '*'
958 | safaridriver: '*'
959 | webdriverio: '*'
960 | peerDependenciesMeta:
961 | '@edge-runtime/vm':
962 | optional: true
963 | '@vitest/browser':
964 | optional: true
965 | '@vitest/ui':
966 | optional: true
967 | happy-dom:
968 | optional: true
969 | jsdom:
970 | optional: true
971 | playwright:
972 | optional: true
973 | safaridriver:
974 | optional: true
975 | webdriverio:
976 | optional: true
977 | dependencies:
978 | '@types/chai': 4.3.6
979 | '@types/chai-subset': 1.3.3
980 | '@types/node': 20.7.0
981 | '@vitest/expect': 0.34.5
982 | '@vitest/runner': 0.34.5
983 | '@vitest/snapshot': 0.34.5
984 | '@vitest/spy': 0.34.5
985 | '@vitest/utils': 0.34.5
986 | acorn: 8.10.0
987 | acorn-walk: 8.2.0
988 | cac: 6.7.14
989 | chai: 4.3.8
990 | debug: 4.3.4
991 | local-pkg: 0.4.3
992 | magic-string: 0.30.3
993 | pathe: 1.1.1
994 | picocolors: 1.0.0
995 | std-env: 3.4.3
996 | strip-literal: 1.3.0
997 | tinybench: 2.5.1
998 | tinypool: 0.7.0
999 | vite: 4.4.9(@types/node@20.7.0)
1000 | vite-node: 0.34.5(@types/node@20.7.0)
1001 | why-is-node-running: 2.2.2
1002 | transitivePeerDependencies:
1003 | - less
1004 | - lightningcss
1005 | - sass
1006 | - stylus
1007 | - sugarss
1008 | - supports-color
1009 | - terser
1010 | dev: true
1011 |
1012 | /web-streams-polyfill@3.2.1:
1013 | resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
1014 | engines: {node: '>= 8'}
1015 | dev: false
1016 |
1017 | /webidl-conversions@3.0.1:
1018 | resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
1019 | dev: false
1020 |
1021 | /whatwg-url@5.0.0:
1022 | resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
1023 | dependencies:
1024 | tr46: 0.0.3
1025 | webidl-conversions: 3.0.1
1026 | dev: false
1027 |
1028 | /why-is-node-running@2.2.2:
1029 | resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
1030 | engines: {node: '>=8'}
1031 | hasBin: true
1032 | dependencies:
1033 | siginfo: 2.0.0
1034 | stackback: 0.0.2
1035 | dev: true
1036 |
1037 | /ws@8.14.2:
1038 | resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
1039 | engines: {node: '>=10.0.0'}
1040 | peerDependencies:
1041 | bufferutil: ^4.0.1
1042 | utf-8-validate: '>=5.0.2'
1043 | peerDependenciesMeta:
1044 | bufferutil:
1045 | optional: true
1046 | utf-8-validate:
1047 | optional: true
1048 | dev: false
1049 |
1050 | /yjs@13.6.8:
1051 | resolution: {integrity: sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==}
1052 | engines: {node: '>=16.0.0', npm: '>=8.0.0'}
1053 | dependencies:
1054 | lib0: 0.2.86
1055 | dev: false
1056 |
1057 | /yocto-queue@1.0.0:
1058 | resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
1059 | engines: {node: '>=12.20'}
1060 | dev: true
1061 |
--------------------------------------------------------------------------------
/server/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '6.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | dependencies:
8 | '@coffeeandfun/google-profanity-words':
9 | specifier: ^2.1.0
10 | version: 2.1.0
11 | '@libsql/client':
12 | specifier: ^0.3.5
13 | version: 0.3.5
14 | '@trpc/server':
15 | specifier: ^10.38.5
16 | version: 10.38.5
17 | cors:
18 | specifier: ^2.8.5
19 | version: 2.8.5
20 | express:
21 | specifier: ^4.18.2
22 | version: 4.18.2
23 | fs-extra:
24 | specifier: ^11.1.1
25 | version: 11.1.1
26 | node-sql-parser:
27 | specifier: ^4.11.0
28 | version: 4.11.0
29 | situated:
30 | specifier: ^0.0.1
31 | version: 0.0.1(react-dom@18.2.0)(react@18.2.0)
32 | trpc-yjs:
33 | specifier: ^0.0.6
34 | version: 0.0.6(@trpc/client@10.38.5)(@trpc/server@10.38.5)(yjs@13.6.8)
35 | tsx:
36 | specifier: ^3.13.0
37 | version: 3.13.0
38 | typescript:
39 | specifier: ^5.0.0
40 | version: 5.2.2
41 | ws:
42 | specifier: ^8.14.2
43 | version: 8.14.2
44 | yjs:
45 | specifier: ^13.6.8
46 | version: 13.6.8
47 | zod:
48 | specifier: ^3.22.2
49 | version: 3.22.2
50 |
51 | devDependencies:
52 | bun-types:
53 | specifier: latest
54 | version: 1.0.3
55 | vitest:
56 | specifier: ^0.34.5
57 | version: 0.34.5
58 |
59 | packages:
60 |
61 | /@coffeeandfun/google-profanity-words@2.1.0:
62 | resolution: {integrity: sha512-j7/N2C01g5W22ReQ4laVR5MdTFRidDLkVY9z6z9Mresla83sd9vQalMXypQnT1+RuHErEfGIYNm5/Hu/LF/zzA==}
63 | dev: false
64 |
65 | /@esbuild/android-arm64@0.18.20:
66 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
67 | engines: {node: '>=12'}
68 | cpu: [arm64]
69 | os: [android]
70 | requiresBuild: true
71 | optional: true
72 |
73 | /@esbuild/android-arm@0.18.20:
74 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
75 | engines: {node: '>=12'}
76 | cpu: [arm]
77 | os: [android]
78 | requiresBuild: true
79 | optional: true
80 |
81 | /@esbuild/android-x64@0.18.20:
82 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
83 | engines: {node: '>=12'}
84 | cpu: [x64]
85 | os: [android]
86 | requiresBuild: true
87 | optional: true
88 |
89 | /@esbuild/darwin-arm64@0.18.20:
90 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
91 | engines: {node: '>=12'}
92 | cpu: [arm64]
93 | os: [darwin]
94 | requiresBuild: true
95 | optional: true
96 |
97 | /@esbuild/darwin-x64@0.18.20:
98 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
99 | engines: {node: '>=12'}
100 | cpu: [x64]
101 | os: [darwin]
102 | requiresBuild: true
103 | optional: true
104 |
105 | /@esbuild/freebsd-arm64@0.18.20:
106 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
107 | engines: {node: '>=12'}
108 | cpu: [arm64]
109 | os: [freebsd]
110 | requiresBuild: true
111 | optional: true
112 |
113 | /@esbuild/freebsd-x64@0.18.20:
114 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
115 | engines: {node: '>=12'}
116 | cpu: [x64]
117 | os: [freebsd]
118 | requiresBuild: true
119 | optional: true
120 |
121 | /@esbuild/linux-arm64@0.18.20:
122 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
123 | engines: {node: '>=12'}
124 | cpu: [arm64]
125 | os: [linux]
126 | requiresBuild: true
127 | optional: true
128 |
129 | /@esbuild/linux-arm@0.18.20:
130 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
131 | engines: {node: '>=12'}
132 | cpu: [arm]
133 | os: [linux]
134 | requiresBuild: true
135 | optional: true
136 |
137 | /@esbuild/linux-ia32@0.18.20:
138 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
139 | engines: {node: '>=12'}
140 | cpu: [ia32]
141 | os: [linux]
142 | requiresBuild: true
143 | optional: true
144 |
145 | /@esbuild/linux-loong64@0.18.20:
146 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
147 | engines: {node: '>=12'}
148 | cpu: [loong64]
149 | os: [linux]
150 | requiresBuild: true
151 | optional: true
152 |
153 | /@esbuild/linux-mips64el@0.18.20:
154 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
155 | engines: {node: '>=12'}
156 | cpu: [mips64el]
157 | os: [linux]
158 | requiresBuild: true
159 | optional: true
160 |
161 | /@esbuild/linux-ppc64@0.18.20:
162 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
163 | engines: {node: '>=12'}
164 | cpu: [ppc64]
165 | os: [linux]
166 | requiresBuild: true
167 | optional: true
168 |
169 | /@esbuild/linux-riscv64@0.18.20:
170 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
171 | engines: {node: '>=12'}
172 | cpu: [riscv64]
173 | os: [linux]
174 | requiresBuild: true
175 | optional: true
176 |
177 | /@esbuild/linux-s390x@0.18.20:
178 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
179 | engines: {node: '>=12'}
180 | cpu: [s390x]
181 | os: [linux]
182 | requiresBuild: true
183 | optional: true
184 |
185 | /@esbuild/linux-x64@0.18.20:
186 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
187 | engines: {node: '>=12'}
188 | cpu: [x64]
189 | os: [linux]
190 | requiresBuild: true
191 | optional: true
192 |
193 | /@esbuild/netbsd-x64@0.18.20:
194 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
195 | engines: {node: '>=12'}
196 | cpu: [x64]
197 | os: [netbsd]
198 | requiresBuild: true
199 | optional: true
200 |
201 | /@esbuild/openbsd-x64@0.18.20:
202 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
203 | engines: {node: '>=12'}
204 | cpu: [x64]
205 | os: [openbsd]
206 | requiresBuild: true
207 | optional: true
208 |
209 | /@esbuild/sunos-x64@0.18.20:
210 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
211 | engines: {node: '>=12'}
212 | cpu: [x64]
213 | os: [sunos]
214 | requiresBuild: true
215 | optional: true
216 |
217 | /@esbuild/win32-arm64@0.18.20:
218 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
219 | engines: {node: '>=12'}
220 | cpu: [arm64]
221 | os: [win32]
222 | requiresBuild: true
223 | optional: true
224 |
225 | /@esbuild/win32-ia32@0.18.20:
226 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
227 | engines: {node: '>=12'}
228 | cpu: [ia32]
229 | os: [win32]
230 | requiresBuild: true
231 | optional: true
232 |
233 | /@esbuild/win32-x64@0.18.20:
234 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
235 | engines: {node: '>=12'}
236 | cpu: [x64]
237 | os: [win32]
238 | requiresBuild: true
239 | optional: true
240 |
241 | /@jest/schemas@29.6.3:
242 | resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
243 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
244 | dependencies:
245 | '@sinclair/typebox': 0.27.8
246 | dev: true
247 |
248 | /@jridgewell/sourcemap-codec@1.4.15:
249 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
250 | dev: true
251 |
252 | /@libsql/client@0.3.5:
253 | resolution: {integrity: sha512-4fZxGh0qKW5dtp1yuQLRvRAtbt02V4jzjM9sHSmz5k25xZTLg7/GlNudKdqKZrjJXEV5PvDNsczupBtedZZovw==}
254 | dependencies:
255 | '@libsql/hrana-client': 0.5.5
256 | js-base64: 3.7.5
257 | libsql: 0.1.23
258 | transitivePeerDependencies:
259 | - bufferutil
260 | - encoding
261 | - utf-8-validate
262 | dev: false
263 |
264 | /@libsql/darwin-arm64@0.1.23:
265 | resolution: {integrity: sha512-+V9aoOrZ47iYbY5NrcS0F2bDOCH407QI0wxAtss0CLOcFxlz/T6Nw0ryLK31GabklJQAmOXIyqkumLfz5HT64w==}
266 | cpu: [arm64]
267 | os: [darwin]
268 | requiresBuild: true
269 | dev: false
270 | optional: true
271 |
272 | /@libsql/darwin-x64@0.1.23:
273 | resolution: {integrity: sha512-toHo7s0HiMl4VCIfjhGXDe9bGWWo78eP8fxIbwU6RlaLO6MNV9fjHY/GjTWccWOwyxcT+q6X/kUc957HnoW3bg==}
274 | cpu: [x64]
275 | os: [darwin]
276 | requiresBuild: true
277 | dev: false
278 | optional: true
279 |
280 | /@libsql/hrana-client@0.5.5:
281 | resolution: {integrity: sha512-i+hDBpiV719poqEiHupUUZYKJ9YSbCRFe5Q2PQ0v3mHIftePH6gayLjp2u6TXbqbO/Dv6y8yyvYlBXf/kFfRZA==}
282 | dependencies:
283 | '@libsql/isomorphic-fetch': 0.1.10
284 | '@libsql/isomorphic-ws': 0.1.5
285 | js-base64: 3.7.5
286 | node-fetch: 3.3.2
287 | transitivePeerDependencies:
288 | - bufferutil
289 | - encoding
290 | - utf-8-validate
291 | dev: false
292 |
293 | /@libsql/isomorphic-fetch@0.1.10:
294 | resolution: {integrity: sha512-dH0lMk50gKSvEKD78xWMu60SY1sjp1sY//iFLO0XMmBwfVfG136P9KOk06R4maBdlb8KMXOzJ1D28FR5ZKnHTA==}
295 | dependencies:
296 | '@types/node-fetch': 2.6.6
297 | node-fetch: 2.7.0
298 | transitivePeerDependencies:
299 | - encoding
300 | dev: false
301 |
302 | /@libsql/isomorphic-ws@0.1.5:
303 | resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==}
304 | dependencies:
305 | '@types/ws': 8.5.6
306 | ws: 8.14.2
307 | transitivePeerDependencies:
308 | - bufferutil
309 | - utf-8-validate
310 | dev: false
311 |
312 | /@libsql/linux-x64-gnu@0.1.23:
313 | resolution: {integrity: sha512-U11LdjayakOj0lQCHDYkTgUfe4Q+7AjZZh8MzgEDF/9l0bmKNI3eFLWA3JD2Xm98yz65lUx95om0WKOKu5VW/w==}
314 | cpu: [x64]
315 | os: [linux]
316 | requiresBuild: true
317 | dev: false
318 | optional: true
319 |
320 | /@libsql/linux-x64-musl@0.1.23:
321 | resolution: {integrity: sha512-8UcCK2sPVzcafHsEmcU5IDp/NxjD6F6JFS5giijsMX5iGgxYQiiwTUMOmSxW0AWBeT4VY5U7G6rG5PC8JSFtfg==}
322 | cpu: [x64]
323 | os: [linux]
324 | requiresBuild: true
325 | dev: false
326 | optional: true
327 |
328 | /@libsql/win32-x64-msvc@0.1.23:
329 | resolution: {integrity: sha512-HAugD66jTmRRRGNMLKRiaFeMOC3mgUsAiuO6NRdRz3nM6saf9e5QqN/Ppuu9yqHHcZfv7VhQ9UGlAvzVK64Itg==}
330 | cpu: [x64]
331 | os: [win32]
332 | requiresBuild: true
333 | dev: false
334 | optional: true
335 |
336 | /@lmdb/lmdb-darwin-arm64@2.8.5:
337 | resolution: {integrity: sha512-KPDeVScZgA1oq0CiPBcOa3kHIqU+pTOwRFDIhxvmf8CTNvqdZQYp5cCKW0bUk69VygB2PuTiINFWbY78aR2pQw==}
338 | cpu: [arm64]
339 | os: [darwin]
340 | requiresBuild: true
341 | dev: false
342 | optional: true
343 |
344 | /@lmdb/lmdb-darwin-x64@2.8.5:
345 | resolution: {integrity: sha512-w/sLhN4T7MW1nB3R/U8WK5BgQLz904wh+/SmA2jD8NnF7BLLoUgflCNxOeSPOWp8geP6nP/+VjWzZVip7rZ1ug==}
346 | cpu: [x64]
347 | os: [darwin]
348 | requiresBuild: true
349 | dev: false
350 | optional: true
351 |
352 | /@lmdb/lmdb-linux-arm64@2.8.5:
353 | resolution: {integrity: sha512-vtbZRHH5UDlL01TT5jB576Zox3+hdyogvpcbvVJlmU5PdL3c5V7cj1EODdh1CHPksRl+cws/58ugEHi8bcj4Ww==}
354 | cpu: [arm64]
355 | os: [linux]
356 | requiresBuild: true
357 | dev: false
358 | optional: true
359 |
360 | /@lmdb/lmdb-linux-arm@2.8.5:
361 | resolution: {integrity: sha512-c0TGMbm2M55pwTDIfkDLB6BpIsgxV4PjYck2HiOX+cy/JWiBXz32lYbarPqejKs9Flm7YVAKSILUducU9g2RVg==}
362 | cpu: [arm]
363 | os: [linux]
364 | requiresBuild: true
365 | dev: false
366 | optional: true
367 |
368 | /@lmdb/lmdb-linux-x64@2.8.5:
369 | resolution: {integrity: sha512-Xkc8IUx9aEhP0zvgeKy7IQ3ReX2N8N1L0WPcQwnZweWmOuKfwpS3GRIYqLtK5za/w3E60zhFfNdS+3pBZPytqQ==}
370 | cpu: [x64]
371 | os: [linux]
372 | requiresBuild: true
373 | dev: false
374 | optional: true
375 |
376 | /@lmdb/lmdb-win32-x64@2.8.5:
377 | resolution: {integrity: sha512-4wvrf5BgnR8RpogHhtpCPJMKBmvyZPhhUtEwMJbXh0ni2BucpfF07jlmyM11zRqQ2XIq6PbC2j7W7UCCcm1rRQ==}
378 | cpu: [x64]
379 | os: [win32]
380 | requiresBuild: true
381 | dev: false
382 | optional: true
383 |
384 | /@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2:
385 | resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
386 | cpu: [arm64]
387 | os: [darwin]
388 | requiresBuild: true
389 | dev: false
390 | optional: true
391 |
392 | /@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2:
393 | resolution: {integrity: sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==}
394 | cpu: [x64]
395 | os: [darwin]
396 | requiresBuild: true
397 | dev: false
398 | optional: true
399 |
400 | /@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2:
401 | resolution: {integrity: sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==}
402 | cpu: [arm64]
403 | os: [linux]
404 | requiresBuild: true
405 | dev: false
406 | optional: true
407 |
408 | /@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2:
409 | resolution: {integrity: sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==}
410 | cpu: [arm]
411 | os: [linux]
412 | requiresBuild: true
413 | dev: false
414 | optional: true
415 |
416 | /@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2:
417 | resolution: {integrity: sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==}
418 | cpu: [x64]
419 | os: [linux]
420 | requiresBuild: true
421 | dev: false
422 | optional: true
423 |
424 | /@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2:
425 | resolution: {integrity: sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==}
426 | cpu: [x64]
427 | os: [win32]
428 | requiresBuild: true
429 | dev: false
430 | optional: true
431 |
432 | /@neon-rs/load@0.0.4:
433 | resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==}
434 | dev: false
435 |
436 | /@sinclair/typebox@0.27.8:
437 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
438 | dev: true
439 |
440 | /@trpc/client@10.38.5(@trpc/server@10.38.5):
441 | resolution: {integrity: sha512-tpGUsoAP+3CD/1KRqMdWZ+zebvB68/86SaVPAYHaEDozTFLQdNqTe98DS/T0S4hfh7WCKbMSObj40SCzE8amKQ==}
442 | peerDependencies:
443 | '@trpc/server': 10.38.5
444 | dependencies:
445 | '@trpc/server': 10.38.5
446 | dev: false
447 |
448 | /@trpc/server@10.38.5:
449 | resolution: {integrity: sha512-J0d2Y3Gpt2bMohOshPBfuzDqVrPaE3OKEDtJYgTmLk5t1pZy3kXHQep4rP2LEIr+ELbmkelhcrSvvFLA+4/w/Q==}
450 | dev: false
451 |
452 | /@types/chai-subset@1.3.3:
453 | resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
454 | dependencies:
455 | '@types/chai': 4.3.6
456 | dev: true
457 |
458 | /@types/chai@4.3.6:
459 | resolution: {integrity: sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==}
460 | dev: true
461 |
462 | /@types/node-fetch@2.6.6:
463 | resolution: {integrity: sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==}
464 | dependencies:
465 | '@types/node': 20.7.0
466 | form-data: 4.0.0
467 | dev: false
468 |
469 | /@types/node@20.7.0:
470 | resolution: {integrity: sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==}
471 |
472 | /@types/ws@8.5.6:
473 | resolution: {integrity: sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==}
474 | dependencies:
475 | '@types/node': 20.7.0
476 | dev: false
477 |
478 | /@vitest/expect@0.34.5:
479 | resolution: {integrity: sha512-/3RBIV9XEH+nRpRMqDJBufKIOQaYUH2X6bt0rKSCW0MfKhXFLYsR5ivHifeajRSTsln0FwJbitxLKHSQz/Xwkw==}
480 | dependencies:
481 | '@vitest/spy': 0.34.5
482 | '@vitest/utils': 0.34.5
483 | chai: 4.3.8
484 | dev: true
485 |
486 | /@vitest/runner@0.34.5:
487 | resolution: {integrity: sha512-RDEE3ViVvl7jFSCbnBRyYuu23XxmvRTSZWW6W4M7eC5dOsK75d5LIf6uhE5Fqf809DQ1+9ICZZNxhIolWHU4og==}
488 | dependencies:
489 | '@vitest/utils': 0.34.5
490 | p-limit: 4.0.0
491 | pathe: 1.1.1
492 | dev: true
493 |
494 | /@vitest/snapshot@0.34.5:
495 | resolution: {integrity: sha512-+ikwSbhu6z2yOdtKmk/aeoDZ9QPm2g/ZO5rXT58RR9Vmu/kB2MamyDSx77dctqdZfP3Diqv4mbc/yw2kPT8rmA==}
496 | dependencies:
497 | magic-string: 0.30.3
498 | pathe: 1.1.1
499 | pretty-format: 29.7.0
500 | dev: true
501 |
502 | /@vitest/spy@0.34.5:
503 | resolution: {integrity: sha512-epsicsfhvBjRjCMOC/3k00mP/TBGQy8/P0DxOFiWyLt55gnZ99dqCfCiAsKO17BWVjn4eZRIjKvcqNmSz8gvmg==}
504 | dependencies:
505 | tinyspy: 2.1.1
506 | dev: true
507 |
508 | /@vitest/utils@0.34.5:
509 | resolution: {integrity: sha512-ur6CmmYQoeHMwmGb0v+qwkwN3yopZuZyf4xt1DBBSGBed8Hf9Gmbm/5dEWqgpLPdRx6Av6jcWXrjcKfkTzg/pw==}
510 | dependencies:
511 | diff-sequences: 29.6.3
512 | loupe: 2.3.6
513 | pretty-format: 29.7.0
514 | dev: true
515 |
516 | /accepts@1.3.8:
517 | resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
518 | engines: {node: '>= 0.6'}
519 | dependencies:
520 | mime-types: 2.1.35
521 | negotiator: 0.6.3
522 | dev: false
523 |
524 | /acorn-walk@8.2.0:
525 | resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
526 | engines: {node: '>=0.4.0'}
527 | dev: true
528 |
529 | /acorn@8.10.0:
530 | resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
531 | engines: {node: '>=0.4.0'}
532 | hasBin: true
533 | dev: true
534 |
535 | /ansi-styles@5.2.0:
536 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
537 | engines: {node: '>=10'}
538 | dev: true
539 |
540 | /array-flatten@1.1.1:
541 | resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
542 | dev: false
543 |
544 | /assertion-error@1.1.0:
545 | resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
546 | dev: true
547 |
548 | /asynckit@0.4.0:
549 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
550 | dev: false
551 |
552 | /big-integer@1.6.51:
553 | resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
554 | engines: {node: '>=0.6'}
555 | dev: false
556 |
557 | /body-parser@1.20.1:
558 | resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
559 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
560 | dependencies:
561 | bytes: 3.1.2
562 | content-type: 1.0.5
563 | debug: 2.6.9
564 | depd: 2.0.0
565 | destroy: 1.2.0
566 | http-errors: 2.0.0
567 | iconv-lite: 0.4.24
568 | on-finished: 2.4.1
569 | qs: 6.11.0
570 | raw-body: 2.5.1
571 | type-is: 1.6.18
572 | unpipe: 1.0.0
573 | transitivePeerDependencies:
574 | - supports-color
575 | dev: false
576 |
577 | /buffer-from@1.1.2:
578 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
579 | dev: false
580 |
581 | /bun-types@1.0.3:
582 | resolution: {integrity: sha512-XlyKVdYCHa7K5PHYGcwOVOrGE/bMnLS51y7zFA3ZAAXyiQ6dTaNXNCWTTufgII/6ruN770uhAXphQmzvU/r2fQ==}
583 | dev: true
584 |
585 | /bytes@3.1.2:
586 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
587 | engines: {node: '>= 0.8'}
588 | dev: false
589 |
590 | /cac@6.7.14:
591 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
592 | engines: {node: '>=8'}
593 | dev: true
594 |
595 | /call-bind@1.0.2:
596 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
597 | dependencies:
598 | function-bind: 1.1.1
599 | get-intrinsic: 1.2.1
600 | dev: false
601 |
602 | /chai@4.3.8:
603 | resolution: {integrity: sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==}
604 | engines: {node: '>=4'}
605 | dependencies:
606 | assertion-error: 1.1.0
607 | check-error: 1.0.2
608 | deep-eql: 4.1.3
609 | get-func-name: 2.0.0
610 | loupe: 2.3.6
611 | pathval: 1.1.1
612 | type-detect: 4.0.8
613 | dev: true
614 |
615 | /check-error@1.0.2:
616 | resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
617 | dev: true
618 |
619 | /combined-stream@1.0.8:
620 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
621 | engines: {node: '>= 0.8'}
622 | dependencies:
623 | delayed-stream: 1.0.0
624 | dev: false
625 |
626 | /content-disposition@0.5.4:
627 | resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
628 | engines: {node: '>= 0.6'}
629 | dependencies:
630 | safe-buffer: 5.2.1
631 | dev: false
632 |
633 | /content-type@1.0.5:
634 | resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
635 | engines: {node: '>= 0.6'}
636 | dev: false
637 |
638 | /cookie-signature@1.0.6:
639 | resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
640 | dev: false
641 |
642 | /cookie@0.5.0:
643 | resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
644 | engines: {node: '>= 0.6'}
645 | dev: false
646 |
647 | /cors@2.8.5:
648 | resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
649 | engines: {node: '>= 0.10'}
650 | dependencies:
651 | object-assign: 4.1.1
652 | vary: 1.1.2
653 | dev: false
654 |
655 | /data-uri-to-buffer@4.0.1:
656 | resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
657 | engines: {node: '>= 12'}
658 | dev: false
659 |
660 | /debug@2.6.9:
661 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
662 | peerDependencies:
663 | supports-color: '*'
664 | peerDependenciesMeta:
665 | supports-color:
666 | optional: true
667 | dependencies:
668 | ms: 2.0.0
669 | dev: false
670 |
671 | /debug@4.3.4:
672 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
673 | engines: {node: '>=6.0'}
674 | peerDependencies:
675 | supports-color: '*'
676 | peerDependenciesMeta:
677 | supports-color:
678 | optional: true
679 | dependencies:
680 | ms: 2.1.2
681 | dev: true
682 |
683 | /deep-eql@4.1.3:
684 | resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
685 | engines: {node: '>=6'}
686 | dependencies:
687 | type-detect: 4.0.8
688 | dev: true
689 |
690 | /delayed-stream@1.0.0:
691 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
692 | engines: {node: '>=0.4.0'}
693 | dev: false
694 |
695 | /depd@2.0.0:
696 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
697 | engines: {node: '>= 0.8'}
698 | dev: false
699 |
700 | /destroy@1.2.0:
701 | resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
702 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
703 | dev: false
704 |
705 | /detect-libc@2.0.2:
706 | resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
707 | engines: {node: '>=8'}
708 | dev: false
709 |
710 | /diff-sequences@29.6.3:
711 | resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
712 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
713 | dev: true
714 |
715 | /ee-first@1.1.1:
716 | resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
717 | dev: false
718 |
719 | /encodeurl@1.0.2:
720 | resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
721 | engines: {node: '>= 0.8'}
722 | dev: false
723 |
724 | /esbuild@0.18.20:
725 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
726 | engines: {node: '>=12'}
727 | hasBin: true
728 | requiresBuild: true
729 | optionalDependencies:
730 | '@esbuild/android-arm': 0.18.20
731 | '@esbuild/android-arm64': 0.18.20
732 | '@esbuild/android-x64': 0.18.20
733 | '@esbuild/darwin-arm64': 0.18.20
734 | '@esbuild/darwin-x64': 0.18.20
735 | '@esbuild/freebsd-arm64': 0.18.20
736 | '@esbuild/freebsd-x64': 0.18.20
737 | '@esbuild/linux-arm': 0.18.20
738 | '@esbuild/linux-arm64': 0.18.20
739 | '@esbuild/linux-ia32': 0.18.20
740 | '@esbuild/linux-loong64': 0.18.20
741 | '@esbuild/linux-mips64el': 0.18.20
742 | '@esbuild/linux-ppc64': 0.18.20
743 | '@esbuild/linux-riscv64': 0.18.20
744 | '@esbuild/linux-s390x': 0.18.20
745 | '@esbuild/linux-x64': 0.18.20
746 | '@esbuild/netbsd-x64': 0.18.20
747 | '@esbuild/openbsd-x64': 0.18.20
748 | '@esbuild/sunos-x64': 0.18.20
749 | '@esbuild/win32-arm64': 0.18.20
750 | '@esbuild/win32-ia32': 0.18.20
751 | '@esbuild/win32-x64': 0.18.20
752 |
753 | /escape-html@1.0.3:
754 | resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
755 | dev: false
756 |
757 | /etag@1.8.1:
758 | resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
759 | engines: {node: '>= 0.6'}
760 | dev: false
761 |
762 | /express@4.18.2:
763 | resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
764 | engines: {node: '>= 0.10.0'}
765 | dependencies:
766 | accepts: 1.3.8
767 | array-flatten: 1.1.1
768 | body-parser: 1.20.1
769 | content-disposition: 0.5.4
770 | content-type: 1.0.5
771 | cookie: 0.5.0
772 | cookie-signature: 1.0.6
773 | debug: 2.6.9
774 | depd: 2.0.0
775 | encodeurl: 1.0.2
776 | escape-html: 1.0.3
777 | etag: 1.8.1
778 | finalhandler: 1.2.0
779 | fresh: 0.5.2
780 | http-errors: 2.0.0
781 | merge-descriptors: 1.0.1
782 | methods: 1.1.2
783 | on-finished: 2.4.1
784 | parseurl: 1.3.3
785 | path-to-regexp: 0.1.7
786 | proxy-addr: 2.0.7
787 | qs: 6.11.0
788 | range-parser: 1.2.1
789 | safe-buffer: 5.2.1
790 | send: 0.18.0
791 | serve-static: 1.15.0
792 | setprototypeof: 1.2.0
793 | statuses: 2.0.1
794 | type-is: 1.6.18
795 | utils-merge: 1.0.1
796 | vary: 1.1.2
797 | transitivePeerDependencies:
798 | - supports-color
799 | dev: false
800 |
801 | /fetch-blob@3.2.0:
802 | resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
803 | engines: {node: ^12.20 || >= 14.13}
804 | dependencies:
805 | node-domexception: 1.0.0
806 | web-streams-polyfill: 3.2.1
807 | dev: false
808 |
809 | /finalhandler@1.2.0:
810 | resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
811 | engines: {node: '>= 0.8'}
812 | dependencies:
813 | debug: 2.6.9
814 | encodeurl: 1.0.2
815 | escape-html: 1.0.3
816 | on-finished: 2.4.1
817 | parseurl: 1.3.3
818 | statuses: 2.0.1
819 | unpipe: 1.0.0
820 | transitivePeerDependencies:
821 | - supports-color
822 | dev: false
823 |
824 | /form-data@4.0.0:
825 | resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
826 | engines: {node: '>= 6'}
827 | dependencies:
828 | asynckit: 0.4.0
829 | combined-stream: 1.0.8
830 | mime-types: 2.1.35
831 | dev: false
832 |
833 | /formdata-polyfill@4.0.10:
834 | resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
835 | engines: {node: '>=12.20.0'}
836 | dependencies:
837 | fetch-blob: 3.2.0
838 | dev: false
839 |
840 | /forwarded@0.2.0:
841 | resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
842 | engines: {node: '>= 0.6'}
843 | dev: false
844 |
845 | /fresh@0.5.2:
846 | resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
847 | engines: {node: '>= 0.6'}
848 | dev: false
849 |
850 | /fs-extra@11.1.1:
851 | resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
852 | engines: {node: '>=14.14'}
853 | dependencies:
854 | graceful-fs: 4.2.11
855 | jsonfile: 6.1.0
856 | universalify: 2.0.0
857 | dev: false
858 |
859 | /fsevents@2.3.3:
860 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
861 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
862 | os: [darwin]
863 | requiresBuild: true
864 | optional: true
865 |
866 | /function-bind@1.1.1:
867 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
868 | dev: false
869 |
870 | /get-func-name@2.0.0:
871 | resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
872 | dev: true
873 |
874 | /get-intrinsic@1.2.1:
875 | resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
876 | dependencies:
877 | function-bind: 1.1.1
878 | has: 1.0.3
879 | has-proto: 1.0.1
880 | has-symbols: 1.0.3
881 | dev: false
882 |
883 | /get-tsconfig@4.7.2:
884 | resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
885 | dependencies:
886 | resolve-pkg-maps: 1.0.0
887 | dev: false
888 |
889 | /graceful-fs@4.2.11:
890 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
891 | dev: false
892 |
893 | /has-proto@1.0.1:
894 | resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
895 | engines: {node: '>= 0.4'}
896 | dev: false
897 |
898 | /has-symbols@1.0.3:
899 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
900 | engines: {node: '>= 0.4'}
901 | dev: false
902 |
903 | /has@1.0.3:
904 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
905 | engines: {node: '>= 0.4.0'}
906 | dependencies:
907 | function-bind: 1.1.1
908 | dev: false
909 |
910 | /http-errors@2.0.0:
911 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
912 | engines: {node: '>= 0.8'}
913 | dependencies:
914 | depd: 2.0.0
915 | inherits: 2.0.4
916 | setprototypeof: 1.2.0
917 | statuses: 2.0.1
918 | toidentifier: 1.0.1
919 | dev: false
920 |
921 | /iconv-lite@0.4.24:
922 | resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
923 | engines: {node: '>=0.10.0'}
924 | dependencies:
925 | safer-buffer: 2.1.2
926 | dev: false
927 |
928 | /inherits@2.0.4:
929 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
930 | dev: false
931 |
932 | /ipaddr.js@1.9.1:
933 | resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
934 | engines: {node: '>= 0.10'}
935 | dev: false
936 |
937 | /isomorphic.js@0.2.5:
938 | resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
939 | dev: false
940 |
941 | /js-base64@3.7.5:
942 | resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==}
943 | dev: false
944 |
945 | /js-tokens@4.0.0:
946 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
947 | dev: false
948 |
949 | /jsonc-parser@3.2.0:
950 | resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
951 | dev: true
952 |
953 | /jsonfile@6.1.0:
954 | resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
955 | dependencies:
956 | universalify: 2.0.0
957 | optionalDependencies:
958 | graceful-fs: 4.2.11
959 | dev: false
960 |
961 | /lib0@0.2.86:
962 | resolution: {integrity: sha512-kxigQTM4Q7NwJkEgdqQvU21qiR37twcqqLmh+/SbiGbRLfPlLVbHyY9sWp7PwXh0Xus9ELDSjsUOwcrdt5yZ4w==}
963 | engines: {node: '>=16'}
964 | hasBin: true
965 | dependencies:
966 | isomorphic.js: 0.2.5
967 | dev: false
968 |
969 | /libsql@0.1.23:
970 | resolution: {integrity: sha512-Nf/1B2Glxvcnba4jYFhXcaYmicyBA3RRm0LVwBkTl8UWCIDbX+Ad7c1ecrQwixPLPffWOVxKIqyCNTuUHUkVgA==}
971 | cpu: [x64, arm64]
972 | os: [darwin, linux, win32]
973 | dependencies:
974 | '@neon-rs/load': 0.0.4
975 | detect-libc: 2.0.2
976 | optionalDependencies:
977 | '@libsql/darwin-arm64': 0.1.23
978 | '@libsql/darwin-x64': 0.1.23
979 | '@libsql/linux-x64-gnu': 0.1.23
980 | '@libsql/linux-x64-musl': 0.1.23
981 | '@libsql/win32-x64-msvc': 0.1.23
982 | dev: false
983 |
984 | /lmdb@2.8.5:
985 | resolution: {integrity: sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ==}
986 | hasBin: true
987 | requiresBuild: true
988 | dependencies:
989 | msgpackr: 1.9.9
990 | node-addon-api: 6.1.0
991 | node-gyp-build-optional-packages: 5.1.1
992 | ordered-binary: 1.4.1
993 | weak-lru-cache: 1.2.2
994 | optionalDependencies:
995 | '@lmdb/lmdb-darwin-arm64': 2.8.5
996 | '@lmdb/lmdb-darwin-x64': 2.8.5
997 | '@lmdb/lmdb-linux-arm': 2.8.5
998 | '@lmdb/lmdb-linux-arm64': 2.8.5
999 | '@lmdb/lmdb-linux-x64': 2.8.5
1000 | '@lmdb/lmdb-win32-x64': 2.8.5
1001 | dev: false
1002 |
1003 | /local-pkg@0.4.3:
1004 | resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
1005 | engines: {node: '>=14'}
1006 | dev: true
1007 |
1008 | /lodash.debounce@4.0.8:
1009 | resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
1010 | dev: false
1011 |
1012 | /loose-envify@1.4.0:
1013 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
1014 | hasBin: true
1015 | dependencies:
1016 | js-tokens: 4.0.0
1017 | dev: false
1018 |
1019 | /loupe@2.3.6:
1020 | resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
1021 | dependencies:
1022 | get-func-name: 2.0.0
1023 | dev: true
1024 |
1025 | /magic-string@0.30.3:
1026 | resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==}
1027 | engines: {node: '>=12'}
1028 | dependencies:
1029 | '@jridgewell/sourcemap-codec': 1.4.15
1030 | dev: true
1031 |
1032 | /media-typer@0.3.0:
1033 | resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
1034 | engines: {node: '>= 0.6'}
1035 | dev: false
1036 |
1037 | /merge-descriptors@1.0.1:
1038 | resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
1039 | dev: false
1040 |
1041 | /methods@1.1.2:
1042 | resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
1043 | engines: {node: '>= 0.6'}
1044 | dev: false
1045 |
1046 | /mime-db@1.52.0:
1047 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
1048 | engines: {node: '>= 0.6'}
1049 | dev: false
1050 |
1051 | /mime-types@2.1.35:
1052 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
1053 | engines: {node: '>= 0.6'}
1054 | dependencies:
1055 | mime-db: 1.52.0
1056 | dev: false
1057 |
1058 | /mime@1.6.0:
1059 | resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
1060 | engines: {node: '>=4'}
1061 | hasBin: true
1062 | dev: false
1063 |
1064 | /mlly@1.4.2:
1065 | resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
1066 | dependencies:
1067 | acorn: 8.10.0
1068 | pathe: 1.1.1
1069 | pkg-types: 1.0.3
1070 | ufo: 1.3.0
1071 | dev: true
1072 |
1073 | /ms@2.0.0:
1074 | resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
1075 | dev: false
1076 |
1077 | /ms@2.1.2:
1078 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
1079 | dev: true
1080 |
1081 | /ms@2.1.3:
1082 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
1083 | dev: false
1084 |
1085 | /msgpackr-extract@3.0.2:
1086 | resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==}
1087 | hasBin: true
1088 | requiresBuild: true
1089 | dependencies:
1090 | node-gyp-build-optional-packages: 5.0.7
1091 | optionalDependencies:
1092 | '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.2
1093 | '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.2
1094 | '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.2
1095 | '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.2
1096 | '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.2
1097 | '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.2
1098 | dev: false
1099 | optional: true
1100 |
1101 | /msgpackr@1.9.9:
1102 | resolution: {integrity: sha512-sbn6mioS2w0lq1O6PpGtsv6Gy8roWM+o3o4Sqjd6DudrL/nOugY+KyJUimoWzHnf9OkO0T6broHFnYE/R05t9A==}
1103 | optionalDependencies:
1104 | msgpackr-extract: 3.0.2
1105 | dev: false
1106 |
1107 | /nanoid@3.3.6:
1108 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
1109 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
1110 | hasBin: true
1111 | dev: true
1112 |
1113 | /negotiator@0.6.3:
1114 | resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
1115 | engines: {node: '>= 0.6'}
1116 | dev: false
1117 |
1118 | /node-addon-api@6.1.0:
1119 | resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
1120 | dev: false
1121 |
1122 | /node-domexception@1.0.0:
1123 | resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
1124 | engines: {node: '>=10.5.0'}
1125 | dev: false
1126 |
1127 | /node-fetch@2.7.0:
1128 | resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
1129 | engines: {node: 4.x || >=6.0.0}
1130 | peerDependencies:
1131 | encoding: ^0.1.0
1132 | peerDependenciesMeta:
1133 | encoding:
1134 | optional: true
1135 | dependencies:
1136 | whatwg-url: 5.0.0
1137 | dev: false
1138 |
1139 | /node-fetch@3.3.2:
1140 | resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
1141 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
1142 | dependencies:
1143 | data-uri-to-buffer: 4.0.1
1144 | fetch-blob: 3.2.0
1145 | formdata-polyfill: 4.0.10
1146 | dev: false
1147 |
1148 | /node-gyp-build-optional-packages@5.0.7:
1149 | resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==}
1150 | hasBin: true
1151 | requiresBuild: true
1152 | dev: false
1153 | optional: true
1154 |
1155 | /node-gyp-build-optional-packages@5.1.1:
1156 | resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==}
1157 | hasBin: true
1158 | dependencies:
1159 | detect-libc: 2.0.2
1160 | dev: false
1161 |
1162 | /node-sql-parser@4.11.0:
1163 | resolution: {integrity: sha512-ElheoPibjc7IVyRdsORgkzJi0DWm3f0LYSsm/eJIeUt3M/csDLTblLDR4zl5Qi7jmVjJ1KpEkPKSbgVGEzU5Xw==}
1164 | engines: {node: '>=8'}
1165 | dependencies:
1166 | big-integer: 1.6.51
1167 | dev: false
1168 |
1169 | /object-assign@4.1.1:
1170 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
1171 | engines: {node: '>=0.10.0'}
1172 | dev: false
1173 |
1174 | /object-inspect@1.12.3:
1175 | resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
1176 | dev: false
1177 |
1178 | /on-finished@2.4.1:
1179 | resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
1180 | engines: {node: '>= 0.8'}
1181 | dependencies:
1182 | ee-first: 1.1.1
1183 | dev: false
1184 |
1185 | /ordered-binary@1.4.1:
1186 | resolution: {integrity: sha512-9LtiGlPy982CsgxZvJGNNp2/NnrgEr6EAyN3iIEP3/8vd3YLgAZQHbQ75ZrkfBRGrNg37Dk3U6tuVb+B4Xfslg==}
1187 | dev: false
1188 |
1189 | /p-limit@4.0.0:
1190 | resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
1191 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
1192 | dependencies:
1193 | yocto-queue: 1.0.0
1194 | dev: true
1195 |
1196 | /parseurl@1.3.3:
1197 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
1198 | engines: {node: '>= 0.8'}
1199 | dev: false
1200 |
1201 | /path-to-regexp@0.1.7:
1202 | resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
1203 | dev: false
1204 |
1205 | /pathe@1.1.1:
1206 | resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
1207 | dev: true
1208 |
1209 | /pathval@1.1.1:
1210 | resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
1211 | dev: true
1212 |
1213 | /picocolors@1.0.0:
1214 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
1215 | dev: true
1216 |
1217 | /pkg-types@1.0.3:
1218 | resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
1219 | dependencies:
1220 | jsonc-parser: 3.2.0
1221 | mlly: 1.4.2
1222 | pathe: 1.1.1
1223 | dev: true
1224 |
1225 | /postcss@8.4.30:
1226 | resolution: {integrity: sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==}
1227 | engines: {node: ^10 || ^12 || >=14}
1228 | dependencies:
1229 | nanoid: 3.3.6
1230 | picocolors: 1.0.0
1231 | source-map-js: 1.0.2
1232 | dev: true
1233 |
1234 | /pretty-format@29.7.0:
1235 | resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
1236 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
1237 | dependencies:
1238 | '@jest/schemas': 29.6.3
1239 | ansi-styles: 5.2.0
1240 | react-is: 18.2.0
1241 | dev: true
1242 |
1243 | /proxy-addr@2.0.7:
1244 | resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
1245 | engines: {node: '>= 0.10'}
1246 | dependencies:
1247 | forwarded: 0.2.0
1248 | ipaddr.js: 1.9.1
1249 | dev: false
1250 |
1251 | /qs@6.11.0:
1252 | resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
1253 | engines: {node: '>=0.6'}
1254 | dependencies:
1255 | side-channel: 1.0.4
1256 | dev: false
1257 |
1258 | /range-parser@1.2.1:
1259 | resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
1260 | engines: {node: '>= 0.6'}
1261 | dev: false
1262 |
1263 | /raw-body@2.5.1:
1264 | resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
1265 | engines: {node: '>= 0.8'}
1266 | dependencies:
1267 | bytes: 3.1.2
1268 | http-errors: 2.0.0
1269 | iconv-lite: 0.4.24
1270 | unpipe: 1.0.0
1271 | dev: false
1272 |
1273 | /react-dom@18.2.0(react@18.2.0):
1274 | resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
1275 | peerDependencies:
1276 | react: ^18.2.0
1277 | dependencies:
1278 | loose-envify: 1.4.0
1279 | react: 18.2.0
1280 | scheduler: 0.23.0
1281 | dev: false
1282 |
1283 | /react-is@18.2.0:
1284 | resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
1285 | dev: true
1286 |
1287 | /react@18.2.0:
1288 | resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
1289 | engines: {node: '>=0.10.0'}
1290 | dependencies:
1291 | loose-envify: 1.4.0
1292 | dev: false
1293 |
1294 | /resolve-pkg-maps@1.0.0:
1295 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
1296 | dev: false
1297 |
1298 | /rollup@3.29.3:
1299 | resolution: {integrity: sha512-T7du6Hum8jOkSWetjRgbwpM6Sy0nECYrYRSmZjayFcOddtKJWU4d17AC3HNUk7HRuqy4p+G7aEZclSHytqUmEg==}
1300 | engines: {node: '>=14.18.0', npm: '>=8.0.0'}
1301 | hasBin: true
1302 | optionalDependencies:
1303 | fsevents: 2.3.3
1304 | dev: true
1305 |
1306 | /safe-buffer@5.2.1:
1307 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
1308 | dev: false
1309 |
1310 | /safer-buffer@2.1.2:
1311 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
1312 | dev: false
1313 |
1314 | /scheduler@0.23.0:
1315 | resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
1316 | dependencies:
1317 | loose-envify: 1.4.0
1318 | dev: false
1319 |
1320 | /send@0.18.0:
1321 | resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
1322 | engines: {node: '>= 0.8.0'}
1323 | dependencies:
1324 | debug: 2.6.9
1325 | depd: 2.0.0
1326 | destroy: 1.2.0
1327 | encodeurl: 1.0.2
1328 | escape-html: 1.0.3
1329 | etag: 1.8.1
1330 | fresh: 0.5.2
1331 | http-errors: 2.0.0
1332 | mime: 1.6.0
1333 | ms: 2.1.3
1334 | on-finished: 2.4.1
1335 | range-parser: 1.2.1
1336 | statuses: 2.0.1
1337 | transitivePeerDependencies:
1338 | - supports-color
1339 | dev: false
1340 |
1341 | /serve-static@1.15.0:
1342 | resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
1343 | engines: {node: '>= 0.8.0'}
1344 | dependencies:
1345 | encodeurl: 1.0.2
1346 | escape-html: 1.0.3
1347 | parseurl: 1.3.3
1348 | send: 0.18.0
1349 | transitivePeerDependencies:
1350 | - supports-color
1351 | dev: false
1352 |
1353 | /setprototypeof@1.2.0:
1354 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
1355 | dev: false
1356 |
1357 | /side-channel@1.0.4:
1358 | resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
1359 | dependencies:
1360 | call-bind: 1.0.2
1361 | get-intrinsic: 1.2.1
1362 | object-inspect: 1.12.3
1363 | dev: false
1364 |
1365 | /siginfo@2.0.0:
1366 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
1367 | dev: true
1368 |
1369 | /situated@0.0.1(react-dom@18.2.0)(react@18.2.0):
1370 | resolution: {integrity: sha512-ZjcT3dl2TUTsy7bCUqTyVs2VKmHAWswGZEJMf5YbuFXwmecsC2PXoEIcLsU4SEi7s4TpXTAEsKXGafnHoHqsww==}
1371 | dependencies:
1372 | lib0: 0.2.86
1373 | lmdb: 2.8.5
1374 | lodash.debounce: 4.0.8
1375 | use-sync-external-store: 1.2.0(react@18.2.0)
1376 | usehooks-ts: 2.9.1(react-dom@18.2.0)(react@18.2.0)
1377 | y-protocols: 1.0.6(yjs@13.6.8)
1378 | yjs: 13.6.8
1379 | transitivePeerDependencies:
1380 | - react
1381 | - react-dom
1382 | dev: false
1383 |
1384 | /source-map-js@1.0.2:
1385 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
1386 | engines: {node: '>=0.10.0'}
1387 | dev: true
1388 |
1389 | /source-map-support@0.5.21:
1390 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
1391 | dependencies:
1392 | buffer-from: 1.1.2
1393 | source-map: 0.6.1
1394 | dev: false
1395 |
1396 | /source-map@0.6.1:
1397 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
1398 | engines: {node: '>=0.10.0'}
1399 | dev: false
1400 |
1401 | /stackback@0.0.2:
1402 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
1403 | dev: true
1404 |
1405 | /statuses@2.0.1:
1406 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
1407 | engines: {node: '>= 0.8'}
1408 | dev: false
1409 |
1410 | /std-env@3.4.3:
1411 | resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==}
1412 | dev: true
1413 |
1414 | /strip-literal@1.3.0:
1415 | resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==}
1416 | dependencies:
1417 | acorn: 8.10.0
1418 | dev: true
1419 |
1420 | /tinybench@2.5.1:
1421 | resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==}
1422 | dev: true
1423 |
1424 | /tinypool@0.7.0:
1425 | resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==}
1426 | engines: {node: '>=14.0.0'}
1427 | dev: true
1428 |
1429 | /tinyspy@2.1.1:
1430 | resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==}
1431 | engines: {node: '>=14.0.0'}
1432 | dev: true
1433 |
1434 | /toidentifier@1.0.1:
1435 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
1436 | engines: {node: '>=0.6'}
1437 | dev: false
1438 |
1439 | /tr46@0.0.3:
1440 | resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
1441 | dev: false
1442 |
1443 | /trpc-yjs@0.0.6(@trpc/client@10.38.5)(@trpc/server@10.38.5)(yjs@13.6.8):
1444 | resolution: {integrity: sha512-QYw9l6q/Z9O5/39/mguK0Z4DbvTJ9isqnvRT1xovYHPsLCX1LCMYj7MeI/u9E0S5+1scly+iyJWJL2ATFNTVHg==}
1445 | peerDependencies:
1446 | '@trpc/client': '*'
1447 | '@trpc/server': '*'
1448 | yjs: '*'
1449 | dependencies:
1450 | '@trpc/client': 10.38.5(@trpc/server@10.38.5)
1451 | '@trpc/server': 10.38.5
1452 | yjs: 13.6.8
1453 | dev: false
1454 |
1455 | /tsx@3.13.0:
1456 | resolution: {integrity: sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==}
1457 | hasBin: true
1458 | dependencies:
1459 | esbuild: 0.18.20
1460 | get-tsconfig: 4.7.2
1461 | source-map-support: 0.5.21
1462 | optionalDependencies:
1463 | fsevents: 2.3.3
1464 | dev: false
1465 |
1466 | /type-detect@4.0.8:
1467 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
1468 | engines: {node: '>=4'}
1469 | dev: true
1470 |
1471 | /type-is@1.6.18:
1472 | resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
1473 | engines: {node: '>= 0.6'}
1474 | dependencies:
1475 | media-typer: 0.3.0
1476 | mime-types: 2.1.35
1477 | dev: false
1478 |
1479 | /typescript@5.2.2:
1480 | resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
1481 | engines: {node: '>=14.17'}
1482 | hasBin: true
1483 | dev: false
1484 |
1485 | /ufo@1.3.0:
1486 | resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==}
1487 | dev: true
1488 |
1489 | /universalify@2.0.0:
1490 | resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
1491 | engines: {node: '>= 10.0.0'}
1492 | dev: false
1493 |
1494 | /unpipe@1.0.0:
1495 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
1496 | engines: {node: '>= 0.8'}
1497 | dev: false
1498 |
1499 | /use-sync-external-store@1.2.0(react@18.2.0):
1500 | resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
1501 | peerDependencies:
1502 | react: ^16.8.0 || ^17.0.0 || ^18.0.0
1503 | dependencies:
1504 | react: 18.2.0
1505 | dev: false
1506 |
1507 | /usehooks-ts@2.9.1(react-dom@18.2.0)(react@18.2.0):
1508 | resolution: {integrity: sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==}
1509 | engines: {node: '>=16.15.0', npm: '>=8'}
1510 | peerDependencies:
1511 | react: ^16.8.0 || ^17.0.0 || ^18.0.0
1512 | react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
1513 | dependencies:
1514 | react: 18.2.0
1515 | react-dom: 18.2.0(react@18.2.0)
1516 | dev: false
1517 |
1518 | /utils-merge@1.0.1:
1519 | resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
1520 | engines: {node: '>= 0.4.0'}
1521 | dev: false
1522 |
1523 | /vary@1.1.2:
1524 | resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
1525 | engines: {node: '>= 0.8'}
1526 | dev: false
1527 |
1528 | /vite-node@0.34.5(@types/node@20.7.0):
1529 | resolution: {integrity: sha512-RNZ+DwbCvDoI5CbCSQSyRyzDTfFvFauvMs6Yq4ObJROKlIKuat1KgSX/Ako5rlDMfVCyMcpMRMTkJBxd6z8YRA==}
1530 | engines: {node: '>=v14.18.0'}
1531 | hasBin: true
1532 | dependencies:
1533 | cac: 6.7.14
1534 | debug: 4.3.4
1535 | mlly: 1.4.2
1536 | pathe: 1.1.1
1537 | picocolors: 1.0.0
1538 | vite: 4.4.9(@types/node@20.7.0)
1539 | transitivePeerDependencies:
1540 | - '@types/node'
1541 | - less
1542 | - lightningcss
1543 | - sass
1544 | - stylus
1545 | - sugarss
1546 | - supports-color
1547 | - terser
1548 | dev: true
1549 |
1550 | /vite@4.4.9(@types/node@20.7.0):
1551 | resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
1552 | engines: {node: ^14.18.0 || >=16.0.0}
1553 | hasBin: true
1554 | peerDependencies:
1555 | '@types/node': '>= 14'
1556 | less: '*'
1557 | lightningcss: ^1.21.0
1558 | sass: '*'
1559 | stylus: '*'
1560 | sugarss: '*'
1561 | terser: ^5.4.0
1562 | peerDependenciesMeta:
1563 | '@types/node':
1564 | optional: true
1565 | less:
1566 | optional: true
1567 | lightningcss:
1568 | optional: true
1569 | sass:
1570 | optional: true
1571 | stylus:
1572 | optional: true
1573 | sugarss:
1574 | optional: true
1575 | terser:
1576 | optional: true
1577 | dependencies:
1578 | '@types/node': 20.7.0
1579 | esbuild: 0.18.20
1580 | postcss: 8.4.30
1581 | rollup: 3.29.3
1582 | optionalDependencies:
1583 | fsevents: 2.3.3
1584 | dev: true
1585 |
1586 | /vitest@0.34.5:
1587 | resolution: {integrity: sha512-CPI68mmnr2DThSB3frSuE5RLm9wo5wU4fbDrDwWQQB1CWgq9jQVoQwnQSzYAjdoBOPoH2UtXpOgHVge/uScfZg==}
1588 | engines: {node: '>=v14.18.0'}
1589 | hasBin: true
1590 | peerDependencies:
1591 | '@edge-runtime/vm': '*'
1592 | '@vitest/browser': '*'
1593 | '@vitest/ui': '*'
1594 | happy-dom: '*'
1595 | jsdom: '*'
1596 | playwright: '*'
1597 | safaridriver: '*'
1598 | webdriverio: '*'
1599 | peerDependenciesMeta:
1600 | '@edge-runtime/vm':
1601 | optional: true
1602 | '@vitest/browser':
1603 | optional: true
1604 | '@vitest/ui':
1605 | optional: true
1606 | happy-dom:
1607 | optional: true
1608 | jsdom:
1609 | optional: true
1610 | playwright:
1611 | optional: true
1612 | safaridriver:
1613 | optional: true
1614 | webdriverio:
1615 | optional: true
1616 | dependencies:
1617 | '@types/chai': 4.3.6
1618 | '@types/chai-subset': 1.3.3
1619 | '@types/node': 20.7.0
1620 | '@vitest/expect': 0.34.5
1621 | '@vitest/runner': 0.34.5
1622 | '@vitest/snapshot': 0.34.5
1623 | '@vitest/spy': 0.34.5
1624 | '@vitest/utils': 0.34.5
1625 | acorn: 8.10.0
1626 | acorn-walk: 8.2.0
1627 | cac: 6.7.14
1628 | chai: 4.3.8
1629 | debug: 4.3.4
1630 | local-pkg: 0.4.3
1631 | magic-string: 0.30.3
1632 | pathe: 1.1.1
1633 | picocolors: 1.0.0
1634 | std-env: 3.4.3
1635 | strip-literal: 1.3.0
1636 | tinybench: 2.5.1
1637 | tinypool: 0.7.0
1638 | vite: 4.4.9(@types/node@20.7.0)
1639 | vite-node: 0.34.5(@types/node@20.7.0)
1640 | why-is-node-running: 2.2.2
1641 | transitivePeerDependencies:
1642 | - less
1643 | - lightningcss
1644 | - sass
1645 | - stylus
1646 | - sugarss
1647 | - supports-color
1648 | - terser
1649 | dev: true
1650 |
1651 | /weak-lru-cache@1.2.2:
1652 | resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==}
1653 | dev: false
1654 |
1655 | /web-streams-polyfill@3.2.1:
1656 | resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
1657 | engines: {node: '>= 8'}
1658 | dev: false
1659 |
1660 | /webidl-conversions@3.0.1:
1661 | resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
1662 | dev: false
1663 |
1664 | /whatwg-url@5.0.0:
1665 | resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
1666 | dependencies:
1667 | tr46: 0.0.3
1668 | webidl-conversions: 3.0.1
1669 | dev: false
1670 |
1671 | /why-is-node-running@2.2.2:
1672 | resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
1673 | engines: {node: '>=8'}
1674 | hasBin: true
1675 | dependencies:
1676 | siginfo: 2.0.0
1677 | stackback: 0.0.2
1678 | dev: true
1679 |
1680 | /ws@8.14.2:
1681 | resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
1682 | engines: {node: '>=10.0.0'}
1683 | peerDependencies:
1684 | bufferutil: ^4.0.1
1685 | utf-8-validate: '>=5.0.2'
1686 | peerDependenciesMeta:
1687 | bufferutil:
1688 | optional: true
1689 | utf-8-validate:
1690 | optional: true
1691 | dev: false
1692 |
1693 | /y-protocols@1.0.6(yjs@13.6.8):
1694 | resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==}
1695 | engines: {node: '>=16.0.0', npm: '>=8.0.0'}
1696 | peerDependencies:
1697 | yjs: ^13.0.0
1698 | dependencies:
1699 | lib0: 0.2.86
1700 | yjs: 13.6.8
1701 | dev: false
1702 |
1703 | /yjs@13.6.8:
1704 | resolution: {integrity: sha512-ZPq0hpJQb6f59B++Ngg4cKexDJTvfOgeiv0sBc4sUm8CaBWH7OQC4kcCgrqbjJ/B2+6vO49exvTmYfdlPtcjbg==}
1705 | engines: {node: '>=16.0.0', npm: '>=8.0.0'}
1706 | dependencies:
1707 | lib0: 0.2.86
1708 | dev: false
1709 |
1710 | /yocto-queue@1.0.0:
1711 | resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
1712 | engines: {node: '>=12.20'}
1713 | dev: true
1714 |
1715 | /zod@3.22.2:
1716 | resolution: {integrity: sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==}
1717 | dev: false
1718 |
--------------------------------------------------------------------------------