├── example ├── pnpm-workspace.yaml ├── apps │ ├── web │ │ ├── src │ │ │ ├── vite-env.d.ts │ │ │ ├── adapters │ │ │ │ └── client.ts │ │ │ ├── main.tsx │ │ │ ├── description │ │ │ │ ├── styles.module.css │ │ │ │ └── index.tsx │ │ │ ├── editor │ │ │ │ ├── provider.ts │ │ │ │ ├── config.ts │ │ │ │ └── index.tsx │ │ │ ├── styles.css │ │ │ └── assets │ │ │ │ └── react.svg │ │ ├── biome.json │ │ ├── vite.config.ts │ │ ├── tsconfig.node.json │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ ├── package.json │ │ ├── README.md │ │ ├── index.html │ │ └── public │ │ │ └── vite.svg │ └── workers │ │ ├── biome.json │ │ ├── README.md │ │ ├── .gitignore │ │ ├── src │ │ ├── types.ts │ │ └── index.ts │ │ ├── tsconfig.json │ │ ├── wrangler.toml │ │ └── package.json ├── biome.json ├── workspace.code-workspace ├── turbo.json ├── package.json ├── README.md └── pnpm-lock.yaml ├── .mise.toml ├── pnpm-workspace.yaml ├── .gitignore ├── .prettierignore ├── assets └── y-durableobjects-icon.png ├── .npmrc ├── src ├── e2e │ ├── types.d.ts │ ├── helper.ts │ ├── index.ts │ ├── e2e.test.ts │ └── y-durableobjects.test.ts ├── yjs │ ├── storage │ │ ├── storage-key │ │ │ ├── index.ts │ │ │ └── storage-key.test.ts │ │ ├── type.ts │ │ ├── index.ts │ │ └── storage.test.ts │ ├── remote │ │ ├── index.ts │ │ ├── ws-shared-doc.test.ts │ │ └── ws-shared-doc.ts │ ├── hono │ │ ├── index.ts │ │ └── create-app.test.ts │ ├── message-type │ │ ├── index.ts │ │ └── messaeg-type.test.ts │ ├── internal.ts │ ├── client │ │ ├── setup.ts │ │ └── setup.test.ts │ └── index.ts ├── middleware │ ├── index.ts │ └── upgrade.test.ts └── index.ts ├── wrangler.toml ├── .changeset ├── config.json └── README.md ├── vitest.config.ts ├── workspace.code-workspace ├── tsup.config.ts ├── tsconfig.json ├── .github └── workflows │ ├── package-size.yml │ ├── pre-release.yml │ ├── release.yml │ └── check.yml ├── LICENSE ├── CLAUDE.md ├── package.json ├── .serena └── project.yml ├── CHANGELOG.md ├── eslint.config.js └── README.md /example/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" -------------------------------------------------------------------------------- /.mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | node = "22.14.0" 3 | pnpm = "10.8.1" 4 | -------------------------------------------------------------------------------- /example/apps/web/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | onlyBuiltDependencies: 2 | - esbuild 3 | - workerd 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .turbo 3 | dist 4 | build 5 | .mcp.json 6 | .claude/ 7 | -------------------------------------------------------------------------------- /example/apps/web/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../biome.json"], 3 | "formatter": {} 4 | } -------------------------------------------------------------------------------- /example/apps/workers/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../biome.json"], 3 | "formatter": {} 4 | } -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | example 2 | node_modules 3 | .turbo 4 | pnpm-lock.yaml 5 | dist 6 | worker-configuration.d.ts 7 | -------------------------------------------------------------------------------- /example/apps/workers/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm run dev 4 | ``` 5 | 6 | ``` 7 | npm run deploy 8 | ``` 9 | -------------------------------------------------------------------------------- /assets/y-durableobjects-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/napolab/y-durableobjects/HEAD/assets/y-durableobjects-icon.png -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | dedupe-peer-dependents=true 2 | strict-peer-dependencies=false 3 | auto-install-peers=true 4 | resolve-peers-from-workspace-root=true 5 | shamefully-hoist=true -------------------------------------------------------------------------------- /example/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatter": { 3 | "lineWidth": 120, 4 | "indentStyle": "space" 5 | }, 6 | "linter": { 7 | "rules": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/apps/workers/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .wrangler 4 | .dev.vars 5 | 6 | # Change them to your taste: 7 | package-lock.json 8 | yarn.lock 9 | pnpm-lock.yaml 10 | public 11 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /example/apps/web/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 | -------------------------------------------------------------------------------- /example/apps/workers/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { YDurableObjects } from "y-durableobjects"; 2 | 3 | type Bindings = { 4 | Y_DURABLE_OBJECTS: DurableObjectNamespace>; 5 | }; 6 | 7 | export type Env = { 8 | Bindings: Bindings; 9 | }; 10 | -------------------------------------------------------------------------------- /example/apps/web/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 | -------------------------------------------------------------------------------- /src/e2e/types.d.ts: -------------------------------------------------------------------------------- 1 | import "cloudflare:test"; 2 | import type { YDurableObjects } from "../yjs"; 3 | 4 | interface CloudflareEnv { 5 | Y_DURABLE_OBJECTS: DurableObjectNamespace; 6 | } 7 | 8 | declare module "cloudflare:test" { 9 | interface ProvidedEnv extends CloudflareEnv {} 10 | } 11 | -------------------------------------------------------------------------------- /example/workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "apps/web" 8 | }, 9 | { 10 | "path": "apps/workers" 11 | } 12 | ], 13 | "settings": { 14 | "typescript.tsdk": "web/node_modules/typescript/lib" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/yjs/storage/storage-key/index.ts: -------------------------------------------------------------------------------- 1 | export type Key = 2 | | { 3 | type: "update"; 4 | name?: number; 5 | } 6 | | { 7 | type: "state"; 8 | name: "bytes" | "doc" | "count"; 9 | }; 10 | 11 | export const storageKey = (key: Key) => { 12 | return `ydoc:${key.type}:${key.name ?? ""}`; 13 | }; 14 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "yjs-workers" 2 | main = "src/e2e/index.ts" 3 | compatibility_date = "2024-04-05" 4 | compatibility_flags=["nodejs_compat"] 5 | 6 | [[durable_objects.bindings]] 7 | name = "Y_DURABLE_OBJECTS" 8 | class_name = "YDurableObjects" 9 | 10 | [[migrations]] 11 | tag = "v1" 12 | new_classes = ["YDurableObjects"] 13 | 14 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /example/apps/web/src/adapters/client.ts: -------------------------------------------------------------------------------- 1 | import { hc } from "hono/client"; 2 | import type { AppType } from "workers"; 3 | 4 | const API_URL = import.meta.env.PROD ? import.meta.env.VITE_API_URL : "http://localhost:8787"; 5 | 6 | export const client = hc(API_URL); 7 | 8 | // const ws = client.editor[":id"].$ws({ param: { id: "1" } }) 9 | -------------------------------------------------------------------------------- /example/apps/web/.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 | -------------------------------------------------------------------------------- /src/yjs/remote/index.ts: -------------------------------------------------------------------------------- 1 | import type { Awareness } from "y-protocols/awareness"; 2 | import type { Doc } from "yjs"; 3 | export { WSSharedDoc } from "./ws-shared-doc"; 4 | 5 | export type AwarenessChanges = { 6 | added: number[]; 7 | updated: number[]; 8 | removed: number[]; 9 | }; 10 | 11 | export interface RemoteDoc extends Doc { 12 | readonly awareness: Awareness; 13 | } 14 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; 2 | 3 | export default defineWorkersProject({ 4 | test: { 5 | globals: true, 6 | poolOptions: { 7 | workers: { 8 | singleWorker: true, 9 | wrangler: { 10 | configPath: "./wrangler.toml", 11 | }, 12 | }, 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /example/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": ["**/.env.*", "**/wrangler.toml"], 4 | "pipeline": { 5 | "fmt": {}, 6 | "lint": {}, 7 | "typecheck": {}, 8 | "dev": { 9 | "cache": false, 10 | "persistent": true 11 | }, 12 | "build": { 13 | "outputs": ["dist", "build", "lib"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".", 5 | }, 6 | { 7 | "name": "example", 8 | "path": "example", 9 | }, 10 | { 11 | "name": "example/web", 12 | "path": "example/apps/web", 13 | }, 14 | { 15 | "name": "example/workers", 16 | "path": "example/apps/workers", 17 | }, 18 | ], 19 | "settings": {}, 20 | } 21 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: { 5 | index: "src/index.ts", 6 | "helpers/upgrade": "src/middleware/index.ts", 7 | }, 8 | sourcemap: true, 9 | dts: { 10 | banner: '/// ', 11 | }, 12 | splitting: true, 13 | clean: true, 14 | format: ["cjs", "esm"], 15 | external: ["hono", /cloudflare:/], 16 | }); 17 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yjs-worker", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "turbo dev", 6 | "fmt": "turbo fmt", 7 | "lint": "turbo lint", 8 | "typecheck": "turbo typecheck", 9 | "build": "turbo build --filter workers && turbo build" 10 | }, 11 | "author": "naporin0624", 12 | "devDependencies": { 13 | "turbo": "^1.11.2" 14 | }, 15 | "workspaces": [ 16 | "apps/*" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /example/apps/workers/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { cors } from "hono/cors"; 3 | import { YDurableObjects, yRoute } from "y-durableobjects"; 4 | import { Env } from "./types"; 5 | 6 | const app = new Hono(); 7 | app.use("*", cors()); 8 | 9 | const route = app.route( 10 | "/editor", 11 | yRoute((env) => env.Y_DURABLE_OBJECTS), 12 | ); 13 | 14 | export default route; 15 | export type AppType = typeof route; 16 | export { YDurableObjects }; 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "lib": ["ESNext"], 9 | "types": ["@cloudflare/vitest-pool-workers", "vitest/globals"], 10 | "noEmit": true, 11 | "skipLibCheck": true, 12 | "skipDefaultLibCheck": true, 13 | "forceConsistentCasingInFileNames": true 14 | }, 15 | "exclude": ["example"] 16 | } 17 | -------------------------------------------------------------------------------- /example/apps/workers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "lib": ["esnext"], 9 | "types": ["@cloudflare/workers-types"], 10 | "jsx": "react-jsx", 11 | "jsxImportSource": "hono/jsx", 12 | "outDir": "dist", 13 | "rootDir": "src", 14 | "skipLibCheck": true, 15 | "declaration": true 16 | }, 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /example/apps/web/src/main.tsx: -------------------------------------------------------------------------------- 1 | import "@acab/reset.css"; 2 | import React from "react"; 3 | import ReactDOM from "react-dom/client"; 4 | import { Description } from "./description"; 5 | import Editor from "./editor"; 6 | import "./styles.css"; 7 | 8 | // biome-ignore lint/style/noNonNullAssertion: 9 | ReactDOM.createRoot(document.getElementById("root")!).render( 10 | 11 |
12 | 13 | 14 |
15 |
, 16 | ); 17 | -------------------------------------------------------------------------------- /src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import { createMiddleware } from "hono/factory"; 2 | 3 | import type { Env } from "hono"; 4 | 5 | type Input = { 6 | outputFormat: "ws"; 7 | }; 8 | 9 | export const upgrade = () => 10 | createMiddleware(async (c, next) => { 11 | if (c.req.header("Upgrade") !== "websocket") { 12 | return c.body("Expected websocket", { 13 | status: 426, 14 | statusText: "Upgrade Required", 15 | }); 16 | } 17 | 18 | return next(); 19 | }); 20 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /example/apps/web/src/description/styles.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 24px; 3 | font-weight: bold; 4 | margin-bottom: 10px; 5 | } 6 | 7 | .text { 8 | font-size: 16px; 9 | line-height: 1.5; 10 | margin: 10px 0; 11 | } 12 | 13 | .code { 14 | font-family: 'Courier New', Courier, monospace; 15 | background-color: #f5f5f5; 16 | padding: 2px 4px; 17 | border-radius: 3px; 18 | } 19 | 20 | .link { 21 | color: #1a73e8; 22 | text-decoration: none; 23 | } 24 | 25 | .link:hover { 26 | text-decoration: underline; 27 | } 28 | -------------------------------------------------------------------------------- /example/apps/workers/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "yjs-workers" 2 | compatibility_date = "2023-01-01" 3 | workers_dev=false 4 | main="src/index.ts" 5 | 6 | [[durable_objects.bindings]] 7 | name = "Y_DURABLE_OBJECTS" 8 | class_name = "YDurableObjects" 9 | 10 | [[migrations]] 11 | tag = "v1" 12 | new_classes = ["YjsProvider"] 13 | 14 | [[migrations]] 15 | tag = "v2" 16 | renamed_classes = [{ from = "YjsProvider", to = "YWebsocket" }] 17 | 18 | [[migrations]] 19 | tag = "v3" 20 | renamed_classes = [{ from = "YWebsocket", to = "YDurableObjects" }] 21 | -------------------------------------------------------------------------------- /src/yjs/hono/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | 3 | type Service = { 4 | createRoom(roomId: string): Promise | WebSocket; 5 | }; 6 | 7 | export const createApp = (service: Service) => { 8 | const app = new Hono(); 9 | 10 | return app.get("/rooms/:roomId", async (c) => { 11 | const roomId = c.req.param("roomId"); 12 | const client = await service.createRoom(roomId); 13 | 14 | return new Response(null, { 15 | webSocket: client, 16 | status: 101, 17 | statusText: "Switching Protocols", 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /src/yjs/storage/type.ts: -------------------------------------------------------------------------------- 1 | interface ListOptions { 2 | start?: string; 3 | startAfter?: string; 4 | end?: string; 5 | prefix?: string; 6 | reverse?: boolean; 7 | limit?: number; 8 | } 9 | 10 | export interface TransactionStorage { 11 | get(key: string): Promise; 12 | list(options?: ListOptions): Promise>; 13 | put(key: string, value: T): Promise; 14 | delete(key: string | string[]): Promise; 15 | transaction( 16 | closure: (txn: Omit) => Promise, 17 | ): Promise; 18 | } 19 | -------------------------------------------------------------------------------- /src/yjs/message-type/index.ts: -------------------------------------------------------------------------------- 1 | import { createEncoder, writeVarUint } from "lib0/encoding"; 2 | 3 | export const messageType = { 4 | sync: 0, 5 | awareness: 1, 6 | }; 7 | 8 | export const isMessageType = ( 9 | type: string, 10 | ): type is keyof typeof messageType => { 11 | return Object.keys(messageType).includes(type); 12 | }; 13 | 14 | export const createTypedEncoder = (type: keyof typeof messageType) => { 15 | if (!isMessageType(type)) { 16 | throw new Error(`Unsupported message type: ${type}`); 17 | } 18 | 19 | const encoder = createEncoder(); 20 | writeVarUint(encoder, messageType[type]); 21 | 22 | return encoder; 23 | }; 24 | -------------------------------------------------------------------------------- /example/apps/web/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 | }, 22 | "include": ["src"], 23 | "references": [{ "path": "./tsconfig.node.json" }] 24 | } 25 | -------------------------------------------------------------------------------- /example/apps/workers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workers", 3 | "type": "module", 4 | "main": "src/index.ts", 5 | "types": "dist/index.d.ts", 6 | "scripts": { 7 | "dev": "wrangler dev", 8 | "deploy": "wrangler deploy --minify src/index.ts", 9 | "typecheck": "tsc --noEmit", 10 | "lint": "biome lint src", 11 | "fmt": "biome format src --write", 12 | "build": "tsc --emitDeclarationOnly" 13 | }, 14 | "devDependencies": { 15 | "@biomejs/biome": "^1.4.1", 16 | "@cloudflare/workers-types": "^4.20240729.0", 17 | "typescript": "^5.5.4", 18 | "wrangler": "^3.68.0" 19 | }, 20 | "dependencies": { 21 | "hono": "^4.5.3", 22 | "y-durableobjects": "^1.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/yjs/storage/storage-key/storage-key.test.ts: -------------------------------------------------------------------------------- 1 | import { storageKey } from "."; 2 | 3 | import type { Key } from "."; 4 | 5 | describe("storageKey functionality", () => { 6 | it.each([ 7 | [{ type: "update" }, "ydoc:update:"], // nameが省略された場合 8 | [{ type: "update", name: 1 }, "ydoc:update:1"], // nameが数値で提供された場合 9 | [{ type: "state", name: "bytes" }, "ydoc:state:bytes"], // typeがstateでnameがbytesの場合 10 | [{ type: "state", name: "doc" }, "ydoc:state:doc"], // typeがstateでnameがdocの場合 11 | [{ type: "state", name: "count" }, "ydoc:state:count"], // typeがstateでnameが新しく追加されたcountの場合 12 | ])("correctly generates storage key for key: %o", (key, expected) => { 13 | expect(storageKey(key as Key)).toEqual(expected); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.github/workflows/package-size.yml: -------------------------------------------------------------------------------- 1 | name: Package Size Report 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | env: 11 | NODE_VERSION: "22.14.0" 12 | PNPM_VERSION: "10.8.1" 13 | 14 | jobs: 15 | pkg-size-report: 16 | name: Package Size Report 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ env.NODE_VERSION }} 27 | 28 | - name: Package size report 29 | uses: pkg-size/action@v1 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: PR PreRelease 2 | on: 3 | pull_request: 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | env: 10 | NODE_VERSION: "22.14.0" 11 | PNPM_VERSION: "10.8.1" 12 | 13 | jobs: 14 | pre-release: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - run: corepack enable 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ env.NODE_VERSION }} 25 | cache: "pnpm" 26 | 27 | - name: Install dependencies 28 | run: pnpm install 29 | 30 | - name: Build 31 | run: pnpm build 32 | 33 | - run: pnpx pkg-pr-new publish 34 | -------------------------------------------------------------------------------- /src/e2e/helper.ts: -------------------------------------------------------------------------------- 1 | import { createEncoder, toUint8Array, writeVarUint } from "lib0/encoding.js"; 2 | import { writeUpdate } from "y-protocols/sync.js"; 3 | import { Doc, encodeStateAsUpdate } from "yjs"; 4 | 5 | import { messageType } from "../yjs/message-type"; 6 | 7 | // Helper to create updates based on document type 8 | export const createYDocMessage = (content: string = "Hello World!") => { 9 | const doc = new Doc(); 10 | doc.getText("root").insert(0, content); 11 | 12 | return encodeStateAsUpdate(doc); 13 | }; 14 | 15 | // Helper to create an encoded message from an update 16 | export const createSyncMessage = (update: Uint8Array) => { 17 | const encoder = createEncoder(); 18 | writeVarUint(encoder, messageType.sync); 19 | writeUpdate(encoder, update); 20 | 21 | return toUint8Array(encoder); 22 | }; 23 | -------------------------------------------------------------------------------- /example/apps/web/src/editor/provider.ts: -------------------------------------------------------------------------------- 1 | import type { CollaborationPlugin } from "@lexical/react/LexicalCollaborationPlugin"; 2 | import type { Provider } from "@lexical/yjs"; 3 | import type { ComponentProps } from "react"; 4 | import { WebsocketProvider } from "y-websocket"; 5 | import { Doc } from "yjs"; 6 | import { client } from "../adapters/client"; 7 | 8 | type Props = ComponentProps; 9 | type ProviderFactory = Props["providerFactory"]; 10 | 11 | export const providerFactory: ProviderFactory = (id, map) => { 12 | const doc = new Doc(); 13 | map.set(id, doc); 14 | 15 | const url = client.editor[":id"].$url(); 16 | const provider = new WebsocketProvider(url.toString().replace("http", "ws").replace("/:id", ""), id, doc); 17 | 18 | // 公式通りやると型エラーになる調査する 19 | return provider as unknown as Provider; 20 | }; 21 | -------------------------------------------------------------------------------- /example/apps/web/src/editor/config.ts: -------------------------------------------------------------------------------- 1 | import { CodeHighlightNode, CodeNode } from "@lexical/code"; 2 | import { AutoLinkNode, LinkNode } from "@lexical/link"; 3 | import { ListItemNode, ListNode } from "@lexical/list"; 4 | import type { InitialConfigType } from "@lexical/react/LexicalComposer"; 5 | import { HeadingNode, QuoteNode } from "@lexical/rich-text"; 6 | import { TableCellNode, TableNode, TableRowNode } from "@lexical/table"; 7 | 8 | export const initialConfig: InitialConfigType = { 9 | editorState: null, 10 | namespace: "Demo", 11 | nodes: [ 12 | ListNode, 13 | ListItemNode, 14 | LinkNode, 15 | AutoLinkNode, 16 | TableNode, 17 | TableCellNode, 18 | TableRowNode, 19 | HeadingNode, 20 | QuoteNode, 21 | CodeNode, 22 | CodeHighlightNode, 23 | ], 24 | onError: (error: Error) => { 25 | throw error; 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /example/apps/web/src/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | background-color: #f0f0f0; 3 | color: #2d2d2d; 4 | } 5 | 6 | body { 7 | padding: 1em; 8 | box-sizing: border-box; 9 | font-family: Arial, sans-serif; 10 | } 11 | 12 | li { 13 | display: list-item; 14 | } 15 | ul { 16 | list-style-type: disc; 17 | list-style-position: inside; 18 | } 19 | 20 | .root button { 21 | margin-bottom: 24px; 22 | } 23 | 24 | .editor { 25 | padding: 1em; 26 | min-height: 64px; 27 | border: solid 1px #2d2d2d; 28 | border-radius: 0.25em; 29 | } 30 | .editor:focus { 31 | outline: none; 32 | box-shadow: 0 0 0 2px rgba(45, 45, 45, 0.5); 33 | } 34 | 35 | button { 36 | padding: 0.5em 1em; 37 | border: none; 38 | border-radius: 0.25em; 39 | background-color: #2d2d2d; 40 | color: #f0f0f0; 41 | cursor: pointer; 42 | } 43 | button:focus { 44 | outline: none; 45 | box-shadow: 0 0 0 2px #2d2d2d; 46 | } 47 | -------------------------------------------------------------------------------- /src/yjs/internal.ts: -------------------------------------------------------------------------------- 1 | import type { WSSharedDoc } from "./remote"; 2 | import type { YTransactionStorageImpl } from "./storage"; 3 | 4 | export interface InternalYDurableObject { 5 | // private state 6 | doc: WSSharedDoc; 7 | storage: YTransactionStorageImpl; 8 | sessions: Map void>; 9 | awarenessClients: Set; 10 | 11 | // private api 12 | 13 | onStart(): Promise; 14 | createRoom(roomId: string): WebSocket; 15 | 16 | registerWebSocket(ws: WebSocket): void; 17 | unregisterWebSocket(ws: WebSocket): void; 18 | cleanup(): void; 19 | 20 | // public api 21 | fetch(request: Request): Promise; 22 | webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): void; 23 | webSocketError(ws: WebSocket): void; 24 | webSocketClose(ws: WebSocket): void; 25 | 26 | getYDoc(): Promise; 27 | updateYDoc(update: Uint8Array): Promise; 28 | } 29 | -------------------------------------------------------------------------------- /src/yjs/client/setup.ts: -------------------------------------------------------------------------------- 1 | import { toUint8Array, writeVarUint8Array } from "lib0/encoding"; 2 | import { encodeAwarenessUpdate } from "y-protocols/awareness"; 3 | import { writeSyncStep1 } from "y-protocols/sync"; 4 | 5 | import { createTypedEncoder } from "../message-type"; 6 | 7 | import type { RemoteDoc } from "../remote"; 8 | 9 | export const setupWSConnection = (ws: WebSocket, doc: RemoteDoc) => { 10 | { 11 | const encoder = createTypedEncoder("sync"); 12 | writeSyncStep1(encoder, doc); 13 | ws.send(toUint8Array(encoder)); 14 | } 15 | 16 | { 17 | const states = doc.awareness.getStates(); 18 | if (states.size > 0) { 19 | const encoder = createTypedEncoder("awareness"); 20 | const update = encodeAwarenessUpdate( 21 | doc.awareness, 22 | Array.from(states.keys()), 23 | ); 24 | writeVarUint8Array(encoder, update); 25 | 26 | ws.send(toUint8Array(encoder)); 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/middleware/upgrade.test.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | 3 | import { upgrade } from "."; 4 | 5 | describe("Upgrade Middleware", () => { 6 | let app: Hono; 7 | 8 | beforeEach(() => { 9 | app = new Hono(); 10 | app.get("/", upgrade(), (c) => c.text("WebSocket connection established")); 11 | }); 12 | 13 | it("returns status 426 when Upgrade header is not websocket", async () => { 14 | const req = new Request("http://localhost/", { 15 | headers: { Upgrade: "non-websocket" }, 16 | }); 17 | const res = await app.request(req); 18 | expect(res.status).toBe(426); 19 | expect(await res.text()).toBe("Expected websocket"); 20 | }); 21 | 22 | it("passes to the next middleware when Upgrade header is websocket", async () => { 23 | const req = new Request("http://localhost/", { 24 | headers: { Upgrade: "websocket" }, 25 | }); 26 | const res = await app.request(req); 27 | expect(res.status).toBe(200); 28 | expect(await res.text()).toBe("WebSocket connection established"); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 GitHub, Inc. and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | env: 13 | NODE_VERSION: "22.14.0" 14 | PNPM_VERSION: "10.8.1" 15 | 16 | jobs: 17 | release: 18 | name: Release 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout Repo 22 | uses: actions/checkout@v4 23 | 24 | - uses: pnpm/action-setup@v3 25 | with: 26 | version: ${{ env.PNPM_VERSION }} 27 | 28 | - name: Setup Node.js 29 | uses: actions/setup-node@v4 30 | with: 31 | node-version: ${{ env.NODE_VERSION }} 32 | cache: "pnpm" 33 | 34 | - name: Install Dependencies 35 | run: pnpm i 36 | 37 | - name: Build 38 | run: | 39 | pnpm run build 40 | pnpm run publint 41 | 42 | - name: Create Release Pull Request 43 | id: changesets 44 | uses: changesets/action@v1 45 | with: 46 | publish: pnpm run release 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 50 | -------------------------------------------------------------------------------- /example/apps/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "typecheck": "tsc --noEmit", 11 | "lint": "biome check src", 12 | "fmt": "biome check --apply src" 13 | }, 14 | "dependencies": { 15 | "@acab/reset.css": "^0.8.0", 16 | "@lexical/code": "^0.13.1", 17 | "@lexical/html": "^0.13.1", 18 | "@lexical/link": "^0.13.1", 19 | "@lexical/list": "^0.13.1", 20 | "@lexical/markdown": "^0.13.1", 21 | "@lexical/react": "^0.13.1", 22 | "@lexical/rich-text": "^0.13.1", 23 | "@lexical/table": "^0.13.1", 24 | "hono": "^4.5.3", 25 | "lexical": "^0.13.1", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "y-websocket": "^1.5.1", 29 | "yjs": "^13.6.10" 30 | }, 31 | "devDependencies": { 32 | "@biomejs/biome": "^1.5.3", 33 | "@lexical/yjs": "^0.13.1", 34 | "@types/react": "^18.2.43", 35 | "@types/react-dom": "^18.2.17", 36 | "@vitejs/plugin-react-swc": "^3.5.0", 37 | "typescript": "^5.2.2", 38 | "vite": "^5.0.8", 39 | "workers": "workspace:*" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/yjs/message-type/messaeg-type.test.ts: -------------------------------------------------------------------------------- 1 | import { createDecoder, readVarUint } from "lib0/decoding"; 2 | import { toUint8Array } from "lib0/encoding"; 3 | import { describe, it, expect } from "vitest"; 4 | 5 | import { createTypedEncoder, messageType } from "."; 6 | 7 | describe("createTypedEncoder", () => { 8 | // Define test cases for different message types 9 | const cases = [ 10 | ["sync", messageType.sync], 11 | ["awareness", messageType.awareness], 12 | ] as const; 13 | 14 | it.each(cases)( 15 | "should initialize an encoder with the '%s' message type", 16 | (type, expectedCode) => { 17 | const encoder = createTypedEncoder(type); 18 | const uint8Array = toUint8Array(encoder); 19 | const decoder = createDecoder(uint8Array); 20 | const readType = readVarUint(decoder); 21 | 22 | expect(readType).toBe(expectedCode); 23 | }, 24 | ); 25 | 26 | // Optionally, test for handling invalid input keys if the function needs to handle such cases 27 | it("should throw an error for an invalid type key", () => { 28 | const invalidType = "invalidType"; 29 | expect(() => 30 | createTypedEncoder(invalidType as keyof typeof messageType), 31 | ).toThrowError(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /example/apps/web/src/description/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./styles.module.css"; 2 | 3 | export const Description = () => { 4 | return ( 5 |
6 |

y-durableobjects

7 |

8 | The y-durableobjects library is designed to facilitate real-time 9 | collaboration in Cloudflare Workers environment using Yjs and Durable Objects. It provides a straightforward way 10 | to integrate Yjs for decentralized, scalable real-time editing features. 11 |

12 |

13 | GitHub:{" "} 14 | 20 | https://github.com/napolab/y-durableobjects 21 | 22 |

23 |

24 | npm:{" "} 25 | 31 | https://www.npmjs.com/package/y-durableobjects 32 | 33 |

34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /example/apps/web/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { hc } from "hono/client"; 3 | 4 | import { upgrade } from "./middleware"; 5 | 6 | import type { YDurableObjectsAppType } from "./yjs"; 7 | import type { Env } from "hono"; 8 | 9 | const app = new Hono(); 10 | 11 | type Selector = (c: E["Bindings"]) => DurableObjectNamespace; 12 | 13 | export const yRoute = (selector: Selector) => { 14 | const route = app.get("/:id", upgrade(), async (c) => { 15 | const obj = selector(c.env as E["Bindings"]); 16 | const stub = obj.get(obj.idFromName(c.req.param("id"))); 17 | 18 | // get websocket connection 19 | const url = new URL("/", c.req.url); 20 | const client = hc(url.toString(), { 21 | fetch: stub.fetch.bind(stub), 22 | }); 23 | const res = await client.rooms[":roomId"].$get( 24 | { param: { roomId: c.req.param("id") } }, 25 | { init: { headers: c.req.raw.headers } }, 26 | ); 27 | 28 | return new Response(null, { 29 | webSocket: res.webSocket, 30 | status: res.status, 31 | statusText: res.statusText, 32 | }); 33 | }); 34 | 35 | return route; 36 | }; 37 | 38 | export { YDurableObjects, type YDurableObjectsAppType } from "./yjs"; 39 | export type YRoute = ReturnType; 40 | export type { YTransactionStorage } from "./yjs/storage"; 41 | export { type RemoteDoc, WSSharedDoc } from "./yjs/remote"; 42 | -------------------------------------------------------------------------------- /example/apps/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | y-durableobjects demo site 9 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/apps/web/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/yjs/hono/create-app.test.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { vi } from "vitest"; 3 | 4 | import { createApp } from "."; 5 | 6 | type Service = { 7 | createRoom(roomId: string): Promise | WebSocket; 8 | }; 9 | 10 | describe("Room Creation API", () => { 11 | let app: Hono; 12 | let service: Service; 13 | 14 | beforeEach(() => { 15 | service = { 16 | createRoom: vi.fn(), 17 | }; 18 | app = createApp(service); 19 | }); 20 | 21 | it("should initialize correctly", () => { 22 | expect(app).toBeInstanceOf(Hono); 23 | }); 24 | 25 | it("should create a room and establish a WebSocket connection", async () => { 26 | const mockWebSocket = new WebSocket("ws://example.com"); 27 | service.createRoom = vi.fn().mockResolvedValue(mockWebSocket); 28 | 29 | const response = await app.request("http://localhost/rooms/testRoom", { 30 | headers: { 31 | Upgrade: "websocket", 32 | }, 33 | }); 34 | 35 | expect(response.status).toBe(101); 36 | expect(response.webSocket).toBe(mockWebSocket); 37 | expect(service.createRoom).toHaveBeenCalledWith("testRoom"); 38 | }); 39 | 40 | it("should handle createRoom error correctly", async () => { 41 | service.createRoom = vi.fn().mockRejectedValue(new Error("Service Error")); 42 | 43 | const response = await app.request("http://localhost/rooms/testRoom", { 44 | headers: { 45 | Upgrade: "websocket", 46 | }, 47 | }); 48 | 49 | expect(response.status).toBe(500); 50 | expect(service.createRoom).toHaveBeenCalledWith("testRoom"); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Yjs on Cloudflare Workers with Durable Objects Demo 2 | 3 | This project demonstrates the integration of Yjs, a real-time collaboration framework, with Cloudflare Workers using Durable Objects, eliminating the dependency on Node.js. This setup is inspired by the `y-websocket` adapter and aims to provide a scalable and efficient solution for real-time collaborative applications. 4 | 5 | [![Yjs on Cloudflare Workers with Durable Objects Demo Movie](https://i.gyazo.com/e94637740dbb11fc5107b0cd0850326d.gif)](https://gyazo.com/e94637740dbb11fc5107b0cd0850326d) 6 | 7 | Demo: https://yjs.napochaan.dev/ 8 | 9 | ## Getting Started 10 | 11 | To get the demo running on your local environment, follow these steps: 12 | 13 | ### Prerequisites 14 | 15 | Ensure you have the latest version of Node.js installed on your machine. 16 | 17 | ### Installation 18 | 19 | Clone the repository and install the dependencies: 20 | 21 | ```bash 22 | git clone git@github.com:napolab/y-durableobjects.git 23 | cd y-durableobjects/example 24 | npm install 25 | ``` 26 | 27 | ### Running the UI 28 | 29 | Navigate to the UI application directory and start the development server: 30 | 31 | ```bash 32 | cd apps/web 33 | npm run dev 34 | ``` 35 | 36 | This command will serve the UI on a local web server. Open your preferred web browser and navigate to the provided URL to interact with the UI. 37 | 38 | ### Running the Server 39 | 40 | To start the server that includes the Cloudflare Worker with Durable Objects: 41 | 42 | ```bash 43 | cd apps/workers 44 | npm run dev 45 | ``` 46 | 47 | This will emulate the Cloudflare Worker environment locally, allowing you to test the Yjs integration. 48 | -------------------------------------------------------------------------------- /src/e2e/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { hc } from "hono/client"; 3 | import { fromUint8Array } from "js-base64"; 4 | 5 | import { yRoute } from ".."; 6 | import { upgrade } from "../middleware"; 7 | import { YDurableObjects } from "../yjs"; 8 | 9 | import type { CloudflareEnv } from "./types"; 10 | import type { YDurableObjectsAppType } from "../yjs"; 11 | 12 | type Env = { 13 | Bindings: { [K in keyof CloudflareEnv]: CloudflareEnv[K] }; 14 | }; 15 | 16 | const app = new Hono(); 17 | 18 | const route = app 19 | .route( 20 | "/shorthand", 21 | yRoute((env) => env.Y_DURABLE_OBJECTS), 22 | ) 23 | .get("/rooms/:id", upgrade(), async (c) => { 24 | const roomId = c.req.param("id"); 25 | const id = c.env.Y_DURABLE_OBJECTS.idFromName(roomId); 26 | const stub = c.env.Y_DURABLE_OBJECTS.get(id); 27 | 28 | const url = new URL("/", c.req.url); 29 | const client = hc(url.toString(), { 30 | fetch: stub.fetch.bind(stub), 31 | }); 32 | const res = await client.rooms[":roomId"].$get( 33 | { param: { roomId } }, 34 | { init: { headers: c.req.raw.headers } }, 35 | ); 36 | 37 | return new Response(null, { 38 | webSocket: res.webSocket, 39 | status: res.status, 40 | statusText: res.statusText, 41 | }); 42 | }) 43 | .get("/rooms/:id/state", async (c) => { 44 | const roomId = c.req.param("id"); 45 | const id = c.env.Y_DURABLE_OBJECTS.idFromName(roomId); 46 | const stub = c.env.Y_DURABLE_OBJECTS.get(id); 47 | 48 | const doc = await stub.getYDoc(); 49 | const base64 = fromUint8Array(doc); 50 | 51 | return c.json({ doc: base64 }, 200); 52 | }) 53 | .post("/rooms/:id/update", async (c) => { 54 | const roomId = c.req.param("id"); 55 | const id = c.env.Y_DURABLE_OBJECTS.idFromName(roomId); 56 | const stub = c.env.Y_DURABLE_OBJECTS.get(id); 57 | 58 | const buffer = await c.req.arrayBuffer(); 59 | const update = new Uint8Array(buffer); 60 | 61 | await stub.updateYDoc(update); 62 | 63 | return c.json(null, 200); 64 | }); 65 | 66 | export default route; 67 | export type AppType = typeof route; 68 | export { YDurableObjects }; 69 | -------------------------------------------------------------------------------- /src/yjs/client/setup.test.ts: -------------------------------------------------------------------------------- 1 | import { createDecoder, readVarUint, readVarUint8Array } from "lib0/decoding"; 2 | import { vi } from "vitest"; 3 | import { Awareness } from "y-protocols/awareness"; 4 | import { Doc } from "yjs"; 5 | 6 | import { messageType } from "../message-type"; 7 | 8 | import { setupWSConnection } from "./setup"; 9 | 10 | import type { RemoteDoc } from "../remote"; 11 | import type { Mocked } from "vitest"; 12 | 13 | class TestDoc extends Doc implements RemoteDoc { 14 | awareness = new Awareness(this); 15 | } 16 | 17 | describe("setupWSConnection", () => { 18 | let ws: Mocked; 19 | 20 | beforeEach(() => { 21 | ws = { send: vi.fn() } as unknown as Mocked; 22 | }); 23 | 24 | afterEach(() => { 25 | vi.restoreAllMocks(); 26 | }); 27 | 28 | const decodeMessage = (message: Uint8Array) => { 29 | const decoder = createDecoder(message); 30 | const type = readVarUint(decoder); 31 | const data = readVarUint8Array(decoder); 32 | 33 | return { type, data }; 34 | }; 35 | 36 | it("sends sync and awareness messages correctly", () => { 37 | const doc = new TestDoc(); 38 | doc.getText("root").insert(0, "test"); 39 | 40 | setupWSConnection(ws, doc); 41 | 42 | expect(ws.send).toHaveBeenCalledTimes(2); 43 | const [syncMessage, awarenessMessage] = ws.send.mock.calls 44 | .map((call) => call[0]) 45 | .filter((m): m is Uint8Array => m instanceof Uint8Array); 46 | 47 | const { type: syncType } = decodeMessage(syncMessage); 48 | expect(syncType).toBe(messageType.sync); 49 | 50 | const { type: awarenessType } = decodeMessage(awarenessMessage); 51 | expect(awarenessType).toBe(messageType.awareness); 52 | }); 53 | 54 | it("does not send awareness message if there are no states", () => { 55 | const doc = new TestDoc(); 56 | doc.awareness.getStates = vi.fn(() => new Map()); 57 | 58 | setupWSConnection(ws, doc); 59 | 60 | expect(ws.send).toHaveBeenCalledTimes(1); 61 | const [syncMessage] = ws.send.mock.calls 62 | .map((call) => call[0]) 63 | .filter((m): m is Uint8Array => m instanceof Uint8Array); 64 | 65 | const { type } = decodeMessage(syncMessage); 66 | expect(type).toBe(messageType.sync); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: PR Checks 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | env: 11 | NODE_VERSION: "22.14.0" 12 | PNPM_VERSION: "10.8.1" 13 | 14 | jobs: 15 | type-check: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: pnpm/action-setup@v3 21 | with: 22 | version: ${{ env.PNPM_VERSION }} 23 | 24 | - name: Setup Node.js 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ env.NODE_VERSION }} 28 | cache: "pnpm" 29 | 30 | - name: Install dependencies 31 | run: pnpm i 32 | 33 | - name: Type Check 34 | run: pnpm run typecheck 35 | 36 | lint: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v4 40 | 41 | - uses: pnpm/action-setup@v3 42 | with: 43 | version: ${{ env.PNPM_VERSION }} 44 | 45 | - name: Setup Node.js 46 | uses: actions/setup-node@v4 47 | with: 48 | node-version: ${{ env.NODE_VERSION }} 49 | cache: "pnpm" 50 | 51 | - name: Install dependencies 52 | run: pnpm i 53 | 54 | - name: Lint 55 | run: pnpm run lint 56 | 57 | publint: 58 | runs-on: ubuntu-latest 59 | steps: 60 | - uses: actions/checkout@v4 61 | 62 | - uses: pnpm/action-setup@v3 63 | with: 64 | version: ${{ env.PNPM_VERSION }} 65 | 66 | - name: Setup Node.js 67 | uses: actions/setup-node@v4 68 | with: 69 | node-version: ${{ env.NODE_VERSION }} 70 | cache: "pnpm" 71 | 72 | - name: Install dependencies 73 | run: pnpm i 74 | 75 | - name: Build 76 | run: pnpm run build 77 | 78 | - name: Publint 79 | run: pnpm run publint 80 | 81 | test: 82 | runs-on: ubuntu-latest 83 | steps: 84 | - uses: actions/checkout@v4 85 | 86 | - uses: pnpm/action-setup@v3 87 | with: 88 | version: ${{ env.PNPM_VERSION }} 89 | 90 | - name: Setup Node.js 91 | uses: actions/setup-node@v4 92 | with: 93 | node-version: ${{ env.NODE_VERSION }} 94 | cache: "pnpm" 95 | 96 | - name: Install dependencies 97 | run: pnpm i 98 | 99 | - name: test 100 | run: pnpm run test 101 | -------------------------------------------------------------------------------- /example/apps/web/src/editor/index.tsx: -------------------------------------------------------------------------------- 1 | import { $generateHtmlFromNodes } from "@lexical/html"; 2 | import { TRANSFORMERS } from "@lexical/markdown"; 3 | import { CollaborationPlugin } from "@lexical/react/LexicalCollaborationPlugin"; 4 | import { LexicalComposer } from "@lexical/react/LexicalComposer"; 5 | import { ContentEditable } from "@lexical/react/LexicalContentEditable"; 6 | import { EditorRefPlugin } from "@lexical/react/LexicalEditorRefPlugin"; 7 | import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"; 8 | import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; 9 | import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin"; 10 | import { ListPlugin } from "@lexical/react/LexicalListPlugin"; 11 | import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin"; 12 | import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin"; 13 | import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin"; 14 | import { TablePlugin } from "@lexical/react/LexicalTablePlugin"; 15 | import type { LexicalEditor } from "lexical"; 16 | import { type FC, useCallback, useRef } from "react"; 17 | import { initialConfig } from "./config"; 18 | import { providerFactory } from "./provider"; 19 | 20 | type Props = { 21 | id: string; 22 | }; 23 | 24 | const Editor: FC = ({ id }) => { 25 | const ref = useRef(null); 26 | 27 | const copyClipboard = useCallback(async () => { 28 | const editor = ref.current; 29 | if (editor === null) return; 30 | const code = await new Promise((resolve) => { 31 | editor.getEditorState().read(() => { 32 | resolve($generateHtmlFromNodes(editor, null)); 33 | }); 34 | }); 35 | await navigator.clipboard.writeText(code); 36 | alert("HTML copied to clipboard"); 37 | }, []); 38 | 39 | return ( 40 |
41 | 44 | 45 | } 47 | placeholder={
Enter some text...
} 48 | ErrorBoundary={LexicalErrorBoundary} 49 | /> 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 |
60 | ); 61 | }; 62 | 63 | export default Editor; 64 | -------------------------------------------------------------------------------- /src/yjs/remote/ws-shared-doc.test.ts: -------------------------------------------------------------------------------- 1 | import { createDecoder, readVarUint } from "lib0/decoding"; 2 | import { createEncoder, toUint8Array, writeVarUint } from "lib0/encoding"; 3 | import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; 4 | import { readSyncMessage, writeUpdate } from "y-protocols/sync"; 5 | import { Doc, encodeStateAsUpdate } from "yjs"; 6 | 7 | import { messageType } from "../message-type"; 8 | 9 | import { WSSharedDoc } from "./ws-shared-doc"; 10 | 11 | import type { Mock } from "vitest"; 12 | 13 | // Helper to create updates based on document type 14 | const createYDocMessage = (content: string = "Hello World!") => { 15 | const doc = new Doc(); 16 | doc.getText("root").insert(0, content); 17 | 18 | return encodeStateAsUpdate(doc); 19 | }; 20 | 21 | // Helper to create an encoded message from an update 22 | const createSyncMessage = (update: Uint8Array) => { 23 | const encoder = createEncoder(); 24 | writeVarUint(encoder, messageType.sync); 25 | writeUpdate(encoder, update); 26 | 27 | return toUint8Array(encoder); 28 | }; 29 | 30 | // Helper to apply a received message to a new document 31 | const applyMessage = (message: Uint8Array) => { 32 | const receivedDoc = new Doc(); 33 | const decoder = createDecoder(message); 34 | readVarUint(decoder); 35 | readSyncMessage(decoder, createEncoder(), receivedDoc, null); 36 | 37 | return receivedDoc; 38 | }; 39 | 40 | describe("WSSharedDoc", () => { 41 | let doc: WSSharedDoc; 42 | let mockListener: Mock; 43 | 44 | beforeEach(() => { 45 | doc = new WSSharedDoc(); 46 | mockListener = vi.fn(); 47 | doc.notify(mockListener); 48 | }); 49 | 50 | afterEach(() => { 51 | vi.restoreAllMocks(); 52 | }); 53 | 54 | describe("Handling Yjs Updates", () => { 55 | it("should process Yjs updates and notify listeners", () => { 56 | const update = createYDocMessage("Hello, world!"); 57 | const message = createSyncMessage(update); 58 | 59 | doc.update(message); 60 | 61 | expect(mockListener).toHaveBeenCalledWith(expect.any(Uint8Array)); 62 | expect(mockListener.mock.calls[0][0]).toEqual(message); 63 | 64 | const receivedDoc = applyMessage(mockListener.mock.calls[0][0]); 65 | expect(receivedDoc.getText("root").toString()).toEqual("Hello, world!"); 66 | }); 67 | }); 68 | 69 | describe("Event Notification", () => { 70 | it("should add and remove listeners correctly", () => { 71 | const anotherListener = vi.fn(); 72 | const unsubscribe = doc.notify(anotherListener); 73 | const message1 = createSyncMessage(createYDocMessage("text1")); 74 | const message2 = createSyncMessage(createYDocMessage("text2")); 75 | 76 | doc.update(message1); 77 | 78 | expect(mockListener).toHaveBeenCalledTimes(1); 79 | expect(anotherListener).toHaveBeenCalledTimes(1); 80 | 81 | unsubscribe(); 82 | doc.update(message2); 83 | 84 | expect(mockListener).toHaveBeenCalledTimes(2); 85 | expect(anotherListener).toHaveBeenCalledTimes(1); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | `y-durableobjects` is a library for real-time collaboration in Cloudflare Workers using Yjs and Durable Objects. It provides WebSocket-based synchronization for Yjs documents with persistent storage. 8 | 9 | ## Common Development Commands 10 | 11 | ### Build and Development 12 | 13 | - `pnpm build` - Build the library using tsup 14 | - `pnpm typecheck` - Run TypeScript type checking 15 | - `pnpm lint` - Run all linters (ESLint and Prettier) 16 | - `pnpm fmt` - Format code with Prettier and ESLint 17 | - `pnpm test` - Run tests with Vitest 18 | 19 | ### Testing 20 | 21 | - Tests use Vitest with Cloudflare Workers pool 22 | - Run a single test: `pnpm test path/to/test.test.ts` 23 | - Tests are configured with `vitest.config.ts` using Cloudflare Workers environment 24 | 25 | ### Release Process 26 | 27 | - `pnpm release` - Publish packages using changesets 28 | 29 | ## Architecture 30 | 31 | ### Core Components 32 | 33 | 1. **YDurableObjects** (`src/yjs/index.ts`) 34 | 35 | - Main Durable Object class extending Cloudflare's DurableObject 36 | - Manages WebSocket connections and Yjs document synchronization 37 | - Handles persistence through YTransactionStorage 38 | - Provides JS RPC methods: `getYDoc()` and `updateYDoc()` 39 | 40 | 2. **WSSharedDoc** (`src/yjs/remote/ws-shared-doc.ts`) 41 | 42 | - Yjs document wrapper with WebSocket notification support 43 | - Manages awareness protocol for collaborative features 44 | - Handles document updates and broadcasts 45 | 46 | 3. **YTransactionStorage** (`src/yjs/storage/index.ts`) 47 | 48 | - Persistence layer for Yjs updates 49 | - Uses Durable Object storage with transaction support 50 | - Implements incremental update storage with periodic compaction 51 | 52 | 4. **Hono Integration** (`src/index.ts`) 53 | - Provides `yRoute()` helper for easy Hono app integration 54 | - Handles WebSocket upgrade and connection routing 55 | 56 | ### Message Protocol 57 | 58 | - Uses binary WebSocket messages for Yjs synchronization 59 | - Message types defined in `src/yjs/message-type/index.ts` 60 | - Supports sync, awareness, and auth message types 61 | 62 | ### Development Constraints 63 | 64 | 1. **Cloudflare Workers Environment** 65 | 66 | - Code must be compatible with Workers runtime 67 | - Uses Cloudflare-specific APIs (DurableObject, WebSocketPair) 68 | - External imports marked in tsup config: `hono`, `/cloudflare:/` 69 | 70 | 2. **TypeScript Strict Mode** 71 | 72 | - Strict boolean expressions enforced 73 | - No explicit any allowed 74 | - Consistent type imports/exports required 75 | 76 | 3. **Code Style** 77 | - Kebab-case for filenames 78 | - No console.log in production code 79 | - Import ordering enforced by ESLint 80 | - Prettier formatting required 81 | 82 | ### Testing Approach 83 | 84 | Tests follow these patterns: 85 | 86 | - Unit tests for individual components 87 | - Integration tests using Cloudflare Workers test environment 88 | - WebSocket connection tests with mock implementations 89 | - Storage tests with in-memory implementations 90 | -------------------------------------------------------------------------------- /src/yjs/remote/ws-shared-doc.ts: -------------------------------------------------------------------------------- 1 | import { createDecoder, readVarUint, readVarUint8Array } from "lib0/decoding"; 2 | import { 3 | createEncoder, 4 | length, 5 | toUint8Array, 6 | writeVarUint, 7 | writeVarUint8Array, 8 | } from "lib0/encoding"; 9 | import { 10 | applyAwarenessUpdate, 11 | Awareness, 12 | encodeAwarenessUpdate, 13 | } from "y-protocols/awareness"; 14 | import { readSyncMessage, writeUpdate } from "y-protocols/sync"; 15 | import { Doc } from "yjs"; 16 | 17 | import { createTypedEncoder, messageType } from "../message-type"; 18 | 19 | import type { AwarenessChanges, RemoteDoc } from "."; 20 | 21 | type Listener = (message: T) => void; 22 | type Unsubscribe = () => void; 23 | interface Notification extends RemoteDoc { 24 | notify(cb: Listener): Unsubscribe; 25 | } 26 | 27 | export class WSSharedDoc extends Doc implements Notification { 28 | private listeners = new Set>(); 29 | readonly awareness = new Awareness(this); 30 | 31 | constructor(gc = true) { 32 | super({ gc }); 33 | this.awareness.setLocalState(null); 34 | 35 | // カーソルなどの付加情報の更新通知 36 | this.awareness.on("update", (changes: AwarenessChanges) => { 37 | this.awarenessChangeHandler(changes); 38 | }); 39 | // yDoc の更新通知 40 | this.on("update", (update: Uint8Array) => { 41 | this.syncMessageHandler(update); 42 | }); 43 | } 44 | 45 | update(message: Uint8Array) { 46 | const encoder = createEncoder(); 47 | const decoder = createDecoder(message); 48 | const type = readVarUint(decoder); 49 | 50 | switch (type) { 51 | case messageType.sync: { 52 | writeVarUint(encoder, messageType.sync); 53 | readSyncMessage(decoder, encoder, this, null); 54 | 55 | // changed remote doc 56 | if (length(encoder) > 1) { 57 | this._notify(toUint8Array(encoder)); 58 | } 59 | break; 60 | } 61 | case messageType.awareness: { 62 | applyAwarenessUpdate(this.awareness, readVarUint8Array(decoder), null); 63 | break; 64 | } 65 | } 66 | } 67 | 68 | notify(listener: Listener) { 69 | this.listeners.add(listener); 70 | 71 | return () => { 72 | this.listeners.delete(listener); 73 | }; 74 | } 75 | 76 | private syncMessageHandler(update: Uint8Array) { 77 | const encoder = createTypedEncoder("sync"); 78 | writeUpdate(encoder, update); 79 | 80 | this._notify(toUint8Array(encoder)); 81 | } 82 | private awarenessChangeHandler({ 83 | added, 84 | updated, 85 | removed, 86 | }: AwarenessChanges) { 87 | const changed = [...added, ...updated, ...removed]; 88 | const encoder = createTypedEncoder("awareness"); 89 | const update = encodeAwarenessUpdate( 90 | this.awareness, 91 | changed, 92 | this.awareness.states, 93 | ); 94 | writeVarUint8Array(encoder, update); 95 | 96 | this._notify(toUint8Array(encoder)); 97 | } 98 | 99 | private _notify(message: Uint8Array) { 100 | for (const subscriber of this.listeners) { 101 | subscriber(message); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/e2e/e2e.test.ts: -------------------------------------------------------------------------------- 1 | import { SELF, env, runInDurableObject } from "cloudflare:test"; 2 | import { hc } from "hono/client"; 3 | import { fromUint8Array } from "js-base64"; 4 | 5 | import { createSyncMessage, createYDocMessage } from "./helper"; 6 | 7 | import type { AppType } from "."; 8 | import type { InternalYDurableObject } from "../yjs/internal"; 9 | 10 | describe("yRoute Shorthand", () => { 11 | it("should return a route", async () => { 12 | const res = await SELF.fetch("http://localhost/shorthand/1", { 13 | headers: { 14 | Upgrade: "websocket", 15 | }, 16 | }); 17 | expect(res.status).toBe(101); 18 | expect(res.webSocket).toBeInstanceOf(WebSocket); 19 | }); 20 | 21 | it("should return status 426 if no headers are present", async () => { 22 | const res = await SELF.fetch("http://localhost/shorthand/1"); 23 | expect(res.status).toBe(426); 24 | await expect(res.text()).resolves.toBe("Expected websocket"); 25 | }); 26 | 27 | // eslint-disable-next-line vitest/expect-expect 28 | it("should verify the typing of the shorthand route", () => { 29 | const client = hc("http://localhost", { 30 | fetch: SELF.fetch.bind(SELF), 31 | }); 32 | 33 | expectTypeOf(client.shorthand[":id"].$ws).toEqualTypeOf< 34 | (args?: { param: { id: string } } | undefined) => WebSocket 35 | >(); 36 | }); 37 | }); 38 | 39 | describe("endpoint request", () => { 40 | it("should return a WebSocket response", async () => { 41 | const res = await SELF.fetch("http://localhost/rooms/1", { 42 | headers: { Upgrade: "websocket" }, 43 | }); 44 | expect(res.status).toBe(101); 45 | expect(res.webSocket).toBeInstanceOf(WebSocket); 46 | }); 47 | 48 | it("should return status 426 if no headers are present", async () => { 49 | const res = await SELF.fetch("http://localhost/rooms/1"); 50 | expect(res.status).toBe(426); 51 | await expect(res.text()).resolves.toBe("Expected websocket"); 52 | }); 53 | 54 | it("should get the YDoc state", async () => { 55 | const roomId = "1"; 56 | const id = env.Y_DURABLE_OBJECTS.idFromName(roomId); 57 | const stub = env.Y_DURABLE_OBJECTS.get(id); 58 | const message = createYDocMessage("get state"); 59 | const update = createSyncMessage(message); 60 | 61 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 62 | await instance.updateYDoc(update.slice(0)); 63 | }); 64 | 65 | const res = await SELF.fetch(`http://localhost/rooms/${roomId}/state`); 66 | 67 | expect(res.status).toBe(200); 68 | const json = await res.json(); 69 | expect(json).toEqual({ doc: fromUint8Array(message) }); 70 | }); 71 | 72 | it("should update the YDoc state", async () => { 73 | const message = createYDocMessage("get state"); 74 | const update = createSyncMessage(message); 75 | 76 | const roomId = "1"; 77 | const id = env.Y_DURABLE_OBJECTS.idFromName(roomId); 78 | const stub = env.Y_DURABLE_OBJECTS.get(id); 79 | 80 | const res = await SELF.fetch(`http://localhost/rooms/${roomId}/update`, { 81 | method: "POST", 82 | body: update.slice(0).buffer, 83 | }); 84 | expect(res.status).toBe(200); 85 | 86 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 87 | const state = await instance.getYDoc(); 88 | expect(state).toEqual(message); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "y-durableobjects", 3 | "version": "1.0.5", 4 | "type": "module", 5 | "main": "dist/index.cjs", 6 | "module": "dist/index.js", 7 | "types": "dist/index.d.cts", 8 | "private": false, 9 | "license": "MIT", 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/napolab/y-durableobjects.git" 16 | }, 17 | "homepage": "https://yjs.napochaan.dev/", 18 | "bugs": { 19 | "url": "https://github.com/napolab/y-durableobjects/issues" 20 | }, 21 | "exports": { 22 | ".": { 23 | "import": { 24 | "types": "./dist/index.d.ts", 25 | "default": "./dist/index.js" 26 | }, 27 | "require": { 28 | "types": "./dist/index.d.cts", 29 | "default": "./dist/index.cjs" 30 | } 31 | }, 32 | "./helpers/upgrade": { 33 | "import": { 34 | "types": "./dist/helpers/upgrade.d.ts", 35 | "default": "./dist/helpers/upgrade.js" 36 | }, 37 | "require": { 38 | "types": "./dist/helpers/upgrade.d.cts", 39 | "default": "./dist/helpers/upgrade.cjs" 40 | } 41 | } 42 | }, 43 | "typesVersions": { 44 | "*": { 45 | "helpers": [ 46 | "./dist/helpers" 47 | ] 48 | } 49 | }, 50 | "scripts": { 51 | "typecheck": "tsc --noEmit", 52 | "build": "tsup", 53 | "lint": "npm-run-all -p lint:*", 54 | "lint:eslint": "eslint . --ext .ts,.tsx", 55 | "lint:prettier": "prettier --check .", 56 | "fmt": "npm-run-all -s fmt:*", 57 | "fmt:prettier": "prettier --write .", 58 | "fmt:eslint": "eslint . --ext .ts,.tsx --fix", 59 | "publint": "publint", 60 | "release": "changeset publish", 61 | "test": "vitest run --passWithNoTests", 62 | "test:watch": "vitest --passWithNoTests" 63 | }, 64 | "author": "naporin0624", 65 | "files": [ 66 | "dist", 67 | "package.json", 68 | "README.md" 69 | ], 70 | "keywords": [ 71 | "cloudflare", 72 | "cloudflareworkers", 73 | "hono", 74 | "durableobjects", 75 | "yjs" 76 | ], 77 | "devDependencies": { 78 | "@changesets/cli": "^2.29.2", 79 | "@cloudflare/vitest-pool-workers": "^0.8.18", 80 | "@eslint/compat": "^1.2.8", 81 | "@eslint/eslintrc": "^3.3.1", 82 | "@eslint/js": "^9.24.0", 83 | "@typescript-eslint/eslint-plugin": "^8.30.1", 84 | "@typescript-eslint/parser": "^8.30.1", 85 | "@vitest/eslint-plugin": "^1.1.42", 86 | "eslint": "^9.24.0", 87 | "eslint-config-prettier": "^10.1.2", 88 | "eslint-config-standard": "^17.1.0", 89 | "eslint-import-resolver-typescript": "^4.3.2", 90 | "eslint-plugin-import-x": "^4.10.5", 91 | "eslint-plugin-jsx-a11y": "^6.10.2", 92 | "eslint-plugin-react": "^7.37.5", 93 | "eslint-plugin-react-hooks": "^5.2.0", 94 | "eslint-plugin-unicorn": "^58.0.0", 95 | "eslint-plugin-unused-imports": "^4.1.4", 96 | "globals": "^16.0.0", 97 | "hono": "^4.7.7", 98 | "js-base64": "^3.7.7", 99 | "npm-run-all": "^4.1.5", 100 | "prettier": "^3.5.3", 101 | "publint": "^0.3.12", 102 | "tsup": "^8.4.0", 103 | "typescript": "^5.8.3", 104 | "typescript-eslint": "^8.30.1", 105 | "vitest": "3.0.9", 106 | "wrangler": "^4.27.0" 107 | }, 108 | "dependencies": { 109 | "lib0": "^0.2.104", 110 | "y-protocols": "^1.0.6", 111 | "yjs": "^13.6.24" 112 | }, 113 | "peerDependencies": { 114 | "hono": ">=4.3" 115 | }, 116 | "packageManager": "pnpm@10.8.1" 117 | } 118 | -------------------------------------------------------------------------------- /src/yjs/storage/index.ts: -------------------------------------------------------------------------------- 1 | import { Doc, applyUpdate, encodeStateAsUpdate } from "yjs"; 2 | 3 | import { storageKey } from "./storage-key"; 4 | 5 | import type { TransactionStorage } from "./type"; 6 | 7 | export interface YTransactionStorage { 8 | getYDoc(): Promise; 9 | storeUpdate(update: Uint8Array): Promise; 10 | commit(): Promise; 11 | } 12 | 13 | type Options = { 14 | /** 15 | * @description default is 10KB 16 | * @default 10 * 1024 * 1 17 | */ 18 | maxBytes?: number; 19 | /** 20 | * @description default is 500 snapshot 21 | * @default 500 22 | */ 23 | maxUpdates?: number; 24 | }; 25 | 26 | export class YTransactionStorageImpl implements YTransactionStorage { 27 | private readonly MAX_BYTES: number; 28 | private readonly MAX_UPDATES: number; 29 | 30 | constructor( 31 | private readonly storage: TransactionStorage, 32 | options?: Options, 33 | ) { 34 | this.MAX_BYTES = options?.maxBytes ?? 10 * 1024; 35 | if (this.MAX_BYTES > 128 * 1024) { 36 | // https://developers.cloudflare.com/durable-objects/platform/limits/ 37 | throw new Error("maxBytes must be less than 128KB"); 38 | } 39 | 40 | this.MAX_UPDATES = options?.maxUpdates ?? 500; 41 | } 42 | 43 | async getYDoc(): Promise { 44 | const snapshot = await this.storage.get( 45 | storageKey({ type: "state", name: "doc" }), 46 | ); 47 | const data = await this.storage.list({ 48 | prefix: storageKey({ type: "update" }), 49 | }); 50 | 51 | const updates: Uint8Array[] = Array.from(data.values()); 52 | const doc = new Doc(); 53 | 54 | doc.transact(() => { 55 | if (snapshot) { 56 | applyUpdate(doc, snapshot); 57 | } 58 | for (const update of updates) { 59 | applyUpdate(doc, update); 60 | } 61 | }); 62 | 63 | return doc; 64 | } 65 | 66 | storeUpdate(update: Uint8Array): Promise { 67 | return this.storage.transaction(async (tx) => { 68 | const bytes = 69 | (await tx.get(storageKey({ type: "state", name: "bytes" }))) ?? 70 | 0; 71 | const count = 72 | (await tx.get(storageKey({ type: "state", name: "count" }))) ?? 73 | 0; 74 | 75 | const updateBytes = bytes + update.byteLength; 76 | const updateCount = count + 1; 77 | 78 | if (updateBytes > this.MAX_BYTES || updateCount > this.MAX_UPDATES) { 79 | const doc = await this.getYDoc(); 80 | applyUpdate(doc, update); 81 | 82 | await this._commit(doc, tx); 83 | } else { 84 | await tx.put(storageKey({ type: "state", name: "bytes" }), updateBytes); 85 | await tx.put(storageKey({ type: "state", name: "count" }), updateCount); 86 | await tx.put(storageKey({ type: "update", name: updateCount }), update); 87 | } 88 | }); 89 | } 90 | 91 | private async _commit(doc: Doc, tx: Omit) { 92 | const data = await tx.list({ 93 | prefix: storageKey({ type: "update" }), 94 | }); 95 | 96 | for (const update of data.values()) { 97 | applyUpdate(doc, update); 98 | } 99 | 100 | const update = encodeStateAsUpdate(doc); 101 | 102 | await tx.delete(Array.from(data.keys())); 103 | await tx.put(storageKey({ type: "state", name: "bytes" }), 0); 104 | await tx.put(storageKey({ type: "state", name: "count" }), 0); 105 | await tx.put(storageKey({ type: "state", name: "doc" }), update); 106 | } 107 | 108 | async commit(): Promise { 109 | const doc = await this.getYDoc(); 110 | 111 | return this.storage.transaction(async (tx) => { 112 | await this._commit(doc, tx); 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /example/apps/web/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/e2e/y-durableobjects.test.ts: -------------------------------------------------------------------------------- 1 | import { env, runInDurableObject } from "cloudflare:test"; 2 | import { hc } from "hono/client"; 3 | import { expect, describe, it } from "vitest"; 4 | 5 | import { YDurableObjects } from "../yjs"; 6 | 7 | import { createSyncMessage, createYDocMessage } from "./helper"; 8 | 9 | import type { YDurableObjectsAppType } from "../yjs"; 10 | import type { InternalYDurableObject } from "../yjs/internal"; 11 | 12 | describe("YDurableObjects", () => { 13 | it("initializes correctly", async () => { 14 | const id = env.Y_DURABLE_OBJECTS.newUniqueId(); 15 | const stub = env.Y_DURABLE_OBJECTS.get(id); 16 | 17 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 18 | expect(instance).toBeInstanceOf(YDurableObjects); 19 | expect(instance.doc).toBeDefined(); 20 | expect(instance.storage).toBeDefined(); 21 | }); 22 | }); 23 | 24 | it("create a room from request", async () => { 25 | const id = env.Y_DURABLE_OBJECTS.newUniqueId(); 26 | const stub = env.Y_DURABLE_OBJECTS.get(id); 27 | 28 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 29 | const client = hc("http://localhost", { 30 | fetch(req: RequestInfo | URL) { 31 | const r = new Request(req); 32 | 33 | return instance.fetch(r); 34 | }, 35 | }); 36 | const res = await client.rooms[":roomId"].$get({ 37 | param: { roomId: "room1" }, 38 | }); 39 | 40 | expect(res.webSocket).toBeInstanceOf(WebSocket); 41 | }); 42 | }); 43 | 44 | it("creates a room correctly", async () => { 45 | const id = env.Y_DURABLE_OBJECTS.newUniqueId(); 46 | const stub = env.Y_DURABLE_OBJECTS.get(id); 47 | 48 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 49 | const roomId = "room1"; 50 | const client = await instance.createRoom(roomId); 51 | 52 | expect(client).toBeInstanceOf(WebSocket); 53 | expect(instance.sessions.size).toBe(1); 54 | }); 55 | }); 56 | 57 | it("updates YDoc correctly", async () => { 58 | const id = env.Y_DURABLE_OBJECTS.newUniqueId(); 59 | const stub = env.Y_DURABLE_OBJECTS.get(id); 60 | 61 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 62 | const message = createYDocMessage(); 63 | const update = createSyncMessage(message); 64 | await instance.updateYDoc(update.slice(0)); 65 | 66 | const docState = await instance.getYDoc(); 67 | expect(docState).toEqual(message); 68 | }); 69 | }); 70 | 71 | it("handles WebSocket messages correctly", async () => { 72 | const id = env.Y_DURABLE_OBJECTS.newUniqueId(); 73 | const stub = env.Y_DURABLE_OBJECTS.get(id); 74 | 75 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 76 | const roomId = "room1"; 77 | const client = await instance.createRoom(roomId); 78 | 79 | const message = createYDocMessage(); 80 | const update = createSyncMessage(message); 81 | await instance.webSocketMessage(client, update.slice(0).buffer); 82 | 83 | const docState = await instance.getYDoc(); 84 | expect(docState).toEqual(message); 85 | }); 86 | }); 87 | 88 | it("handles WebSocket errors correctly", async () => { 89 | const id = env.Y_DURABLE_OBJECTS.newUniqueId(); 90 | const stub = env.Y_DURABLE_OBJECTS.get(id); 91 | 92 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 93 | const roomId = "room1"; 94 | await instance.createRoom(roomId); 95 | const [server] = Array.from(instance.sessions.entries()).at(0)!; 96 | 97 | await instance.webSocketError(server); 98 | 99 | expect(instance.sessions.size).toBe(0); 100 | }); 101 | }); 102 | 103 | it("handles WebSocket close correctly", async () => { 104 | const id = env.Y_DURABLE_OBJECTS.newUniqueId(); 105 | const stub = env.Y_DURABLE_OBJECTS.get(id); 106 | 107 | await runInDurableObject(stub, async (instance: InternalYDurableObject) => { 108 | const roomId = "room1"; 109 | await instance.createRoom(roomId); 110 | const [server] = Array.from(instance.sessions.entries()).at(0)!; 111 | 112 | await instance.webSocketClose(server); 113 | 114 | expect(instance.sessions.size).toBe(0); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /.serena/project.yml: -------------------------------------------------------------------------------- 1 | # language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby) 2 | # * For C, use cpp 3 | # * For JavaScript, use typescript 4 | # Special requirements: 5 | # * csharp: Requires the presence of a .sln file in the project folder. 6 | language: typescript 7 | 8 | # whether to use the project's gitignore file to ignore files 9 | # Added on 2025-04-07 10 | ignore_all_files_in_gitignore: true 11 | # list of additional paths to ignore 12 | # same syntax as gitignore, so you can use * and ** 13 | # Was previously called `ignored_dirs`, please update your config if you are using that. 14 | # Added (renamed)on 2025-04-07 15 | ignored_paths: [] 16 | 17 | # whether the project is in read-only mode 18 | # If set to true, all editing tools will be disabled and attempts to use them will result in an error 19 | # Added on 2025-04-18 20 | read_only: false 21 | 22 | # list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. 23 | # Below is the complete list of tools for convenience. 24 | # To make sure you have the latest list of tools, and to view their descriptions, 25 | # execute `uv run scripts/print_tool_overview.py`. 26 | # 27 | # * `activate_project`: Activates a project by name. 28 | # * `check_onboarding_performed`: Checks whether project onboarding was already performed. 29 | # * `create_text_file`: Creates/overwrites a file in the project directory. 30 | # * `delete_lines`: Deletes a range of lines within a file. 31 | # * `delete_memory`: Deletes a memory from Serena's project-specific memory store. 32 | # * `execute_shell_command`: Executes a shell command. 33 | # * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. 34 | # * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). 35 | # * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). 36 | # * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. 37 | # * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file or directory. 38 | # * `initial_instructions`: Gets the initial instructions for the current project. 39 | # Should only be used in settings where the system prompt cannot be set, 40 | # e.g. in clients you have no control over, like Claude Desktop. 41 | # * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. 42 | # * `insert_at_line`: Inserts content at a given line in a file. 43 | # * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. 44 | # * `list_dir`: Lists files and directories in the given directory (optionally with recursion). 45 | # * `list_memories`: Lists memories in Serena's project-specific memory store. 46 | # * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). 47 | # * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). 48 | # * `read_file`: Reads a file within the project directory. 49 | # * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. 50 | # * `remove_project`: Removes a project from the Serena configuration. 51 | # * `replace_lines`: Replaces a range of lines within a file with new content. 52 | # * `replace_symbol_body`: Replaces the full definition of a symbol. 53 | # * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. 54 | # * `search_for_pattern`: Performs a search for a pattern in the project. 55 | # * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. 56 | # * `switch_modes`: Activates modes by providing a list of their names 57 | # * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. 58 | # * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. 59 | # * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. 60 | # * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. 61 | excluded_tools: [] 62 | 63 | # initial prompt for the project. It will always be given to the LLM upon activating the project 64 | # (contrary to the memories, which are loaded on demand). 65 | initial_prompt: "" 66 | 67 | project_name: "y-durableobjects" 68 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # y-durableobjects 2 | 3 | ## 1.0.5 4 | 5 | ### Patch Changes 6 | 7 | - 3675eb1: Fix Cloudflare Env type collision and improve TypeScript support 8 | 9 | This patch resolves type collision issues and improves TypeScript support: 10 | 11 | - **Fixed CI failures**: Removed unavailable @cloudflare/workers-types references from tsconfig.json that were causing TypeScript compilation errors 12 | - **Adopted wrangler types**: Added worker-configuration.d.ts generated by `wrangler types` (introduced in Wrangler v3) for accurate type definitions 13 | - **Resolved type collisions**: Eliminated conflicts where empty Cloudflare.Env from node_modules overwrote project-specific environment types 14 | - **Enhanced documentation**: Added comprehensive troubleshooting guide for type collision issues 15 | 16 | Users benefit automatically from improved type support without needing to modify their code. The `wrangler types` command is now the recommended approach for generating Cloudflare Workers TypeScript types. 17 | 18 | ## 1.0.4 19 | 20 | ### Patch Changes 21 | 22 | - 3faf6a2: upgrade dependencies 23 | 24 | ## 1.0.3 25 | 26 | ### Patch Changes 27 | 28 | - 5049074: migrate eslint config to flatconfig 29 | 30 | ## 1.0.2 31 | 32 | ### Patch Changes 33 | 34 | - a10b1df: update lib0 35 | 36 | ## 1.0.1 37 | 38 | ### Patch Changes 39 | 40 | - d9839ce: change readme hono env description 41 | 42 | ## 1.0.0 43 | 44 | ### Major Changes 45 | 46 | - 0446fbd: Add JS RPC Support for getYDoc and updateYDoc 47 | 48 | 1. **Major Features**: 49 | 50 | - **JS RPC APIs `getYDoc` and `updateYDoc`**: 51 | - Implemented new JS RPC APIs to fetch (`getYDoc`) and update (`updateYDoc`) YDocs within Durable Objects. 52 | - Allows manipulating YDocs from sources other than WebSocket, enhancing flexibility and control. 53 | 54 | 2. **Hono Integration**: 55 | 56 | - Added examples for integrating `y-durableobjects` with Hono, using both shorthand and detailed methods. 57 | - Demonstrated how to handle WebSocket connections via fetch due to the current limitations of JS RPC (see [Cloudflare issue](https://github.com/cloudflare/workerd/issues/2319)). 58 | 59 | 3. **Extending with JS RPC**: 60 | 61 | - Explained how to extend `y-durableobjects` for advanced operations, including accessing and manipulating protected fields: 62 | - `app`: The Hono app instance used to handle requests. 63 | - `doc`: An instance of `WSSharedDoc` managing the YDoc state. 64 | - `storage`: A `YTransactionStorageImpl` instance for storing and retrieving YDoc updates. 65 | - `sessions`: A map to manage active WebSocket sessions. 66 | - `awarenessClients`: A set to track client awareness states. 67 | - Provided a minimal example of creating a custom Durable Object by extending `YDurableObjects`. 68 | 69 | 4. **Client-side Typed Fetch with Hono RPC**: 70 | 71 | - Included a guide for creating a typed client using `hc` from `hono/client` to facilitate Hono RPC on the client side. 72 | 73 | 5. **Documentation Updates**: 74 | - Updated the README with detailed examples and explanations for the new features and integrations. 75 | - Ensured clarity and ease of understanding for developers looking to utilize the new functionalities. 76 | 77 | ## 0.4.2 78 | 79 | ### Patch Changes 80 | 81 | - 1c87a94: fix yTansactionStorage maxBytes limits 82 | 83 | ## 0.4.1 84 | 85 | ### Patch Changes 86 | 87 | - ee6cdf3: update dependencies pacakges 88 | - a7f594a: export inner modules 89 | 90 | ## 0.4.0 91 | 92 | ### Minor Changes 93 | 94 | - 0167e63: update hono4.3 client types 95 | 96 | ## 0.3.2 97 | 98 | ### Patch Changes 99 | 100 | - 663be81: fix typo package.json keyword 101 | 102 | ## 0.3.1 103 | 104 | ### Patch Changes 105 | 106 | - a3cd212: Fixed a problem that awareness remains when reloading. 107 | 108 | ## 0.3.0 109 | 110 | ### Minor Changes 111 | 112 | - c2d0a17: yDoc must be saved with each change. 113 | 114 | ## 0.2.2 115 | 116 | ### Patch Changes 117 | 118 | - 4176b18: enhanced readme 119 | 120 | ## 0.2.1 121 | 122 | ### Patch Changes 123 | 124 | - 2a92448: fix hono dependencies 125 | 126 | ## 0.2.0 127 | 128 | ### Minor Changes 129 | 130 | - 13dc461: add typed-websocket route 131 | 132 | ## 0.1.3 133 | 134 | ### Patch Changes 135 | 136 | - 9b0bf0a: add homepage field 137 | - 754fcf6: FIX README Hono typing 138 | 139 | ## 0.1.2 140 | 141 | ### Patch Changes 142 | 143 | - b574bbf: fix readme code 144 | 145 | ## 0.1.1 146 | 147 | ### Patch Changes 148 | 149 | - 1e1f4ce: add license 150 | 151 | ## 0.1.0 152 | 153 | ### Minor Changes 154 | 155 | - 75e5ba9: y-durableobjects publish 156 | -------------------------------------------------------------------------------- /src/yjs/index.ts: -------------------------------------------------------------------------------- 1 | import { DurableObject } from "cloudflare:workers"; 2 | import { removeAwarenessStates } from "y-protocols/awareness"; 3 | import { applyUpdate, encodeStateAsUpdate } from "yjs"; 4 | 5 | import { WSSharedDoc } from "../yjs/remote"; 6 | 7 | import { setupWSConnection } from "./client/setup"; 8 | import { createApp } from "./hono"; 9 | import { YTransactionStorageImpl } from "./storage"; 10 | 11 | import type { AwarenessChanges } from "../yjs/remote"; 12 | import type { Env } from "hono"; 13 | 14 | export type WebSocketAttachment = { 15 | roomId: string; 16 | connectedAt: Date; 17 | }; 18 | 19 | export type YDurableObjectsAppType = ReturnType; 20 | 21 | export class YDurableObjects extends DurableObject< 22 | T["Bindings"] 23 | > { 24 | protected app = createApp({ 25 | createRoom: this.createRoom.bind(this), 26 | }); 27 | protected doc = new WSSharedDoc(); 28 | protected storage = new YTransactionStorageImpl({ 29 | get: (key) => this.state.storage.get(key), 30 | list: (options) => this.state.storage.list(options), 31 | put: (key, value) => this.state.storage.put(key, value), 32 | delete: async (key) => 33 | this.state.storage.delete(Array.isArray(key) ? key : [key]), 34 | transaction: (closure) => this.state.storage.transaction(closure), 35 | }); 36 | protected sessions = new Map void>(); 37 | private awarenessClients = new Set(); 38 | 39 | constructor( 40 | public state: DurableObjectState, 41 | public env: T["Bindings"], 42 | ) { 43 | super(state, env); 44 | 45 | void this.state.blockConcurrencyWhile(this.onStart.bind(this)); 46 | } 47 | 48 | protected async onStart(): Promise { 49 | const doc = await this.storage.getYDoc(); 50 | applyUpdate(this.doc, encodeStateAsUpdate(doc)); 51 | 52 | for (const ws of this.state.getWebSockets()) { 53 | this.registerWebSocket(ws); 54 | } 55 | 56 | this.doc.on("update", async (update) => { 57 | await this.storage.storeUpdate(update); 58 | }); 59 | this.doc.awareness.on( 60 | "update", 61 | async ({ added, removed, updated }: AwarenessChanges) => { 62 | for (const client of [...added, ...updated]) { 63 | this.awarenessClients.add(client); 64 | } 65 | for (const client of removed) { 66 | this.awarenessClients.delete(client); 67 | } 68 | }, 69 | ); 70 | } 71 | 72 | protected createRoom(roomId: string) { 73 | const pair = new WebSocketPair(); 74 | const client = pair[0]; 75 | const server = pair[1]; 76 | server.serializeAttachment({ 77 | roomId, 78 | connectedAt: new Date(), 79 | } satisfies WebSocketAttachment); 80 | 81 | this.state.acceptWebSocket(server); 82 | this.registerWebSocket(server); 83 | 84 | return client; 85 | } 86 | 87 | fetch(request: Request): Response | Promise { 88 | return this.app.request(request, undefined, this.env); 89 | } 90 | 91 | async updateYDoc(update: Uint8Array): Promise { 92 | this.doc.update(update); 93 | await this.cleanup(); 94 | } 95 | async getYDoc(): Promise { 96 | return encodeStateAsUpdate(this.doc); 97 | } 98 | 99 | async webSocketMessage( 100 | ws: WebSocket, 101 | message: string | ArrayBuffer, 102 | ): Promise { 103 | if (!(message instanceof ArrayBuffer)) return; 104 | 105 | const update = new Uint8Array(message); 106 | await this.updateYDoc(update); 107 | } 108 | 109 | async webSocketError(ws: WebSocket): Promise { 110 | await this.unregisterWebSocket(ws); 111 | await this.cleanup(); 112 | } 113 | 114 | async webSocketClose(ws: WebSocket): Promise { 115 | await this.unregisterWebSocket(ws); 116 | await this.cleanup(); 117 | } 118 | 119 | protected registerWebSocket(ws: WebSocket) { 120 | setupWSConnection(ws, this.doc); 121 | const s = this.doc.notify((message) => { 122 | ws.send(message); 123 | }); 124 | this.sessions.set(ws, s); 125 | } 126 | 127 | protected async unregisterWebSocket(ws: WebSocket) { 128 | try { 129 | const dispose = this.sessions.get(ws); 130 | dispose?.(); 131 | this.sessions.delete(ws); 132 | const clientIds = this.awarenessClients; 133 | 134 | removeAwarenessStates(this.doc.awareness, Array.from(clientIds), null); 135 | } catch (e) { 136 | // eslint-disable-next-line no-console 137 | console.error(e); 138 | } 139 | } 140 | 141 | protected async cleanup() { 142 | if (this.sessions.size < 1) { 143 | await this.storage.commit(); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { fixupConfigRules } from "@eslint/compat"; 2 | import { FlatCompat } from "@eslint/eslintrc"; 3 | import eslint from "@eslint/js"; 4 | import vitestPlugin from "@vitest/eslint-plugin"; 5 | import prettierConfig from "eslint-config-prettier"; 6 | import importPlugin from "eslint-plugin-import-x"; 7 | import unicornPlugin from "eslint-plugin-unicorn"; 8 | import unusedImportsPlugin from "eslint-plugin-unused-imports"; 9 | import globals from "globals"; 10 | import tseslint from "typescript-eslint"; 11 | 12 | const compat = new FlatCompat(); 13 | const standard = fixupConfigRules(compat.config({ extends: ["standard"] })); 14 | 15 | /** @type {import("eslint").Linter.Config[]} */ 16 | const config = [ 17 | eslint.configs.recommended, 18 | ...standard, 19 | { 20 | ignores: [ 21 | "**/fixtures/**", 22 | "**/dist/**", 23 | "**/build/**", 24 | "**/node_modules/**", 25 | "worker-configuration.d.ts", 26 | ".wrangler", 27 | "example/**", 28 | "*.config.js", 29 | ], 30 | }, 31 | { 32 | files: ["**/*.{js,ts,tsx}"], 33 | languageOptions: { 34 | ecmaVersion: 2024, 35 | sourceType: "module", 36 | parser: tseslint.parser, 37 | parserOptions: { 38 | project: true, 39 | }, 40 | }, 41 | rules: { 42 | "no-void": "off", 43 | "no-unreachable": "error", 44 | "no-restricted-imports": "off", 45 | "lines-between-class-members": "off", 46 | "no-array-constructor": "off", 47 | "no-console": "error", 48 | "newline-before-return": "error", 49 | "no-unused-vars": "off", 50 | camelcase: [ 51 | "error", 52 | { 53 | ignoreImports: true, 54 | properties: "never", 55 | allow: [], 56 | }, 57 | ], 58 | }, 59 | }, 60 | { 61 | files: ["**/*.{js,ts,tsx}"], 62 | languageOptions: { 63 | ecmaVersion: 2024, 64 | sourceType: "module", 65 | parser: tseslint.parser, 66 | parserOptions: { 67 | project: true, 68 | }, 69 | globals: { 70 | ...globals.es2024, 71 | CloudflareEnv: "readonly", 72 | Queue: "readonly", 73 | ExecutionContext: "readonly", 74 | ExportedHandler: "readonly", 75 | DurableObjectNamespace: "readonly", 76 | WebSocket: "readonly", 77 | DurableObjectStub: "readonly", 78 | DurableObjectTransaction: "readonly", 79 | DurableObjectState: "readonly", 80 | WebSocketPair: "readonly", 81 | RequestInfo: "readonly", 82 | }, 83 | }, 84 | plugins: { 85 | "@typescript-eslint": tseslint.plugin, 86 | "import-x": importPlugin, 87 | "unused-imports": unusedImportsPlugin, 88 | unicorn: unicornPlugin, 89 | }, 90 | rules: { 91 | ...importPlugin.configs.recommended.rules, 92 | "@typescript-eslint/no-unused-vars": [ 93 | "error", 94 | { 95 | argsIgnorePattern: "^_", 96 | varsIgnorePattern: "^_", 97 | caughtErrorsIgnorePattern: "^_", 98 | destructuredArrayIgnorePattern: "^_", 99 | }, 100 | ], 101 | "@typescript-eslint/no-use-before-define": "off", 102 | "@typescript-eslint/interface-name-prefix": "off", 103 | "@typescript-eslint/explicit-module-boundary-types": "off", 104 | "@typescript-eslint/no-explicit-any": "error", 105 | "@typescript-eslint/no-var-requires": "error", 106 | "@typescript-eslint/no-floating-promises": "error", 107 | "@typescript-eslint/no-array-constructor": "error", 108 | "@typescript-eslint/no-unnecessary-condition": "error", 109 | "@typescript-eslint/strict-boolean-expressions": "error", 110 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 111 | "@typescript-eslint/consistent-generic-constructors": [ 112 | "error", 113 | "constructor", 114 | ], 115 | "@typescript-eslint/array-type": ["error", { default: "array" }], 116 | "@typescript-eslint/consistent-type-exports": [ 117 | "error", 118 | { 119 | fixMixedExportsWithInlineTypeSpecifier: false, 120 | }, 121 | ], 122 | "@typescript-eslint/consistent-type-imports": [ 123 | "error", 124 | { 125 | prefer: "type-imports", 126 | }, 127 | ], 128 | "import-x/default": "off", 129 | "import-x/no-named-as-default-member": "off", 130 | "import-x/order": [ 131 | "error", 132 | { 133 | groups: [ 134 | "builtin", 135 | "external", 136 | "internal", 137 | "parent", 138 | "sibling", 139 | "index", 140 | "object", 141 | "type", 142 | ], 143 | "newlines-between": "always", 144 | pathGroupsExcludedImportTypes: ["builtin"], 145 | alphabetize: { order: "asc", caseInsensitive: true }, 146 | pathGroups: [ 147 | { pattern: "./types/**", group: "internal", position: "before" }, 148 | ], 149 | }, 150 | ], 151 | "import-x/no-unresolved": [ 152 | "error", 153 | { 154 | ignore: ["^cloudflare:"], 155 | }, 156 | ], 157 | "unused-imports/no-unused-imports": "warn", 158 | "unicorn/prefer-node-protocol": "error", 159 | "unicorn/filename-case": ["error", { case: "kebabCase" }], 160 | "unicorn/better-regex": "error", 161 | }, 162 | settings: { 163 | "import-x/resolver": { 164 | typescript: true, 165 | node: true, 166 | }, 167 | "import-x/parsers": { 168 | "@typescript-eslint/parser": [".js", ".jsx", ".ts", ".tsx"], 169 | }, 170 | }, 171 | }, 172 | { 173 | files: ["**/*.test.{ts,tsx}"], 174 | plugins: { 175 | vitest: vitestPlugin, 176 | }, 177 | languageOptions: { 178 | globals: { 179 | ...globals.vitest, 180 | }, 181 | }, 182 | rules: { 183 | ...vitestPlugin.configs.recommended.rules, 184 | "@typescript-eslint/no-unused-vars": "off", 185 | "no-console": "off", 186 | "vitest/max-nested-describe": ["error", { max: 3 }], 187 | }, 188 | }, 189 | prettierConfig, 190 | ]; 191 | 192 | export default config; 193 | -------------------------------------------------------------------------------- /src/yjs/storage/storage.test.ts: -------------------------------------------------------------------------------- 1 | import { vi } from "vitest"; 2 | import { Doc, encodeStateAsUpdate } from "yjs"; 3 | 4 | import { storageKey } from "./storage-key"; 5 | 6 | import { YTransactionStorageImpl } from "."; 7 | 8 | import type { TransactionStorage } from "./type"; 9 | import type { Mocked } from "vitest"; 10 | 11 | describe("YTransactionStorageImpl", () => { 12 | let storage: Mocked; 13 | 14 | beforeEach(() => { 15 | storage = { 16 | get: vi.fn(), 17 | list: vi.fn(), 18 | put: vi.fn(), 19 | delete: vi.fn(), 20 | transaction: vi.fn(async (closure) => closure(storage)), 21 | } as Mocked; 22 | }); 23 | 24 | afterEach(() => { 25 | vi.clearAllMocks(); 26 | vi.resetAllMocks(); 27 | }); 28 | 29 | describe("Document Retrieval", () => { 30 | it("returns an empty YDoc when there are no updates", async () => { 31 | storage.get.mockResolvedValueOnce(undefined); 32 | storage.list.mockResolvedValueOnce(new Map()); 33 | 34 | const yStorage = new YTransactionStorageImpl(storage); 35 | const doc = await yStorage.getYDoc(); 36 | expect(encodeStateAsUpdate(doc)).toEqual(encodeStateAsUpdate(new Doc())); 37 | }); 38 | 39 | it("reconstructs the correct YDoc state from updates", async () => { 40 | const stored = new Doc(); 41 | stored.getText("root").insert(0, "Hello World"); 42 | const update = encodeStateAsUpdate(stored); 43 | 44 | storage.get.mockResolvedValueOnce(undefined); 45 | storage.list.mockResolvedValueOnce(new Map([["ydoc:update:1", update]])); 46 | 47 | const yStorage = new YTransactionStorageImpl(storage); 48 | const doc = await yStorage.getYDoc(); 49 | expect(encodeStateAsUpdate(doc)).toEqual(encodeStateAsUpdate(stored)); 50 | }); 51 | }); 52 | 53 | describe("Update Storage", () => { 54 | it("stores updates correctly under unique keys", async () => { 55 | const update = new Uint8Array([1, 2, 3]); 56 | storage.get.mockResolvedValueOnce(0).mockResolvedValueOnce(0); 57 | 58 | const yStorage = new YTransactionStorageImpl(storage); 59 | await yStorage.storeUpdate(update); 60 | expect(storage.put).toHaveBeenCalledWith("ydoc:state:bytes", 3); 61 | expect(storage.put).toHaveBeenCalledWith("ydoc:state:count", 1); 62 | expect(storage.put).toHaveBeenCalledWith("ydoc:update:1", update); 63 | }); 64 | 65 | it("increments the update count and update bytes on subsequent updates", async () => { 66 | storage.get.mockResolvedValueOnce(3).mockResolvedValueOnce(1); 67 | 68 | const yStorage = new YTransactionStorageImpl(storage); 69 | const update = new Uint8Array([4, 5, 6]); 70 | await yStorage.storeUpdate(update); 71 | expect(storage.put).toHaveBeenCalledWith( 72 | "ydoc:state:bytes", 73 | 3 + update.byteLength, 74 | ); 75 | expect(storage.put).toHaveBeenCalledWith("ydoc:state:count", 2); 76 | expect(storage.put).toHaveBeenCalledWith("ydoc:update:2", update); 77 | }); 78 | }); 79 | 80 | describe("Handling Exceeded Limits", () => { 81 | it.each([ 82 | [2048 * 1024 * 2 + 1, 10], // Exceeded maxBytes 83 | [10, 501], // Exceeded maxUpdates 84 | ])( 85 | "resets counts and bytes and stores combined state doc when limits are exceeded (%p bytes, %p updates)", 86 | async (exceededBytes, exceededUpdates) => { 87 | const doc = new Doc(); 88 | doc.getText("root").insert(0, "Hello World"); 89 | const update = encodeStateAsUpdate(doc); 90 | 91 | storage.get.mockImplementation((key) => { 92 | switch (key) { 93 | case storageKey({ type: "state", name: "bytes" }): 94 | return Promise.resolve(exceededBytes); 95 | case storageKey({ type: "state", name: "count" }): 96 | return Promise.resolve(exceededUpdates); 97 | default: 98 | return Promise.resolve(undefined); 99 | } 100 | }); 101 | storage.list.mockResolvedValue( 102 | new Map( 103 | Array(exceededUpdates) 104 | .fill(0) 105 | .map((_, i) => [`ydoc:update:${i + 1}`, update]), 106 | ), 107 | ); 108 | 109 | const yStorage = new YTransactionStorageImpl(storage); 110 | await yStorage.storeUpdate(update); 111 | 112 | const newDoc = await yStorage.getYDoc(); 113 | const expectedState = encodeStateAsUpdate(newDoc); 114 | 115 | expect(storage.delete).toHaveBeenCalledWith( 116 | Array(exceededUpdates) 117 | .fill(0) 118 | .map((_, i) => `ydoc:update:${i + 1}`), 119 | ); 120 | expect(storage.put).toHaveBeenCalledWith("ydoc:state:bytes", 0); 121 | expect(storage.put).toHaveBeenCalledWith("ydoc:state:count", 0); 122 | expect(storage.put).toHaveBeenCalledWith( 123 | "ydoc:state:doc", 124 | expect.any(Uint8Array), 125 | ); 126 | expect(expectedState).toEqual(encodeStateAsUpdate(newDoc)); 127 | }, 128 | ); 129 | }); 130 | 131 | describe("Configuration Options", () => { 132 | it("handles options to modify maxBytes and maxUpdates", () => { 133 | const yStorage = new YTransactionStorageImpl(storage, { 134 | maxBytes: 1024, 135 | maxUpdates: 100, 136 | }); 137 | expect(yStorage).toHaveProperty("MAX_BYTES", 1024); 138 | expect(yStorage).toHaveProperty("MAX_UPDATES", 100); 139 | }); 140 | 141 | it("throws an error when maxBytes exceeds 128KB", () => { 142 | expect(() => { 143 | return new YTransactionStorageImpl(storage, { 144 | maxBytes: 128 * 1024 + 1, 145 | maxUpdates: 100, 146 | }); 147 | }).toThrow("maxBytes must be less than 128KB"); 148 | }); 149 | }); 150 | 151 | describe("commit method", () => { 152 | it("commits all updates and clears all related storage keys", async () => { 153 | const yStorage = new YTransactionStorageImpl(storage); 154 | 155 | const doc = new Doc(); 156 | doc.getText("root").insert(0, "Hello World"); 157 | const update = encodeStateAsUpdate(doc); 158 | storage.get.mockImplementation((key) => { 159 | switch (key) { 160 | case storageKey({ type: "state", name: "bytes" }): 161 | return Promise.resolve(update.byteLength); 162 | case storageKey({ type: "state", name: "count" }): 163 | return Promise.resolve(1); 164 | case storageKey({ type: "state", name: "doc" }): 165 | return Promise.resolve(undefined); 166 | default: { 167 | throw new Error("Unexpected key"); 168 | } 169 | } 170 | }); 171 | storage.list.mockResolvedValue( 172 | new Map( 173 | Array(1) 174 | .fill(0) 175 | .map((_, i) => [`ydoc:update:${i + 1}`, update]), 176 | ), 177 | ); 178 | 179 | await yStorage.commit(); 180 | 181 | expect(storage.delete).toHaveBeenCalledWith(expect.any(Array)); 182 | expect(storage.put).toHaveBeenCalledWith( 183 | storageKey({ type: "state", name: "bytes" }), 184 | 0, 185 | ); 186 | expect(storage.put).toHaveBeenCalledWith( 187 | storageKey({ type: "state", name: "count" }), 188 | 0, 189 | ); 190 | expect(storage.put).toHaveBeenCalledWith( 191 | storageKey({ type: "state", name: "doc" }), 192 | expect.any(Uint8Array), 193 | ); 194 | }); 195 | 196 | it("does not throw errors when there are no updates to commit", async () => { 197 | const yStorage = new YTransactionStorageImpl(storage); 198 | storage.get.mockImplementation((key) => { 199 | switch (key) { 200 | case storageKey({ type: "state", name: "bytes" }): 201 | return Promise.resolve(undefined); 202 | case storageKey({ type: "state", name: "count" }): 203 | return Promise.resolve(undefined); 204 | case storageKey({ type: "state", name: "doc" }): 205 | return Promise.resolve(undefined); 206 | default: { 207 | throw new Error("Unexpected key"); 208 | } 209 | } 210 | }); 211 | 212 | storage.list.mockResolvedValue(new Map()); 213 | 214 | await expect(yStorage.commit()).resolves.not.toThrow(); 215 | }); 216 | }); 217 | }); 218 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | y-durableobjects logo 3 |
4 | 5 | # y-durableobjects 6 | 7 | [![Yjs on Cloudflare Workers with Durable Objects Demo Movie](https://i.gyazo.com/e94637740dbb11fc5107b0cd0850326d.gif)](https://gyazo.com/e94637740dbb11fc5107b0cd0850326d) 8 | 9 | The `y-durableobjects` library is designed to facilitate real-time collaboration in the Cloudflare Workers environment using Yjs and Durable Objects. It provides a straightforward way to integrate Yjs for decentralized, scalable real-time editing features. 10 | 11 | ## Requirements 12 | 13 | - Hono version 4.3 or higher is required. 14 | 15 | ## Installation 16 | 17 | To use `y-durableobjects`, you need to install the package along with `hono`, as it is a peer dependency. 18 | 19 | ```bash 20 | npm install y-durableobjects hono 21 | ``` 22 | 23 | or using yarn: 24 | 25 | ```bash 26 | yarn add y-durableobjects hono 27 | ``` 28 | 29 | or pnpm: 30 | 31 | ```bash 32 | pnpm add y-durableobjects hono 33 | ``` 34 | 35 | ### Configuration for Durable Objects 36 | 37 | To properly utilize Durable Objects, you need to configure bindings in your `wrangler.toml` file. This involves specifying the name of the Durable Object binding and the class name that represents your Durable Object. For detailed instructions on how to set up your `wrangler.toml` for Durable Objects, including setting up environment variables and additional resources, refer to [Cloudflare's Durable Objects documentation](https://developers.cloudflare.com/durable-objects/get-started/#5-configure-durable-object-bindings). 38 | 39 | This configuration ensures that your Cloudflare Worker can correctly instantiate and interact with Durable Objects, allowing `y-durableobjects` to manage real-time collaboration sessions. 40 | 41 | ```toml 42 | name = "your-worker-name" 43 | main = "src/index.ts" 44 | compatibility_date = "2024-04-05" 45 | 46 | account_id = "your-account-id" 47 | workers_dev = true 48 | 49 | # Durable Objects binding 50 | [durable_objects] 51 | bindings = [ 52 | { name = "Y_DURABLE_OBJECTS", class_name = "YDurableObjects" } 53 | ] 54 | 55 | # Durable Objects migrations 56 | [[migrations]] 57 | tag = "v1" 58 | new_classes = ["YDurableObjects"] 59 | ``` 60 | 61 | ## Usage 62 | 63 | ### With Hono shorthand 64 | 65 | ```typescript 66 | import { Hono } from "hono"; 67 | import { YDurableObjects, yRoute } from "y-durableobjects"; 68 | 69 | type Bindings = { 70 | Y_DURABLE_OBJECTS: DurableObjectNamespace>; 71 | }; 72 | 73 | type Env = { 74 | Bindings: Bindings; 75 | }; 76 | 77 | const app = new Hono(); 78 | 79 | const route = app.route( 80 | "/editor", 81 | yRoute((env) => env.Y_DURABLE_OBJECTS), 82 | ); 83 | 84 | export default route; 85 | export type AppType = typeof route; 86 | export { YDurableObjects }; 87 | ``` 88 | 89 | ### Without the shorthand 90 | 91 | The following example demonstrates how to integrate Hono RPC with `y-durableobjects`. Note that WebSocket connections must be handled via fetch due to the current limitations of JS RPC (see [Cloudflare issue](https://github.com/cloudflare/workerd/issues/2319)): 92 | 93 | ```typescript 94 | import { Hono } from "hono"; 95 | import { YDurableObjects, type YDurableObjectsAppType } from "y-durableobjects"; 96 | import { upgrade } from "y-durableobjects/helpers/upgrade"; 97 | 98 | type Bindings = { 99 | Y_DURABLE_OBJECTS: DurableObjectNamespace>; 100 | }; 101 | 102 | type Env = { 103 | Bindings: Bindings; 104 | }; 105 | 106 | const app = new Hono(); 107 | app.get("/editor/:id", upgrade(), async (c) => { 108 | const id = c.env.Y_DURABLE_OBJECTS.idFromName(c.req.param("id")); 109 | const stub = c.env.Y_DURABLE_OBJECTS.get(id); 110 | 111 | const url = new URL("/", c.req.url); 112 | const client = hc(url.toString(), { 113 | fetch: stub.fetch.bind(stub), 114 | }); 115 | 116 | const res = await client.rooms[":id"].$get( 117 | { param: { id: c.req.param("id") } }, 118 | { init: { headers: c.req.raw.headers } }, 119 | ); 120 | 121 | return new Response(null, { 122 | webSocket: res.webSocket, 123 | status: res.status, 124 | statusText: res.statusText, 125 | }); 126 | }); 127 | 128 | export default app; 129 | export type AppType = typeof app; 130 | export { YDurableObjects }; 131 | ``` 132 | 133 | ### JS RPC support 134 | 135 | `y-durableobjects` supports JS RPC for fetching and updating YDocs. Below are examples of how to use the `getYDoc` and `updateYDoc` interfaces. 136 | 137 | #### getYDoc 138 | 139 | This API fetches the state of the YDoc within a Durable Object. 140 | 141 | Example usage in Hono: 142 | 143 | ```typescript 144 | import { Hono } from "hono"; 145 | import { YDurableObjects } from "y-durableobjects"; 146 | import { fromUint8Array } from "js-base64"; 147 | 148 | type Bindings = { 149 | Y_DURABLE_OBJECTS: DurableObjectNamespace>; 150 | }; 151 | 152 | type Env = { 153 | Bindings: Bindings; 154 | }; 155 | 156 | const app = new Hono(); 157 | 158 | app.get("/rooms/:id/state", async (c) => { 159 | const roomId = c.req.param("id"); 160 | const id = c.env.Y_DURABLE_OBJECTS.idFromName(roomId); 161 | const stub = c.env.Y_DURABLE_OBJECTS.get(id); 162 | 163 | const doc = await stub.getYDoc(); 164 | const base64 = fromUint8Array(doc); 165 | 166 | return c.json({ doc: base64 }, 200); 167 | }); 168 | 169 | export default app; 170 | export { YDurableObjects }; 171 | ``` 172 | 173 | #### updateYDoc 174 | 175 | This API updates the state of the YDoc within a Durable Object. 176 | 177 | Example usage in Hono: 178 | 179 | ```typescript 180 | import { Hono } from "hono"; 181 | import { YDurableObjects } from "y-durableobjects"; 182 | 183 | type Bindings = { 184 | Y_DURABLE_OBJECTS: DurableObjectNamespace>; 185 | }; 186 | 187 | type Env = { 188 | Bindings: Bindings; 189 | }; 190 | 191 | const app = new Hono(); 192 | 193 | app.post("/rooms/:id/update", async (c) => { 194 | const roomId = c.req.param("id"); 195 | const id = c.env.Y_DURABLE_OBJECTS.idFromName(roomId); 196 | const stub = c.env.Y_DURABLE_OBJECTS.get(id); 197 | 198 | const buffer = await c.req.arrayBuffer(); 199 | const update = new Uint8Array(buffer); 200 | 201 | await stub.updateYDoc(update); 202 | 203 | return c.json(null, 200); 204 | }); 205 | 206 | export default app; 207 | export { YDurableObjects }; 208 | ``` 209 | 210 | ### Extending with JS RPC 211 | 212 | By supporting JS RPC, `y-durableobjects` allows for advanced operations through extensions. You can manipulate the protected fields for custom functionality: 213 | 214 | Example: 215 | 216 | ```typescript 217 | import { applyUpdate, encodeStateAsUpdate } from "yjs"; 218 | import { YDurableObjects } from "y-durableobjects"; 219 | 220 | export class CustomDurableObject extends YDurableObjects { 221 | async customMethod() { 222 | // Access and manipulate the YDoc state 223 | const update = new Uint8Array([ 224 | /* some update data */ 225 | ]); 226 | this.doc.update(update); 227 | await this.cleanup(); 228 | } 229 | } 230 | ``` 231 | 232 | ### Hono RPC support for ClientSide 233 | 234 | - Utilizes Hono's WebSocket Helper, making the `$ws` method available in `hono/client` for WebSocket communications. 235 | - For more information on server and client setup, see the [Hono WebSocket Helper documentation](https://hono.dev/helpers/websocket#server-and-client). 236 | 237 | #### Server Implementation 238 | 239 | ##### Using shorthand 240 | 241 | ```typescript 242 | import { Hono } from "hono"; 243 | import { YDurableObjects, yRoute } from "y-durableobjects"; 244 | 245 | type Bindings = { 246 | Y_DURABLE_OBJECTS: DurableObjectNamespace>; 247 | }; 248 | 249 | type Env = { 250 | Bindings: Bindings; 251 | }; 252 | 253 | const app = new Hono(); 254 | const route = app.route( 255 | "/editor", 256 | yRoute((env) => env.Y_DURABLE_OBJECTS), 257 | ); 258 | 259 | export default route; 260 | export type AppType = typeof route; 261 | export { YDurableObjects }; 262 | ``` 263 | 264 | ##### Without shorthand 265 | 266 | ```typescript 267 | import { Hono } from "hono"; 268 | import { YDurableObjects, YDurableObjectsAppType } from "y-durableobjects"; 269 | import { upgrade } from "y-durableobjects/helpers/upgrade"; 270 | 271 | type Bindings = { 272 | Y_DURABLE_OBJECTS: DurableObjectNamespace>; 273 | }; 274 | 275 | type Env = { 276 | Bindings: Bindings; 277 | }; 278 | 279 | const app = new Hono(); 280 | app.get("/editor/:id", upgrade(), async (c) => { 281 | const id = c.env.Y_DURABLE_OBJECTS.idFromName(c.req.param("id")); 282 | const stub = c.env.Y_DURABLE_OBJECTS.get(id); 283 | 284 | const url = new URL("/", c.req.url); 285 | const client = hc(url, { 286 | fetch: stub.fetch.bind(stub), 287 | }); 288 | 289 | const res = await client.rooms[":id"].$get( 290 | { param: { id: c.req.param("id") } }, 291 | { init: { headers: c.req.raw.headers } }, 292 | ); 293 | 294 | return new Response(null, { 295 | webSocket: res.webSocket, 296 | status: res.status, 297 | statusText: res.statusText, 298 | }); 299 | }); 300 | 301 | export default app; 302 | export type AppType = typeof app; 303 | export { YDurableObjects }; 304 | ``` 305 | 306 | #### Client Implementation 307 | 308 | To utilize Hono RPC on the client side, you can create a typed client using `hc` from `hono/client`: 309 | 310 | ```typescript 311 | import { hc } from "hono/client"; 312 | import type { AppType } from "./server"; // Adjust the import path as needed 313 | 314 | const API_URL = "http://localhost:8787"; 315 | 316 | export const client = hc(API_URL); 317 | const ws = client.editor[":id"].$ws({ param: { id: "example" } }); 318 | // ^?const ws: WebSocket 319 | ``` 320 | 321 | ## Troubleshooting 322 | 323 | ### Cloudflare Env Type Collision 324 | 325 | If you encounter TypeScript errors related to `Cloudflare.Env` type being empty or undefined, this is likely due to a type collision issue. 326 | 327 | #### Problem 328 | 329 | The issue occurs because `y-durableobjects` requires `@cloudflare/workers-types` as a peer dependency, which can create an empty `Cloudflare.Env` interface in `node_modules`. This empty interface overwrites your project's custom environment types and causes the `worker-configuration.d.ts` file generated from `wrangler types` to be ignored. 330 | 331 | #### Recommended Approach 332 | 333 | Starting with Wrangler v3, the `wrangler types` command is the recommended approach for generating TypeScript types for Cloudflare Workers. This command generates types based on your project's configuration, compatibility date, and bindings, ensuring accurate and up-to-date type definitions. 334 | 335 | To generate types for your project: 336 | 337 | ```bash 338 | wrangler types 339 | ``` 340 | 341 | This will create a `worker-configuration.d.ts` file (or similar) with the proper type definitions for your Cloudflare Workers environment. 342 | 343 | #### Solution 344 | 345 | If `Cloudflare.Env` remains empty even after upgrading to the latest `y-durableobjects` version, follow these steps: 346 | 347 | 1. **Check for remaining dependencies:** 348 | 349 | ```bash 350 | pnpm why @cloudflare/workers-types 351 | ``` 352 | 353 | 2. **Clean install dependencies:** 354 | 355 | ```bash 356 | # Delete lock files and node_modules 357 | rm -rf node_modules pnpm-lock.yaml 358 | # Reinstall dependencies 359 | pnpm install 360 | ``` 361 | 362 | 3. **Verify the fix:** 363 | Run `pnpm why @cloudflare/workers-types` again. If it returns nothing, the issue is resolved. 364 | 365 | This issue was addressed in [PR #62](https://github.com/napolab/y-durableobjects/pull/62), but residual `@cloudflare/workers-types` dependencies from `wrangler` may still remain in your lock file until you perform a clean install. 366 | -------------------------------------------------------------------------------- /example/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | turbo: 12 | specifier: ^1.11.2 13 | version: 1.13.2 14 | 15 | apps/web: 16 | dependencies: 17 | '@acab/reset.css': 18 | specifier: ^0.8.0 19 | version: 0.8.0 20 | '@lexical/code': 21 | specifier: ^0.13.1 22 | version: 0.13.1(lexical@0.13.1) 23 | '@lexical/html': 24 | specifier: ^0.13.1 25 | version: 0.13.1(lexical@0.13.1) 26 | '@lexical/link': 27 | specifier: ^0.13.1 28 | version: 0.13.1(lexical@0.13.1) 29 | '@lexical/list': 30 | specifier: ^0.13.1 31 | version: 0.13.1(lexical@0.13.1) 32 | '@lexical/markdown': 33 | specifier: ^0.13.1 34 | version: 0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(lexical@0.13.1) 35 | '@lexical/react': 36 | specifier: ^0.13.1 37 | version: 0.13.1(lexical@0.13.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(yjs@13.6.14) 38 | '@lexical/rich-text': 39 | specifier: ^0.13.1 40 | version: 0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(@lexical/utils@0.13.1(lexical@0.13.1))(lexical@0.13.1) 41 | '@lexical/table': 42 | specifier: ^0.13.1 43 | version: 0.13.1(lexical@0.13.1) 44 | hono: 45 | specifier: ^4.5.3 46 | version: 4.5.3 47 | lexical: 48 | specifier: ^0.13.1 49 | version: 0.13.1 50 | react: 51 | specifier: ^18.2.0 52 | version: 18.2.0 53 | react-dom: 54 | specifier: ^18.2.0 55 | version: 18.2.0(react@18.2.0) 56 | y-websocket: 57 | specifier: ^1.5.1 58 | version: 1.5.4(yjs@13.6.14) 59 | yjs: 60 | specifier: ^13.6.10 61 | version: 13.6.14 62 | devDependencies: 63 | '@biomejs/biome': 64 | specifier: ^1.5.3 65 | version: 1.6.4 66 | '@lexical/yjs': 67 | specifier: ^0.13.1 68 | version: 0.13.1(lexical@0.13.1)(yjs@13.6.14) 69 | '@types/react': 70 | specifier: ^18.2.43 71 | version: 18.2.75 72 | '@types/react-dom': 73 | specifier: ^18.2.17 74 | version: 18.2.24 75 | '@vitejs/plugin-react-swc': 76 | specifier: ^3.5.0 77 | version: 3.6.0(vite@5.2.8(@types/node@20.12.6)) 78 | typescript: 79 | specifier: ^5.2.2 80 | version: 5.4.4 81 | vite: 82 | specifier: ^5.0.8 83 | version: 5.2.8(@types/node@20.12.6) 84 | workers: 85 | specifier: workspace:* 86 | version: link:../workers 87 | 88 | apps/workers: 89 | dependencies: 90 | hono: 91 | specifier: ^4.5.3 92 | version: 4.5.3 93 | y-durableobjects: 94 | specifier: ^1.0.0 95 | version: 1.0.0(@cloudflare/workers-types@4.20240729.0)(hono@4.5.3) 96 | devDependencies: 97 | '@biomejs/biome': 98 | specifier: ^1.4.1 99 | version: 1.6.4 100 | '@cloudflare/workers-types': 101 | specifier: ^4.20240729.0 102 | version: 4.20240729.0 103 | typescript: 104 | specifier: ^5.5.4 105 | version: 5.5.4 106 | wrangler: 107 | specifier: ^3.68.0 108 | version: 3.68.0(@cloudflare/workers-types@4.20240729.0) 109 | 110 | packages: 111 | 112 | '@acab/reset.css@0.8.0': 113 | resolution: {integrity: sha512-uLTamlIusQU1Y7d4Wc68Zh7XfqYL6xEFHP8AcXh4gTizWeEnM7NEeGBbmTLBc0zzdJSTzjYKnyQhmJ3BLFz6og==} 114 | 115 | '@babel/runtime@7.24.4': 116 | resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==} 117 | engines: {node: '>=6.9.0'} 118 | 119 | '@biomejs/biome@1.6.4': 120 | resolution: {integrity: sha512-3groVd2oWsLC0ZU+XXgHSNbq31lUcOCBkCcA7sAQGBopHcmL+jmmdoWlY3S61zIh+f2mqQTQte1g6PZKb3JJjA==} 121 | engines: {node: '>=14.21.3'} 122 | hasBin: true 123 | 124 | '@biomejs/cli-darwin-arm64@1.6.4': 125 | resolution: {integrity: sha512-2WZef8byI9NRzGajGj5RTrroW9BxtfbP9etigW1QGAtwu/6+cLkdPOWRAs7uFtaxBNiKFYA8j/BxV5zeAo5QOQ==} 126 | engines: {node: '>=14.21.3'} 127 | cpu: [arm64] 128 | os: [darwin] 129 | 130 | '@biomejs/cli-darwin-x64@1.6.4': 131 | resolution: {integrity: sha512-uo1zgM7jvzcoDpF6dbGizejDLCqNpUIRkCj/oEK0PB0NUw8re/cn1EnxuOLZqDpn+8G75COLQTOx8UQIBBN/Kg==} 132 | engines: {node: '>=14.21.3'} 133 | cpu: [x64] 134 | os: [darwin] 135 | 136 | '@biomejs/cli-linux-arm64-musl@1.6.4': 137 | resolution: {integrity: sha512-Hp8Jwt6rjj0wCcYAEN6/cfwrrPLLlGOXZ56Lei4Pt4jy39+UuPeAVFPeclrrCfxyL1wQ2xPrhd/saTHSL6DoJg==} 138 | engines: {node: '>=14.21.3'} 139 | cpu: [arm64] 140 | os: [linux] 141 | 142 | '@biomejs/cli-linux-arm64@1.6.4': 143 | resolution: {integrity: sha512-wAOieaMNIpLrxGc2/xNvM//CIZg7ueWy3V5A4T7gDZ3OL/Go27EKE59a+vMKsBCYmTt7jFl4yHz0TUkUbodA/w==} 144 | engines: {node: '>=14.21.3'} 145 | cpu: [arm64] 146 | os: [linux] 147 | 148 | '@biomejs/cli-linux-x64-musl@1.6.4': 149 | resolution: {integrity: sha512-wqi0hr8KAx5kBO0B+m5u8QqiYFFBJOSJVSuRqTeGWW+GYLVUtXNidykNqf1JsW6jJDpbkSp2xHKE/bTlVaG2Kg==} 150 | engines: {node: '>=14.21.3'} 151 | cpu: [x64] 152 | os: [linux] 153 | 154 | '@biomejs/cli-linux-x64@1.6.4': 155 | resolution: {integrity: sha512-qTWhuIw+/ePvOkjE9Zxf5OqSCYxtAvcTJtVmZT8YQnmY2I62JKNV2m7tf6O5ViKZUOP0mOQ6NgqHKcHH1eT8jw==} 156 | engines: {node: '>=14.21.3'} 157 | cpu: [x64] 158 | os: [linux] 159 | 160 | '@biomejs/cli-win32-arm64@1.6.4': 161 | resolution: {integrity: sha512-Wp3FiEeF6v6C5qMfLkHwf4YsoNHr/n0efvoC8jCKO/kX05OXaVExj+1uVQ1eGT7Pvx0XVm/TLprRO0vq/V6UzA==} 162 | engines: {node: '>=14.21.3'} 163 | cpu: [arm64] 164 | os: [win32] 165 | 166 | '@biomejs/cli-win32-x64@1.6.4': 167 | resolution: {integrity: sha512-mz183Di5hTSGP7KjNWEhivcP1wnHLGmOxEROvoFsIxMYtDhzJDad4k5gI/1JbmA0xe4n52vsgqo09tBhrMT/Zg==} 168 | engines: {node: '>=14.21.3'} 169 | cpu: [x64] 170 | os: [win32] 171 | 172 | '@cloudflare/kv-asset-handler@0.3.4': 173 | resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} 174 | engines: {node: '>=16.13'} 175 | 176 | '@cloudflare/workerd-darwin-64@1.20240725.0': 177 | resolution: {integrity: sha512-KpE7eycdZ9ON+tKBuTyqZh8SdFWHGrh2Ru9LcbpeFwb7O9gDQv9ceSdoV/T598qlT0a0yVKM62R6xa5ec0UOWA==} 178 | engines: {node: '>=16'} 179 | cpu: [x64] 180 | os: [darwin] 181 | 182 | '@cloudflare/workerd-darwin-arm64@1.20240725.0': 183 | resolution: {integrity: sha512-/UQlI04FdXLpPlDzzsWGz8TuKrMZKLowTo+8PkxgEiWIaBhE4DIDM5bwj3jM4Bs8yOLiL2ovQNpld5CnAKqA8g==} 184 | engines: {node: '>=16'} 185 | cpu: [arm64] 186 | os: [darwin] 187 | 188 | '@cloudflare/workerd-linux-64@1.20240725.0': 189 | resolution: {integrity: sha512-Z5t12qYLvHz0b3ZRBBm2HQ93RiHrAnjFfdhtjMcgJypAGkiWpOCEn2xar/WqDhMfqnk0sa8aYiYAbMAlP1WN6w==} 190 | engines: {node: '>=16'} 191 | cpu: [x64] 192 | os: [linux] 193 | 194 | '@cloudflare/workerd-linux-arm64@1.20240725.0': 195 | resolution: {integrity: sha512-j9gYXLOwOyNehLMzP7KxQ+Y6/nxcL9i6LTDJC6RChoaxLRbm0Y/9Otu+hfyzeNeRpt31ip6vqXZ1QQk6ygzI8A==} 196 | engines: {node: '>=16'} 197 | cpu: [arm64] 198 | os: [linux] 199 | 200 | '@cloudflare/workerd-windows-64@1.20240725.0': 201 | resolution: {integrity: sha512-fkrJLWNN6rrPjZ0eKJx328NVMo4BsainKxAfqaPMEd6uRwjOM8uN8V4sSLsXXP8GQMAx6hAG2hU86givS4GItg==} 202 | engines: {node: '>=16'} 203 | cpu: [x64] 204 | os: [win32] 205 | 206 | '@cloudflare/workers-types@4.20240729.0': 207 | resolution: {integrity: sha512-wfe44YQkv5T9aBr/z95P706r2/Ydg32weJYyBOhvge7FqtdY6mM7l39rybNiJrbJoyN16dd0xxyQMf23aJNC6Q==} 208 | 209 | '@cspotcode/source-map-support@0.8.1': 210 | resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 211 | engines: {node: '>=12'} 212 | 213 | '@esbuild-plugins/node-globals-polyfill@0.2.3': 214 | resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} 215 | peerDependencies: 216 | esbuild: '*' 217 | 218 | '@esbuild-plugins/node-modules-polyfill@0.2.2': 219 | resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==} 220 | peerDependencies: 221 | esbuild: '*' 222 | 223 | '@esbuild/aix-ppc64@0.20.2': 224 | resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} 225 | engines: {node: '>=12'} 226 | cpu: [ppc64] 227 | os: [aix] 228 | 229 | '@esbuild/android-arm64@0.17.19': 230 | resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} 231 | engines: {node: '>=12'} 232 | cpu: [arm64] 233 | os: [android] 234 | 235 | '@esbuild/android-arm64@0.20.2': 236 | resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} 237 | engines: {node: '>=12'} 238 | cpu: [arm64] 239 | os: [android] 240 | 241 | '@esbuild/android-arm@0.17.19': 242 | resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} 243 | engines: {node: '>=12'} 244 | cpu: [arm] 245 | os: [android] 246 | 247 | '@esbuild/android-arm@0.20.2': 248 | resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} 249 | engines: {node: '>=12'} 250 | cpu: [arm] 251 | os: [android] 252 | 253 | '@esbuild/android-x64@0.17.19': 254 | resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} 255 | engines: {node: '>=12'} 256 | cpu: [x64] 257 | os: [android] 258 | 259 | '@esbuild/android-x64@0.20.2': 260 | resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} 261 | engines: {node: '>=12'} 262 | cpu: [x64] 263 | os: [android] 264 | 265 | '@esbuild/darwin-arm64@0.17.19': 266 | resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} 267 | engines: {node: '>=12'} 268 | cpu: [arm64] 269 | os: [darwin] 270 | 271 | '@esbuild/darwin-arm64@0.20.2': 272 | resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} 273 | engines: {node: '>=12'} 274 | cpu: [arm64] 275 | os: [darwin] 276 | 277 | '@esbuild/darwin-x64@0.17.19': 278 | resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} 279 | engines: {node: '>=12'} 280 | cpu: [x64] 281 | os: [darwin] 282 | 283 | '@esbuild/darwin-x64@0.20.2': 284 | resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} 285 | engines: {node: '>=12'} 286 | cpu: [x64] 287 | os: [darwin] 288 | 289 | '@esbuild/freebsd-arm64@0.17.19': 290 | resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} 291 | engines: {node: '>=12'} 292 | cpu: [arm64] 293 | os: [freebsd] 294 | 295 | '@esbuild/freebsd-arm64@0.20.2': 296 | resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} 297 | engines: {node: '>=12'} 298 | cpu: [arm64] 299 | os: [freebsd] 300 | 301 | '@esbuild/freebsd-x64@0.17.19': 302 | resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} 303 | engines: {node: '>=12'} 304 | cpu: [x64] 305 | os: [freebsd] 306 | 307 | '@esbuild/freebsd-x64@0.20.2': 308 | resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} 309 | engines: {node: '>=12'} 310 | cpu: [x64] 311 | os: [freebsd] 312 | 313 | '@esbuild/linux-arm64@0.17.19': 314 | resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} 315 | engines: {node: '>=12'} 316 | cpu: [arm64] 317 | os: [linux] 318 | 319 | '@esbuild/linux-arm64@0.20.2': 320 | resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} 321 | engines: {node: '>=12'} 322 | cpu: [arm64] 323 | os: [linux] 324 | 325 | '@esbuild/linux-arm@0.17.19': 326 | resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} 327 | engines: {node: '>=12'} 328 | cpu: [arm] 329 | os: [linux] 330 | 331 | '@esbuild/linux-arm@0.20.2': 332 | resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} 333 | engines: {node: '>=12'} 334 | cpu: [arm] 335 | os: [linux] 336 | 337 | '@esbuild/linux-ia32@0.17.19': 338 | resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} 339 | engines: {node: '>=12'} 340 | cpu: [ia32] 341 | os: [linux] 342 | 343 | '@esbuild/linux-ia32@0.20.2': 344 | resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} 345 | engines: {node: '>=12'} 346 | cpu: [ia32] 347 | os: [linux] 348 | 349 | '@esbuild/linux-loong64@0.17.19': 350 | resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} 351 | engines: {node: '>=12'} 352 | cpu: [loong64] 353 | os: [linux] 354 | 355 | '@esbuild/linux-loong64@0.20.2': 356 | resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} 357 | engines: {node: '>=12'} 358 | cpu: [loong64] 359 | os: [linux] 360 | 361 | '@esbuild/linux-mips64el@0.17.19': 362 | resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} 363 | engines: {node: '>=12'} 364 | cpu: [mips64el] 365 | os: [linux] 366 | 367 | '@esbuild/linux-mips64el@0.20.2': 368 | resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} 369 | engines: {node: '>=12'} 370 | cpu: [mips64el] 371 | os: [linux] 372 | 373 | '@esbuild/linux-ppc64@0.17.19': 374 | resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} 375 | engines: {node: '>=12'} 376 | cpu: [ppc64] 377 | os: [linux] 378 | 379 | '@esbuild/linux-ppc64@0.20.2': 380 | resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} 381 | engines: {node: '>=12'} 382 | cpu: [ppc64] 383 | os: [linux] 384 | 385 | '@esbuild/linux-riscv64@0.17.19': 386 | resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} 387 | engines: {node: '>=12'} 388 | cpu: [riscv64] 389 | os: [linux] 390 | 391 | '@esbuild/linux-riscv64@0.20.2': 392 | resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} 393 | engines: {node: '>=12'} 394 | cpu: [riscv64] 395 | os: [linux] 396 | 397 | '@esbuild/linux-s390x@0.17.19': 398 | resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} 399 | engines: {node: '>=12'} 400 | cpu: [s390x] 401 | os: [linux] 402 | 403 | '@esbuild/linux-s390x@0.20.2': 404 | resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} 405 | engines: {node: '>=12'} 406 | cpu: [s390x] 407 | os: [linux] 408 | 409 | '@esbuild/linux-x64@0.17.19': 410 | resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} 411 | engines: {node: '>=12'} 412 | cpu: [x64] 413 | os: [linux] 414 | 415 | '@esbuild/linux-x64@0.20.2': 416 | resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} 417 | engines: {node: '>=12'} 418 | cpu: [x64] 419 | os: [linux] 420 | 421 | '@esbuild/netbsd-x64@0.17.19': 422 | resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} 423 | engines: {node: '>=12'} 424 | cpu: [x64] 425 | os: [netbsd] 426 | 427 | '@esbuild/netbsd-x64@0.20.2': 428 | resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} 429 | engines: {node: '>=12'} 430 | cpu: [x64] 431 | os: [netbsd] 432 | 433 | '@esbuild/openbsd-x64@0.17.19': 434 | resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} 435 | engines: {node: '>=12'} 436 | cpu: [x64] 437 | os: [openbsd] 438 | 439 | '@esbuild/openbsd-x64@0.20.2': 440 | resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} 441 | engines: {node: '>=12'} 442 | cpu: [x64] 443 | os: [openbsd] 444 | 445 | '@esbuild/sunos-x64@0.17.19': 446 | resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} 447 | engines: {node: '>=12'} 448 | cpu: [x64] 449 | os: [sunos] 450 | 451 | '@esbuild/sunos-x64@0.20.2': 452 | resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} 453 | engines: {node: '>=12'} 454 | cpu: [x64] 455 | os: [sunos] 456 | 457 | '@esbuild/win32-arm64@0.17.19': 458 | resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} 459 | engines: {node: '>=12'} 460 | cpu: [arm64] 461 | os: [win32] 462 | 463 | '@esbuild/win32-arm64@0.20.2': 464 | resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} 465 | engines: {node: '>=12'} 466 | cpu: [arm64] 467 | os: [win32] 468 | 469 | '@esbuild/win32-ia32@0.17.19': 470 | resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} 471 | engines: {node: '>=12'} 472 | cpu: [ia32] 473 | os: [win32] 474 | 475 | '@esbuild/win32-ia32@0.20.2': 476 | resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} 477 | engines: {node: '>=12'} 478 | cpu: [ia32] 479 | os: [win32] 480 | 481 | '@esbuild/win32-x64@0.17.19': 482 | resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} 483 | engines: {node: '>=12'} 484 | cpu: [x64] 485 | os: [win32] 486 | 487 | '@esbuild/win32-x64@0.20.2': 488 | resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} 489 | engines: {node: '>=12'} 490 | cpu: [x64] 491 | os: [win32] 492 | 493 | '@fastify/busboy@2.1.1': 494 | resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} 495 | engines: {node: '>=14'} 496 | 497 | '@jridgewell/resolve-uri@3.1.2': 498 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 499 | engines: {node: '>=6.0.0'} 500 | 501 | '@jridgewell/sourcemap-codec@1.4.15': 502 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 503 | 504 | '@jridgewell/trace-mapping@0.3.9': 505 | resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 506 | 507 | '@lexical/clipboard@0.13.1': 508 | resolution: {integrity: sha512-gMSbVeqb7S+XAi/EMMlwl+FCurLPugN2jAXcp5k5ZaUd7be8B+iupbYdoKkjt4qBhxmvmfe9k46GoC0QOPl/nw==} 509 | peerDependencies: 510 | lexical: 0.13.1 511 | 512 | '@lexical/code@0.13.1': 513 | resolution: {integrity: sha512-QK77r3QgEtJy96ahYXNgpve8EY64BQgBSnPDOuqVrLdl92nPzjqzlsko2OZldlrt7gjXcfl9nqfhZ/CAhStfOg==} 514 | peerDependencies: 515 | lexical: 0.13.1 516 | 517 | '@lexical/dragon@0.13.1': 518 | resolution: {integrity: sha512-aNlqfif4//jW7gOxbBgdrbDovU6m3EwQrUw+Y/vqRkY+sWmloyAUeNwCPH1QP3Q5cvfolzOeN5igfBljsFr+1g==} 519 | peerDependencies: 520 | lexical: 0.13.1 521 | 522 | '@lexical/hashtag@0.13.1': 523 | resolution: {integrity: sha512-Dl0dUG4ZXNjYYuAUR0GMGpLGsA+cps2/ln3xEmy28bZR0sKkjXugsu2QOIxZjYIPBewDrXzPcvK8md45cMYoSg==} 524 | peerDependencies: 525 | lexical: 0.13.1 526 | 527 | '@lexical/history@0.13.1': 528 | resolution: {integrity: sha512-cZXt30MalEEiRaflE9tHeGYnwT1xSDjXLsf9M409DSU9POJyZ1fsULJrG1tWv2uFQOhwal33rve9+MatUlITrg==} 529 | peerDependencies: 530 | lexical: 0.13.1 531 | 532 | '@lexical/html@0.13.1': 533 | resolution: {integrity: sha512-XkZrnCSHIUavtpMol6aG8YsJ5KqC9hMxEhAENf3HTGi3ocysCByyXOyt1EhEYpjJvgDG4wRqt25xGDbLjj1/sA==} 534 | peerDependencies: 535 | lexical: 0.13.1 536 | 537 | '@lexical/link@0.13.1': 538 | resolution: {integrity: sha512-7E3B2juL2UoMj2n+CiyFZ7tlpsdViAoIE7MpegXwfe/VQ66wFwk/VxGTa/69ng2EoF7E0kh+SldvGQDrWAWb1g==} 539 | peerDependencies: 540 | lexical: 0.13.1 541 | 542 | '@lexical/list@0.13.1': 543 | resolution: {integrity: sha512-6U1pmNZcKLuOWiWRML8Raf9zSEuUCMlsOye82niyF6I0rpPgYo5UFghAAbGISDsyqzM1B2L4BgJ6XrCk/dJptg==} 544 | peerDependencies: 545 | lexical: 0.13.1 546 | 547 | '@lexical/mark@0.13.1': 548 | resolution: {integrity: sha512-dW27PW8wWDOKFqXTBUuUfV+umU0KfwvXGkPUAxRJrvwUWk5RKaS48LhgbNlQ5BfT84Q8dSiQzvbaa6T40t9a3A==} 549 | peerDependencies: 550 | lexical: 0.13.1 551 | 552 | '@lexical/markdown@0.13.1': 553 | resolution: {integrity: sha512-6tbdme2h5Zy/M88loVQVH5G0Nt7VMR9UUkyiSaicyBRDOU2OHacaXEp+KSS/XuF+d7TA+v/SzyDq8HS77cO1wA==} 554 | peerDependencies: 555 | lexical: 0.13.1 556 | 557 | '@lexical/offset@0.13.1': 558 | resolution: {integrity: sha512-j/RZcztJ7dyTrfA2+C3yXDzWDXV+XmMpD5BYeQCEApaHvlo20PHt1BISk7RcrnQW8PdzGvpKblRWf//c08LS9w==} 559 | peerDependencies: 560 | lexical: 0.13.1 561 | 562 | '@lexical/overflow@0.13.1': 563 | resolution: {integrity: sha512-Uw34j+qG2UJRCIR+bykfFMduFk7Pc4r/kNt8N1rjxGuGXAsreTVch1iOhu7Ev6tJgkURsduKuaJCAi7iHnKl7g==} 564 | peerDependencies: 565 | lexical: 0.13.1 566 | 567 | '@lexical/plain-text@0.13.1': 568 | resolution: {integrity: sha512-4j5KAsMKUvJ8LhVDSS4zczbYXzdfmgYSAVhmqpSnJtud425Nk0TAfpUBLFoivxZB7KMoT1LGWQZvd47IvJPvtA==} 569 | peerDependencies: 570 | '@lexical/clipboard': 0.13.1 571 | '@lexical/selection': 0.13.1 572 | '@lexical/utils': 0.13.1 573 | lexical: 0.13.1 574 | 575 | '@lexical/react@0.13.1': 576 | resolution: {integrity: sha512-Sy6EL230KAb0RZsZf1dZrRrc3+rvCDQWltcd8C/cqBUYlxsLYCW9s4f3RB2werngD/PtLYbBB48SYXNkIALITA==} 577 | peerDependencies: 578 | lexical: 0.13.1 579 | react: '>=17.x' 580 | react-dom: '>=17.x' 581 | 582 | '@lexical/rich-text@0.13.1': 583 | resolution: {integrity: sha512-HliB9Ync06mv9DBg/5j0lIsTJp+exLHlaLJe+n8Zq1QNTzZzu2LsIT/Crquk50In7K/cjtlaQ/d5RB0LkjMHYg==} 584 | peerDependencies: 585 | '@lexical/clipboard': 0.13.1 586 | '@lexical/selection': 0.13.1 587 | '@lexical/utils': 0.13.1 588 | lexical: 0.13.1 589 | 590 | '@lexical/selection@0.13.1': 591 | resolution: {integrity: sha512-Kt9eSwjxPznj7yzIYipu9yYEgmRJhHiq3DNxHRxInYcZJWWNNHum2xKyxwwcN8QYBBzgfPegfM/geqQEJSV1lQ==} 592 | peerDependencies: 593 | lexical: 0.13.1 594 | 595 | '@lexical/table@0.13.1': 596 | resolution: {integrity: sha512-VQzgkfkEmnvn6C64O/kvl0HI3bFoBh3WA/U67ALw+DS11Mb5CKjbt0Gzm/258/reIxNMpshjjicpWMv9Miwauw==} 597 | peerDependencies: 598 | lexical: 0.13.1 599 | 600 | '@lexical/text@0.13.1': 601 | resolution: {integrity: sha512-NYy3TZKt3qzReDwN2Rr5RxyFlg84JjXP2JQGMrXSSN7wYe73ysQIU6PqdVrz4iZkP+w34F3pl55dJ24ei3An9w==} 602 | peerDependencies: 603 | lexical: 0.13.1 604 | 605 | '@lexical/utils@0.13.1': 606 | resolution: {integrity: sha512-AtQQKzYymkbOaQxaBXjRBS8IPxF9zWQnqwHTUTrJqJ4hX71aIQd/thqZbfQETAFJfC8pNBZw5zpxN6yPHk23dQ==} 607 | peerDependencies: 608 | lexical: 0.13.1 609 | 610 | '@lexical/yjs@0.13.1': 611 | resolution: {integrity: sha512-4GbqQM+PwNTV59AZoNrfTe/0rLjs+cX6Y6yAdZSRPBwr5L3JzYeU1TTcFCVQTtsE7KF8ddVP8sD7w9pi8rOWLA==} 612 | peerDependencies: 613 | lexical: 0.13.1 614 | yjs: '>=13.5.22' 615 | 616 | '@rollup/rollup-android-arm-eabi@4.14.1': 617 | resolution: {integrity: sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==} 618 | cpu: [arm] 619 | os: [android] 620 | 621 | '@rollup/rollup-android-arm64@4.14.1': 622 | resolution: {integrity: sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==} 623 | cpu: [arm64] 624 | os: [android] 625 | 626 | '@rollup/rollup-darwin-arm64@4.14.1': 627 | resolution: {integrity: sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==} 628 | cpu: [arm64] 629 | os: [darwin] 630 | 631 | '@rollup/rollup-darwin-x64@4.14.1': 632 | resolution: {integrity: sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==} 633 | cpu: [x64] 634 | os: [darwin] 635 | 636 | '@rollup/rollup-linux-arm-gnueabihf@4.14.1': 637 | resolution: {integrity: sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==} 638 | cpu: [arm] 639 | os: [linux] 640 | 641 | '@rollup/rollup-linux-arm64-gnu@4.14.1': 642 | resolution: {integrity: sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==} 643 | cpu: [arm64] 644 | os: [linux] 645 | 646 | '@rollup/rollup-linux-arm64-musl@4.14.1': 647 | resolution: {integrity: sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==} 648 | cpu: [arm64] 649 | os: [linux] 650 | 651 | '@rollup/rollup-linux-powerpc64le-gnu@4.14.1': 652 | resolution: {integrity: sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==} 653 | cpu: [ppc64le] 654 | os: [linux] 655 | 656 | '@rollup/rollup-linux-riscv64-gnu@4.14.1': 657 | resolution: {integrity: sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==} 658 | cpu: [riscv64] 659 | os: [linux] 660 | 661 | '@rollup/rollup-linux-s390x-gnu@4.14.1': 662 | resolution: {integrity: sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==} 663 | cpu: [s390x] 664 | os: [linux] 665 | 666 | '@rollup/rollup-linux-x64-gnu@4.14.1': 667 | resolution: {integrity: sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==} 668 | cpu: [x64] 669 | os: [linux] 670 | 671 | '@rollup/rollup-linux-x64-musl@4.14.1': 672 | resolution: {integrity: sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==} 673 | cpu: [x64] 674 | os: [linux] 675 | 676 | '@rollup/rollup-win32-arm64-msvc@4.14.1': 677 | resolution: {integrity: sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==} 678 | cpu: [arm64] 679 | os: [win32] 680 | 681 | '@rollup/rollup-win32-ia32-msvc@4.14.1': 682 | resolution: {integrity: sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==} 683 | cpu: [ia32] 684 | os: [win32] 685 | 686 | '@rollup/rollup-win32-x64-msvc@4.14.1': 687 | resolution: {integrity: sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==} 688 | cpu: [x64] 689 | os: [win32] 690 | 691 | '@swc/core-darwin-arm64@1.4.13': 692 | resolution: {integrity: sha512-36P72FLpm5iq85IvoEjBvi22DiqkkEIanJ1M0E8bkxcFHUbjBrYfPY9T6cpPyK5oQqkaTBvNAc3j1BlVD6IH6w==} 693 | engines: {node: '>=10'} 694 | cpu: [arm64] 695 | os: [darwin] 696 | 697 | '@swc/core-darwin-x64@1.4.13': 698 | resolution: {integrity: sha512-ye7OgKpDdyA8AMIVVdmD1ICDaFXgoEXORnVO8bBHyul0WN71yUBZMX+YxEx2lpWtiftA2vY/1MAuOR80vHkBCw==} 699 | engines: {node: '>=10'} 700 | cpu: [x64] 701 | os: [darwin] 702 | 703 | '@swc/core-linux-arm-gnueabihf@1.4.13': 704 | resolution: {integrity: sha512-+x593Jlmu4c3lJtZUKRejWpV2MAij1Js5nmQLLdjo6ChR2D4B2rzj3iMiKn5gITew7fraF9t3fvXALdWh7HmUg==} 705 | engines: {node: '>=10'} 706 | cpu: [arm] 707 | os: [linux] 708 | 709 | '@swc/core-linux-arm64-gnu@1.4.13': 710 | resolution: {integrity: sha512-0x8OVw4dfyNerrs/9eZX9wNnmvwbwXSMCi+LbE6Xt1pXOIwvoLtFIXcV3NsrlkFboO3sr5UAQIwDxKqbIZA9pQ==} 711 | engines: {node: '>=10'} 712 | cpu: [arm64] 713 | os: [linux] 714 | 715 | '@swc/core-linux-arm64-musl@1.4.13': 716 | resolution: {integrity: sha512-Z9c4JiequtZvngPcxbCuAOkmWBxi2vInZbjjhD5I+Q9oiJdXUz1t2USGwsGPS41Xvk1BOA3ecK2Sn1ilY3titg==} 717 | engines: {node: '>=10'} 718 | cpu: [arm64] 719 | os: [linux] 720 | 721 | '@swc/core-linux-x64-gnu@1.4.13': 722 | resolution: {integrity: sha512-ChatHtk+vX0Ke5QG+jO+rIapw/KwZsi9MedCBHFXHH6iWF4z8d51cJeN68ykcn+vAXzjNeFNdlNy5Vbkd1zAqg==} 723 | engines: {node: '>=10'} 724 | cpu: [x64] 725 | os: [linux] 726 | 727 | '@swc/core-linux-x64-musl@1.4.13': 728 | resolution: {integrity: sha512-0Pz39YR530mXpsztwQkmEKdkkZy4fY4Smdh4pkm6Ly8Nndyo0te/l4bcAGqN24Jp7aVwF/QSy14SAtw4HRjU9g==} 729 | engines: {node: '>=10'} 730 | cpu: [x64] 731 | os: [linux] 732 | 733 | '@swc/core-win32-arm64-msvc@1.4.13': 734 | resolution: {integrity: sha512-LVZfhlD+jHcAbz5NN+gAJ1BEasB0WpcvUzcsJt0nQSRsojgzPzFjJ+fzEBnvT7SMtqKkrnVJ0OmDYeh88bDRpw==} 735 | engines: {node: '>=10'} 736 | cpu: [arm64] 737 | os: [win32] 738 | 739 | '@swc/core-win32-ia32-msvc@1.4.13': 740 | resolution: {integrity: sha512-78hxHWUvUZtWsnhcf8DKwhBcNFJw+j4y4fN2B9ioXmBWX2tIyw+BqUHOrismOtjPihaZmwe/Ok2e4qmkawE2fw==} 741 | engines: {node: '>=10'} 742 | cpu: [ia32] 743 | os: [win32] 744 | 745 | '@swc/core-win32-x64-msvc@1.4.13': 746 | resolution: {integrity: sha512-WSfy1u2Xde6jU7UpHIInCUMW98Zw9iZglddKUAvmr1obkZji5U6EX0Oca3asEJdZPFb+2lMLjt0Mh5a1YisROg==} 747 | engines: {node: '>=10'} 748 | cpu: [x64] 749 | os: [win32] 750 | 751 | '@swc/core@1.4.13': 752 | resolution: {integrity: sha512-rOtusBE+2gaeRkAJn5E4zp5yzZekZOypzSOz5ZG6P1hFbd+Cc26fWEdK6sUSnrkkvTd0Oj33KXLB/4UkbK/UHA==} 753 | engines: {node: '>=10'} 754 | peerDependencies: 755 | '@swc/helpers': ^0.5.0 756 | peerDependenciesMeta: 757 | '@swc/helpers': 758 | optional: true 759 | 760 | '@swc/counter@0.1.3': 761 | resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} 762 | 763 | '@swc/types@0.1.6': 764 | resolution: {integrity: sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==} 765 | 766 | '@types/estree@1.0.5': 767 | resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} 768 | 769 | '@types/node-forge@1.3.11': 770 | resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} 771 | 772 | '@types/node@20.12.6': 773 | resolution: {integrity: sha512-3KurE8taB8GCvZBPngVbp0lk5CKi8M9f9k1rsADh0Evdz5SzJ+Q+Hx9uHoFGsLnLnd1xmkDQr2hVhlA0Mn0lKQ==} 774 | 775 | '@types/prop-types@15.7.12': 776 | resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} 777 | 778 | '@types/react-dom@18.2.24': 779 | resolution: {integrity: sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg==} 780 | 781 | '@types/react@18.2.75': 782 | resolution: {integrity: sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==} 783 | 784 | '@vitejs/plugin-react-swc@3.6.0': 785 | resolution: {integrity: sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g==} 786 | peerDependencies: 787 | vite: ^4 || ^5 788 | 789 | abstract-leveldown@6.2.3: 790 | resolution: {integrity: sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==} 791 | engines: {node: '>=6'} 792 | 793 | abstract-leveldown@6.3.0: 794 | resolution: {integrity: sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==} 795 | engines: {node: '>=6'} 796 | 797 | acorn-walk@8.3.2: 798 | resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} 799 | engines: {node: '>=0.4.0'} 800 | 801 | acorn@8.11.3: 802 | resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} 803 | engines: {node: '>=0.4.0'} 804 | hasBin: true 805 | 806 | anymatch@3.1.3: 807 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 808 | engines: {node: '>= 8'} 809 | 810 | as-table@1.0.55: 811 | resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} 812 | 813 | async-limiter@1.0.1: 814 | resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} 815 | 816 | base64-js@1.5.1: 817 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 818 | 819 | binary-extensions@2.3.0: 820 | resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 821 | engines: {node: '>=8'} 822 | 823 | blake3-wasm@2.1.5: 824 | resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} 825 | 826 | braces@3.0.2: 827 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 828 | engines: {node: '>=8'} 829 | 830 | buffer@5.7.1: 831 | resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} 832 | 833 | capnp-ts@0.7.0: 834 | resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} 835 | 836 | chokidar@3.6.0: 837 | resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 838 | engines: {node: '>= 8.10.0'} 839 | 840 | consola@3.2.3: 841 | resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} 842 | engines: {node: ^14.18.0 || >=16.10.0} 843 | 844 | cookie@0.5.0: 845 | resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} 846 | engines: {node: '>= 0.6'} 847 | 848 | csstype@3.1.3: 849 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 850 | 851 | data-uri-to-buffer@2.0.2: 852 | resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} 853 | 854 | date-fns@3.6.0: 855 | resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} 856 | 857 | debug@4.3.4: 858 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 859 | engines: {node: '>=6.0'} 860 | peerDependencies: 861 | supports-color: '*' 862 | peerDependenciesMeta: 863 | supports-color: 864 | optional: true 865 | 866 | deferred-leveldown@5.3.0: 867 | resolution: {integrity: sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==} 868 | engines: {node: '>=6'} 869 | 870 | defu@6.1.4: 871 | resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 872 | 873 | encoding-down@6.3.0: 874 | resolution: {integrity: sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==} 875 | engines: {node: '>=6'} 876 | 877 | errno@0.1.8: 878 | resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} 879 | hasBin: true 880 | 881 | esbuild@0.17.19: 882 | resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} 883 | engines: {node: '>=12'} 884 | hasBin: true 885 | 886 | esbuild@0.20.2: 887 | resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} 888 | engines: {node: '>=12'} 889 | hasBin: true 890 | 891 | escape-string-regexp@4.0.0: 892 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 893 | engines: {node: '>=10'} 894 | 895 | estree-walker@0.6.1: 896 | resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} 897 | 898 | exit-hook@2.2.1: 899 | resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} 900 | engines: {node: '>=6'} 901 | 902 | fill-range@7.0.1: 903 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 904 | engines: {node: '>=8'} 905 | 906 | fsevents@2.3.3: 907 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 908 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 909 | os: [darwin] 910 | 911 | function-bind@1.1.2: 912 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 913 | 914 | get-source@2.0.12: 915 | resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} 916 | 917 | glob-parent@5.1.2: 918 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 919 | engines: {node: '>= 6'} 920 | 921 | glob-to-regexp@0.4.1: 922 | resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} 923 | 924 | hasown@2.0.2: 925 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 926 | engines: {node: '>= 0.4'} 927 | 928 | hono@4.5.3: 929 | resolution: {integrity: sha512-r26WwwbKD3BAYdfB294knNnegNda7VfV1tVn66D9Kvl9WQTdrR+5eKdoeaQNHQcC3Gr0KBikzAtjd6VsRGVSaw==} 930 | engines: {node: '>=16.0.0'} 931 | 932 | ieee754@1.2.1: 933 | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 934 | 935 | immediate@3.3.0: 936 | resolution: {integrity: sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==} 937 | 938 | inherits@2.0.4: 939 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 940 | 941 | is-binary-path@2.1.0: 942 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 943 | engines: {node: '>=8'} 944 | 945 | is-core-module@2.13.1: 946 | resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} 947 | 948 | is-extglob@2.1.1: 949 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 950 | engines: {node: '>=0.10.0'} 951 | 952 | is-glob@4.0.3: 953 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 954 | engines: {node: '>=0.10.0'} 955 | 956 | is-number@7.0.0: 957 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 958 | engines: {node: '>=0.12.0'} 959 | 960 | isomorphic.js@0.2.5: 961 | resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} 962 | 963 | js-tokens@4.0.0: 964 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 965 | 966 | level-codec@9.0.2: 967 | resolution: {integrity: sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==} 968 | engines: {node: '>=6'} 969 | 970 | level-concat-iterator@2.0.1: 971 | resolution: {integrity: sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==} 972 | engines: {node: '>=6'} 973 | 974 | level-errors@2.0.1: 975 | resolution: {integrity: sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==} 976 | engines: {node: '>=6'} 977 | 978 | level-iterator-stream@4.0.2: 979 | resolution: {integrity: sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==} 980 | engines: {node: '>=6'} 981 | 982 | level-js@5.0.2: 983 | resolution: {integrity: sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==} 984 | 985 | level-packager@5.1.1: 986 | resolution: {integrity: sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==} 987 | engines: {node: '>=6'} 988 | 989 | level-supports@1.0.1: 990 | resolution: {integrity: sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==} 991 | engines: {node: '>=6'} 992 | 993 | level@6.0.1: 994 | resolution: {integrity: sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==} 995 | engines: {node: '>=8.6.0'} 996 | 997 | leveldown@5.6.0: 998 | resolution: {integrity: sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==} 999 | engines: {node: '>=8.6.0'} 1000 | 1001 | levelup@4.4.0: 1002 | resolution: {integrity: sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==} 1003 | engines: {node: '>=6'} 1004 | 1005 | lexical@0.13.1: 1006 | resolution: {integrity: sha512-jaqRYzVEfBKbX4FwYpd/g+MyOjRaraAel0iQsTrwvx3hyN0bswUZuzb6H6nGlFSjcdrc77wKpyKwoWj4aUd+Bw==} 1007 | 1008 | lib0@0.2.93: 1009 | resolution: {integrity: sha512-M5IKsiFJYulS+8Eal8f+zAqf5ckm1vffW0fFDxfgxJ+uiVopvDdd3PxJmz0GsVi3YNO7QCFSq0nAsiDmNhLj9Q==} 1010 | engines: {node: '>=16'} 1011 | hasBin: true 1012 | 1013 | lib0@0.2.96: 1014 | resolution: {integrity: sha512-xeV9M34+D4HD1sd6xAarnWYgU7pKau64bvmPySibX85G+hx/KonzISpO409K6OS9IVLORWfQZkKBRZV5sQegFQ==} 1015 | engines: {node: '>=16'} 1016 | hasBin: true 1017 | 1018 | lodash.debounce@4.0.8: 1019 | resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} 1020 | 1021 | loose-envify@1.4.0: 1022 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 1023 | hasBin: true 1024 | 1025 | ltgt@2.2.1: 1026 | resolution: {integrity: sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==} 1027 | 1028 | magic-string@0.25.9: 1029 | resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} 1030 | 1031 | mime@3.0.0: 1032 | resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} 1033 | engines: {node: '>=10.0.0'} 1034 | hasBin: true 1035 | 1036 | miniflare@3.20240725.0: 1037 | resolution: {integrity: sha512-n9NTLI8J9Xt0Cls6dRpqoIPkVFnxD9gMnU/qDkDX9diKfN16HyxpAdA5mto/hKuRpjW19TxnTMcxBo90vZXemw==} 1038 | engines: {node: '>=16.13'} 1039 | hasBin: true 1040 | 1041 | ms@2.1.2: 1042 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1043 | 1044 | mustache@4.2.0: 1045 | resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} 1046 | hasBin: true 1047 | 1048 | nanoid@3.3.7: 1049 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 1050 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1051 | hasBin: true 1052 | 1053 | napi-macros@2.0.0: 1054 | resolution: {integrity: sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==} 1055 | 1056 | node-fetch-native@1.6.4: 1057 | resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} 1058 | 1059 | node-forge@1.3.1: 1060 | resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} 1061 | engines: {node: '>= 6.13.0'} 1062 | 1063 | node-gyp-build@4.1.1: 1064 | resolution: {integrity: sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==} 1065 | hasBin: true 1066 | 1067 | normalize-path@3.0.0: 1068 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 1069 | engines: {node: '>=0.10.0'} 1070 | 1071 | path-parse@1.0.7: 1072 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1073 | 1074 | path-to-regexp@6.2.2: 1075 | resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} 1076 | 1077 | pathe@1.1.2: 1078 | resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} 1079 | 1080 | picocolors@1.0.0: 1081 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 1082 | 1083 | picomatch@2.3.1: 1084 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1085 | engines: {node: '>=8.6'} 1086 | 1087 | postcss@8.4.38: 1088 | resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} 1089 | engines: {node: ^10 || ^12 || >=14} 1090 | 1091 | printable-characters@1.0.42: 1092 | resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} 1093 | 1094 | prismjs@1.29.0: 1095 | resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} 1096 | engines: {node: '>=6'} 1097 | 1098 | prr@1.0.1: 1099 | resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} 1100 | 1101 | react-dom@18.2.0: 1102 | resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} 1103 | peerDependencies: 1104 | react: ^18.2.0 1105 | 1106 | react-error-boundary@3.1.4: 1107 | resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} 1108 | engines: {node: '>=10', npm: '>=6'} 1109 | peerDependencies: 1110 | react: '>=16.13.1' 1111 | 1112 | react@18.2.0: 1113 | resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} 1114 | engines: {node: '>=0.10.0'} 1115 | 1116 | readable-stream@3.6.2: 1117 | resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 1118 | engines: {node: '>= 6'} 1119 | 1120 | readdirp@3.6.0: 1121 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1122 | engines: {node: '>=8.10.0'} 1123 | 1124 | regenerator-runtime@0.14.1: 1125 | resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} 1126 | 1127 | resolve.exports@2.0.2: 1128 | resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} 1129 | engines: {node: '>=10'} 1130 | 1131 | resolve@1.22.8: 1132 | resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} 1133 | hasBin: true 1134 | 1135 | rollup-plugin-inject@3.0.2: 1136 | resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} 1137 | deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. 1138 | 1139 | rollup-plugin-node-polyfills@0.2.1: 1140 | resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} 1141 | 1142 | rollup-pluginutils@2.8.2: 1143 | resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} 1144 | 1145 | rollup@4.14.1: 1146 | resolution: {integrity: sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==} 1147 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1148 | hasBin: true 1149 | 1150 | safe-buffer@5.2.1: 1151 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1152 | 1153 | scheduler@0.23.0: 1154 | resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} 1155 | 1156 | selfsigned@2.4.1: 1157 | resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} 1158 | engines: {node: '>=10'} 1159 | 1160 | source-map-js@1.2.0: 1161 | resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} 1162 | engines: {node: '>=0.10.0'} 1163 | 1164 | source-map@0.6.1: 1165 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 1166 | engines: {node: '>=0.10.0'} 1167 | 1168 | sourcemap-codec@1.4.8: 1169 | resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} 1170 | deprecated: Please use @jridgewell/sourcemap-codec instead 1171 | 1172 | stacktracey@2.1.8: 1173 | resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} 1174 | 1175 | stoppable@1.1.0: 1176 | resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} 1177 | engines: {node: '>=4', npm: '>=6'} 1178 | 1179 | string_decoder@1.3.0: 1180 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1181 | 1182 | supports-preserve-symlinks-flag@1.0.0: 1183 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1184 | engines: {node: '>= 0.4'} 1185 | 1186 | to-regex-range@5.0.1: 1187 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1188 | engines: {node: '>=8.0'} 1189 | 1190 | tslib@2.6.2: 1191 | resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 1192 | 1193 | turbo-darwin-64@1.13.2: 1194 | resolution: {integrity: sha512-CCSuD8CfmtncpohCuIgq7eAzUas0IwSbHfI8/Q3vKObTdXyN8vAo01gwqXjDGpzG9bTEVedD0GmLbD23dR0MLA==} 1195 | cpu: [x64] 1196 | os: [darwin] 1197 | 1198 | turbo-darwin-arm64@1.13.2: 1199 | resolution: {integrity: sha512-0HySm06/D2N91rJJ89FbiI/AodmY8B3WDSFTVEpu2+8spUw7hOJ8okWOT0e5iGlyayUP9gr31eOeL3VFZkpfCw==} 1200 | cpu: [arm64] 1201 | os: [darwin] 1202 | 1203 | turbo-linux-64@1.13.2: 1204 | resolution: {integrity: sha512-7HnibgbqZrjn4lcfIouzlPu8ZHSBtURG4c7Bedu7WJUDeZo+RE1crlrQm8wuwO54S0siYqUqo7GNHxu4IXbioQ==} 1205 | cpu: [x64] 1206 | os: [linux] 1207 | 1208 | turbo-linux-arm64@1.13.2: 1209 | resolution: {integrity: sha512-sUq4dbpk6SNKg/Hkwn256Vj2AEYSQdG96repio894h5/LEfauIK2QYiC/xxAeW3WBMc6BngmvNyURIg7ltrePg==} 1210 | cpu: [arm64] 1211 | os: [linux] 1212 | 1213 | turbo-windows-64@1.13.2: 1214 | resolution: {integrity: sha512-DqzhcrciWq3dpzllJR2VVIyOhSlXYCo4mNEWl98DJ3FZ08PEzcI3ceudlH6F0t/nIcfSItK1bDP39cs7YoZHEA==} 1215 | cpu: [x64] 1216 | os: [win32] 1217 | 1218 | turbo-windows-arm64@1.13.2: 1219 | resolution: {integrity: sha512-WnPMrwfCXxK69CdDfS1/j2DlzcKxSmycgDAqV0XCYpK/812KB0KlvsVAt5PjEbZGXkY88pCJ1BLZHAjF5FcbqA==} 1220 | cpu: [arm64] 1221 | os: [win32] 1222 | 1223 | turbo@1.13.2: 1224 | resolution: {integrity: sha512-rX/d9f4MgRT3yK6cERPAkfavIxbpBZowDQpgvkYwGMGDQ0Nvw1nc0NVjruE76GrzXQqoxR1UpnmEP54vBARFHQ==} 1225 | hasBin: true 1226 | 1227 | typescript@5.4.4: 1228 | resolution: {integrity: sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==} 1229 | engines: {node: '>=14.17'} 1230 | hasBin: true 1231 | 1232 | typescript@5.5.4: 1233 | resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} 1234 | engines: {node: '>=14.17'} 1235 | hasBin: true 1236 | 1237 | ufo@1.5.4: 1238 | resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 1239 | 1240 | undici-types@5.26.5: 1241 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 1242 | 1243 | undici@5.28.4: 1244 | resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} 1245 | engines: {node: '>=14.0'} 1246 | 1247 | unenv-nightly@1.10.0-1717606461.a117952: 1248 | resolution: {integrity: sha512-u3TfBX02WzbHTpaEfWEKwDijDSFAHcgXkayUZ+MVDrjhLFvgAJzFGTSTmwlEhwWi2exyRQey23ah9wELMM6etg==} 1249 | 1250 | util-deprecate@1.0.2: 1251 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1252 | 1253 | vite@5.2.8: 1254 | resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} 1255 | engines: {node: ^18.0.0 || >=20.0.0} 1256 | hasBin: true 1257 | peerDependencies: 1258 | '@types/node': ^18.0.0 || >=20.0.0 1259 | less: '*' 1260 | lightningcss: ^1.21.0 1261 | sass: '*' 1262 | stylus: '*' 1263 | sugarss: '*' 1264 | terser: ^5.4.0 1265 | peerDependenciesMeta: 1266 | '@types/node': 1267 | optional: true 1268 | less: 1269 | optional: true 1270 | lightningcss: 1271 | optional: true 1272 | sass: 1273 | optional: true 1274 | stylus: 1275 | optional: true 1276 | sugarss: 1277 | optional: true 1278 | terser: 1279 | optional: true 1280 | 1281 | workerd@1.20240725.0: 1282 | resolution: {integrity: sha512-VZwgejRcHsQ9FEPtc7v25ebINLAR+stL3q1hC1xByE+quskdoWpTXHkZwZ3IdSgvm9vPVbCbJw9p5mGnDByW2A==} 1283 | engines: {node: '>=16'} 1284 | hasBin: true 1285 | 1286 | wrangler@3.68.0: 1287 | resolution: {integrity: sha512-gsIeglkh5nOn1mHJs0bf1pOq/DvIt+umjO/5a867IYYXaN4j/ar5cRR1+F5ue3S7uEjYCLIZZjs8ESiPTSEt+Q==} 1288 | engines: {node: '>=16.17.0'} 1289 | hasBin: true 1290 | peerDependencies: 1291 | '@cloudflare/workers-types': ^4.20240725.0 1292 | peerDependenciesMeta: 1293 | '@cloudflare/workers-types': 1294 | optional: true 1295 | 1296 | ws@6.2.2: 1297 | resolution: {integrity: sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==} 1298 | peerDependencies: 1299 | bufferutil: ^4.0.1 1300 | utf-8-validate: ^5.0.2 1301 | peerDependenciesMeta: 1302 | bufferutil: 1303 | optional: true 1304 | utf-8-validate: 1305 | optional: true 1306 | 1307 | ws@8.18.0: 1308 | resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} 1309 | engines: {node: '>=10.0.0'} 1310 | peerDependencies: 1311 | bufferutil: ^4.0.1 1312 | utf-8-validate: '>=5.0.2' 1313 | peerDependenciesMeta: 1314 | bufferutil: 1315 | optional: true 1316 | utf-8-validate: 1317 | optional: true 1318 | 1319 | xtend@4.0.2: 1320 | resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} 1321 | engines: {node: '>=0.4'} 1322 | 1323 | xxhash-wasm@1.0.2: 1324 | resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==} 1325 | 1326 | y-durableobjects@1.0.0: 1327 | resolution: {integrity: sha512-ne4szYOpdQZJRStVFsUeAyJWCXxa3o0bI/6W4745QXKlJTrTyH7k8lewhdJomHjpWD4o+RlW4yl08QJAiZ9u/w==} 1328 | peerDependencies: 1329 | '@cloudflare/workers-types': '>=4.20240405.0' 1330 | hono: '>=4.3' 1331 | 1332 | y-leveldb@0.1.2: 1333 | resolution: {integrity: sha512-6ulEn5AXfXJYi89rXPEg2mMHAyyw8+ZfeMMdOtBbV8FJpQ1NOrcgi6DTAcXof0dap84NjHPT2+9d0rb6cFsjEg==} 1334 | peerDependencies: 1335 | yjs: ^13.0.0 1336 | 1337 | y-protocols@1.0.6: 1338 | resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==} 1339 | engines: {node: '>=16.0.0', npm: '>=8.0.0'} 1340 | peerDependencies: 1341 | yjs: ^13.0.0 1342 | 1343 | y-websocket@1.5.4: 1344 | resolution: {integrity: sha512-Y3021uy0anOIHqAPyAZbNDoR05JuMEGjRNI8c+K9MHzVS8dWoImdJUjccljAznc8H2L7WkIXhRHZ1igWNRSgPw==} 1345 | engines: {node: '>=16.0.0', npm: '>=8.0.0'} 1346 | hasBin: true 1347 | peerDependencies: 1348 | yjs: ^13.5.6 1349 | 1350 | yjs@13.6.14: 1351 | resolution: {integrity: sha512-D+7KcUr0j+vBCUSKXXEWfA+bG4UQBviAwP3gYBhkstkgwy5+8diOPMx0iqLIOxNo/HxaREUimZRxqHGAHCL2BQ==} 1352 | engines: {node: '>=16.0.0', npm: '>=8.0.0'} 1353 | 1354 | yjs@13.6.18: 1355 | resolution: {integrity: sha512-GBTjO4QCmv2HFKFkYIJl7U77hIB1o22vSCSQD1Ge8ZxWbIbn8AltI4gyXbtL+g5/GJep67HCMq3Y5AmNwDSyEg==} 1356 | engines: {node: '>=16.0.0', npm: '>=8.0.0'} 1357 | 1358 | youch@3.3.3: 1359 | resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==} 1360 | 1361 | zod@3.22.4: 1362 | resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} 1363 | 1364 | snapshots: 1365 | 1366 | '@acab/reset.css@0.8.0': {} 1367 | 1368 | '@babel/runtime@7.24.4': 1369 | dependencies: 1370 | regenerator-runtime: 0.14.1 1371 | 1372 | '@biomejs/biome@1.6.4': 1373 | optionalDependencies: 1374 | '@biomejs/cli-darwin-arm64': 1.6.4 1375 | '@biomejs/cli-darwin-x64': 1.6.4 1376 | '@biomejs/cli-linux-arm64': 1.6.4 1377 | '@biomejs/cli-linux-arm64-musl': 1.6.4 1378 | '@biomejs/cli-linux-x64': 1.6.4 1379 | '@biomejs/cli-linux-x64-musl': 1.6.4 1380 | '@biomejs/cli-win32-arm64': 1.6.4 1381 | '@biomejs/cli-win32-x64': 1.6.4 1382 | 1383 | '@biomejs/cli-darwin-arm64@1.6.4': 1384 | optional: true 1385 | 1386 | '@biomejs/cli-darwin-x64@1.6.4': 1387 | optional: true 1388 | 1389 | '@biomejs/cli-linux-arm64-musl@1.6.4': 1390 | optional: true 1391 | 1392 | '@biomejs/cli-linux-arm64@1.6.4': 1393 | optional: true 1394 | 1395 | '@biomejs/cli-linux-x64-musl@1.6.4': 1396 | optional: true 1397 | 1398 | '@biomejs/cli-linux-x64@1.6.4': 1399 | optional: true 1400 | 1401 | '@biomejs/cli-win32-arm64@1.6.4': 1402 | optional: true 1403 | 1404 | '@biomejs/cli-win32-x64@1.6.4': 1405 | optional: true 1406 | 1407 | '@cloudflare/kv-asset-handler@0.3.4': 1408 | dependencies: 1409 | mime: 3.0.0 1410 | 1411 | '@cloudflare/workerd-darwin-64@1.20240725.0': 1412 | optional: true 1413 | 1414 | '@cloudflare/workerd-darwin-arm64@1.20240725.0': 1415 | optional: true 1416 | 1417 | '@cloudflare/workerd-linux-64@1.20240725.0': 1418 | optional: true 1419 | 1420 | '@cloudflare/workerd-linux-arm64@1.20240725.0': 1421 | optional: true 1422 | 1423 | '@cloudflare/workerd-windows-64@1.20240725.0': 1424 | optional: true 1425 | 1426 | '@cloudflare/workers-types@4.20240729.0': {} 1427 | 1428 | '@cspotcode/source-map-support@0.8.1': 1429 | dependencies: 1430 | '@jridgewell/trace-mapping': 0.3.9 1431 | 1432 | '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': 1433 | dependencies: 1434 | esbuild: 0.17.19 1435 | 1436 | '@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19)': 1437 | dependencies: 1438 | esbuild: 0.17.19 1439 | escape-string-regexp: 4.0.0 1440 | rollup-plugin-node-polyfills: 0.2.1 1441 | 1442 | '@esbuild/aix-ppc64@0.20.2': 1443 | optional: true 1444 | 1445 | '@esbuild/android-arm64@0.17.19': 1446 | optional: true 1447 | 1448 | '@esbuild/android-arm64@0.20.2': 1449 | optional: true 1450 | 1451 | '@esbuild/android-arm@0.17.19': 1452 | optional: true 1453 | 1454 | '@esbuild/android-arm@0.20.2': 1455 | optional: true 1456 | 1457 | '@esbuild/android-x64@0.17.19': 1458 | optional: true 1459 | 1460 | '@esbuild/android-x64@0.20.2': 1461 | optional: true 1462 | 1463 | '@esbuild/darwin-arm64@0.17.19': 1464 | optional: true 1465 | 1466 | '@esbuild/darwin-arm64@0.20.2': 1467 | optional: true 1468 | 1469 | '@esbuild/darwin-x64@0.17.19': 1470 | optional: true 1471 | 1472 | '@esbuild/darwin-x64@0.20.2': 1473 | optional: true 1474 | 1475 | '@esbuild/freebsd-arm64@0.17.19': 1476 | optional: true 1477 | 1478 | '@esbuild/freebsd-arm64@0.20.2': 1479 | optional: true 1480 | 1481 | '@esbuild/freebsd-x64@0.17.19': 1482 | optional: true 1483 | 1484 | '@esbuild/freebsd-x64@0.20.2': 1485 | optional: true 1486 | 1487 | '@esbuild/linux-arm64@0.17.19': 1488 | optional: true 1489 | 1490 | '@esbuild/linux-arm64@0.20.2': 1491 | optional: true 1492 | 1493 | '@esbuild/linux-arm@0.17.19': 1494 | optional: true 1495 | 1496 | '@esbuild/linux-arm@0.20.2': 1497 | optional: true 1498 | 1499 | '@esbuild/linux-ia32@0.17.19': 1500 | optional: true 1501 | 1502 | '@esbuild/linux-ia32@0.20.2': 1503 | optional: true 1504 | 1505 | '@esbuild/linux-loong64@0.17.19': 1506 | optional: true 1507 | 1508 | '@esbuild/linux-loong64@0.20.2': 1509 | optional: true 1510 | 1511 | '@esbuild/linux-mips64el@0.17.19': 1512 | optional: true 1513 | 1514 | '@esbuild/linux-mips64el@0.20.2': 1515 | optional: true 1516 | 1517 | '@esbuild/linux-ppc64@0.17.19': 1518 | optional: true 1519 | 1520 | '@esbuild/linux-ppc64@0.20.2': 1521 | optional: true 1522 | 1523 | '@esbuild/linux-riscv64@0.17.19': 1524 | optional: true 1525 | 1526 | '@esbuild/linux-riscv64@0.20.2': 1527 | optional: true 1528 | 1529 | '@esbuild/linux-s390x@0.17.19': 1530 | optional: true 1531 | 1532 | '@esbuild/linux-s390x@0.20.2': 1533 | optional: true 1534 | 1535 | '@esbuild/linux-x64@0.17.19': 1536 | optional: true 1537 | 1538 | '@esbuild/linux-x64@0.20.2': 1539 | optional: true 1540 | 1541 | '@esbuild/netbsd-x64@0.17.19': 1542 | optional: true 1543 | 1544 | '@esbuild/netbsd-x64@0.20.2': 1545 | optional: true 1546 | 1547 | '@esbuild/openbsd-x64@0.17.19': 1548 | optional: true 1549 | 1550 | '@esbuild/openbsd-x64@0.20.2': 1551 | optional: true 1552 | 1553 | '@esbuild/sunos-x64@0.17.19': 1554 | optional: true 1555 | 1556 | '@esbuild/sunos-x64@0.20.2': 1557 | optional: true 1558 | 1559 | '@esbuild/win32-arm64@0.17.19': 1560 | optional: true 1561 | 1562 | '@esbuild/win32-arm64@0.20.2': 1563 | optional: true 1564 | 1565 | '@esbuild/win32-ia32@0.17.19': 1566 | optional: true 1567 | 1568 | '@esbuild/win32-ia32@0.20.2': 1569 | optional: true 1570 | 1571 | '@esbuild/win32-x64@0.17.19': 1572 | optional: true 1573 | 1574 | '@esbuild/win32-x64@0.20.2': 1575 | optional: true 1576 | 1577 | '@fastify/busboy@2.1.1': {} 1578 | 1579 | '@jridgewell/resolve-uri@3.1.2': {} 1580 | 1581 | '@jridgewell/sourcemap-codec@1.4.15': {} 1582 | 1583 | '@jridgewell/trace-mapping@0.3.9': 1584 | dependencies: 1585 | '@jridgewell/resolve-uri': 3.1.2 1586 | '@jridgewell/sourcemap-codec': 1.4.15 1587 | 1588 | '@lexical/clipboard@0.13.1(lexical@0.13.1)': 1589 | dependencies: 1590 | '@lexical/html': 0.13.1(lexical@0.13.1) 1591 | '@lexical/list': 0.13.1(lexical@0.13.1) 1592 | '@lexical/selection': 0.13.1(lexical@0.13.1) 1593 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1594 | lexical: 0.13.1 1595 | 1596 | '@lexical/code@0.13.1(lexical@0.13.1)': 1597 | dependencies: 1598 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1599 | lexical: 0.13.1 1600 | prismjs: 1.29.0 1601 | 1602 | '@lexical/dragon@0.13.1(lexical@0.13.1)': 1603 | dependencies: 1604 | lexical: 0.13.1 1605 | 1606 | '@lexical/hashtag@0.13.1(lexical@0.13.1)': 1607 | dependencies: 1608 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1609 | lexical: 0.13.1 1610 | 1611 | '@lexical/history@0.13.1(lexical@0.13.1)': 1612 | dependencies: 1613 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1614 | lexical: 0.13.1 1615 | 1616 | '@lexical/html@0.13.1(lexical@0.13.1)': 1617 | dependencies: 1618 | '@lexical/selection': 0.13.1(lexical@0.13.1) 1619 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1620 | lexical: 0.13.1 1621 | 1622 | '@lexical/link@0.13.1(lexical@0.13.1)': 1623 | dependencies: 1624 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1625 | lexical: 0.13.1 1626 | 1627 | '@lexical/list@0.13.1(lexical@0.13.1)': 1628 | dependencies: 1629 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1630 | lexical: 0.13.1 1631 | 1632 | '@lexical/mark@0.13.1(lexical@0.13.1)': 1633 | dependencies: 1634 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1635 | lexical: 0.13.1 1636 | 1637 | '@lexical/markdown@0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(lexical@0.13.1)': 1638 | dependencies: 1639 | '@lexical/code': 0.13.1(lexical@0.13.1) 1640 | '@lexical/link': 0.13.1(lexical@0.13.1) 1641 | '@lexical/list': 0.13.1(lexical@0.13.1) 1642 | '@lexical/rich-text': 0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(@lexical/utils@0.13.1(lexical@0.13.1))(lexical@0.13.1) 1643 | '@lexical/text': 0.13.1(lexical@0.13.1) 1644 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1645 | lexical: 0.13.1 1646 | transitivePeerDependencies: 1647 | - '@lexical/clipboard' 1648 | - '@lexical/selection' 1649 | 1650 | '@lexical/offset@0.13.1(lexical@0.13.1)': 1651 | dependencies: 1652 | lexical: 0.13.1 1653 | 1654 | '@lexical/overflow@0.13.1(lexical@0.13.1)': 1655 | dependencies: 1656 | lexical: 0.13.1 1657 | 1658 | '@lexical/plain-text@0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(@lexical/utils@0.13.1(lexical@0.13.1))(lexical@0.13.1)': 1659 | dependencies: 1660 | '@lexical/clipboard': 0.13.1(lexical@0.13.1) 1661 | '@lexical/selection': 0.13.1(lexical@0.13.1) 1662 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1663 | lexical: 0.13.1 1664 | 1665 | '@lexical/react@0.13.1(lexical@0.13.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(yjs@13.6.14)': 1666 | dependencies: 1667 | '@lexical/clipboard': 0.13.1(lexical@0.13.1) 1668 | '@lexical/code': 0.13.1(lexical@0.13.1) 1669 | '@lexical/dragon': 0.13.1(lexical@0.13.1) 1670 | '@lexical/hashtag': 0.13.1(lexical@0.13.1) 1671 | '@lexical/history': 0.13.1(lexical@0.13.1) 1672 | '@lexical/link': 0.13.1(lexical@0.13.1) 1673 | '@lexical/list': 0.13.1(lexical@0.13.1) 1674 | '@lexical/mark': 0.13.1(lexical@0.13.1) 1675 | '@lexical/markdown': 0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(lexical@0.13.1) 1676 | '@lexical/overflow': 0.13.1(lexical@0.13.1) 1677 | '@lexical/plain-text': 0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(@lexical/utils@0.13.1(lexical@0.13.1))(lexical@0.13.1) 1678 | '@lexical/rich-text': 0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(@lexical/utils@0.13.1(lexical@0.13.1))(lexical@0.13.1) 1679 | '@lexical/selection': 0.13.1(lexical@0.13.1) 1680 | '@lexical/table': 0.13.1(lexical@0.13.1) 1681 | '@lexical/text': 0.13.1(lexical@0.13.1) 1682 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1683 | '@lexical/yjs': 0.13.1(lexical@0.13.1)(yjs@13.6.14) 1684 | lexical: 0.13.1 1685 | react: 18.2.0 1686 | react-dom: 18.2.0(react@18.2.0) 1687 | react-error-boundary: 3.1.4(react@18.2.0) 1688 | transitivePeerDependencies: 1689 | - yjs 1690 | 1691 | '@lexical/rich-text@0.13.1(@lexical/clipboard@0.13.1(lexical@0.13.1))(@lexical/selection@0.13.1(lexical@0.13.1))(@lexical/utils@0.13.1(lexical@0.13.1))(lexical@0.13.1)': 1692 | dependencies: 1693 | '@lexical/clipboard': 0.13.1(lexical@0.13.1) 1694 | '@lexical/selection': 0.13.1(lexical@0.13.1) 1695 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1696 | lexical: 0.13.1 1697 | 1698 | '@lexical/selection@0.13.1(lexical@0.13.1)': 1699 | dependencies: 1700 | lexical: 0.13.1 1701 | 1702 | '@lexical/table@0.13.1(lexical@0.13.1)': 1703 | dependencies: 1704 | '@lexical/utils': 0.13.1(lexical@0.13.1) 1705 | lexical: 0.13.1 1706 | 1707 | '@lexical/text@0.13.1(lexical@0.13.1)': 1708 | dependencies: 1709 | lexical: 0.13.1 1710 | 1711 | '@lexical/utils@0.13.1(lexical@0.13.1)': 1712 | dependencies: 1713 | '@lexical/list': 0.13.1(lexical@0.13.1) 1714 | '@lexical/selection': 0.13.1(lexical@0.13.1) 1715 | '@lexical/table': 0.13.1(lexical@0.13.1) 1716 | lexical: 0.13.1 1717 | 1718 | '@lexical/yjs@0.13.1(lexical@0.13.1)(yjs@13.6.14)': 1719 | dependencies: 1720 | '@lexical/offset': 0.13.1(lexical@0.13.1) 1721 | lexical: 0.13.1 1722 | yjs: 13.6.14 1723 | 1724 | '@rollup/rollup-android-arm-eabi@4.14.1': 1725 | optional: true 1726 | 1727 | '@rollup/rollup-android-arm64@4.14.1': 1728 | optional: true 1729 | 1730 | '@rollup/rollup-darwin-arm64@4.14.1': 1731 | optional: true 1732 | 1733 | '@rollup/rollup-darwin-x64@4.14.1': 1734 | optional: true 1735 | 1736 | '@rollup/rollup-linux-arm-gnueabihf@4.14.1': 1737 | optional: true 1738 | 1739 | '@rollup/rollup-linux-arm64-gnu@4.14.1': 1740 | optional: true 1741 | 1742 | '@rollup/rollup-linux-arm64-musl@4.14.1': 1743 | optional: true 1744 | 1745 | '@rollup/rollup-linux-powerpc64le-gnu@4.14.1': 1746 | optional: true 1747 | 1748 | '@rollup/rollup-linux-riscv64-gnu@4.14.1': 1749 | optional: true 1750 | 1751 | '@rollup/rollup-linux-s390x-gnu@4.14.1': 1752 | optional: true 1753 | 1754 | '@rollup/rollup-linux-x64-gnu@4.14.1': 1755 | optional: true 1756 | 1757 | '@rollup/rollup-linux-x64-musl@4.14.1': 1758 | optional: true 1759 | 1760 | '@rollup/rollup-win32-arm64-msvc@4.14.1': 1761 | optional: true 1762 | 1763 | '@rollup/rollup-win32-ia32-msvc@4.14.1': 1764 | optional: true 1765 | 1766 | '@rollup/rollup-win32-x64-msvc@4.14.1': 1767 | optional: true 1768 | 1769 | '@swc/core-darwin-arm64@1.4.13': 1770 | optional: true 1771 | 1772 | '@swc/core-darwin-x64@1.4.13': 1773 | optional: true 1774 | 1775 | '@swc/core-linux-arm-gnueabihf@1.4.13': 1776 | optional: true 1777 | 1778 | '@swc/core-linux-arm64-gnu@1.4.13': 1779 | optional: true 1780 | 1781 | '@swc/core-linux-arm64-musl@1.4.13': 1782 | optional: true 1783 | 1784 | '@swc/core-linux-x64-gnu@1.4.13': 1785 | optional: true 1786 | 1787 | '@swc/core-linux-x64-musl@1.4.13': 1788 | optional: true 1789 | 1790 | '@swc/core-win32-arm64-msvc@1.4.13': 1791 | optional: true 1792 | 1793 | '@swc/core-win32-ia32-msvc@1.4.13': 1794 | optional: true 1795 | 1796 | '@swc/core-win32-x64-msvc@1.4.13': 1797 | optional: true 1798 | 1799 | '@swc/core@1.4.13': 1800 | dependencies: 1801 | '@swc/counter': 0.1.3 1802 | '@swc/types': 0.1.6 1803 | optionalDependencies: 1804 | '@swc/core-darwin-arm64': 1.4.13 1805 | '@swc/core-darwin-x64': 1.4.13 1806 | '@swc/core-linux-arm-gnueabihf': 1.4.13 1807 | '@swc/core-linux-arm64-gnu': 1.4.13 1808 | '@swc/core-linux-arm64-musl': 1.4.13 1809 | '@swc/core-linux-x64-gnu': 1.4.13 1810 | '@swc/core-linux-x64-musl': 1.4.13 1811 | '@swc/core-win32-arm64-msvc': 1.4.13 1812 | '@swc/core-win32-ia32-msvc': 1.4.13 1813 | '@swc/core-win32-x64-msvc': 1.4.13 1814 | 1815 | '@swc/counter@0.1.3': {} 1816 | 1817 | '@swc/types@0.1.6': 1818 | dependencies: 1819 | '@swc/counter': 0.1.3 1820 | 1821 | '@types/estree@1.0.5': {} 1822 | 1823 | '@types/node-forge@1.3.11': 1824 | dependencies: 1825 | '@types/node': 20.12.6 1826 | 1827 | '@types/node@20.12.6': 1828 | dependencies: 1829 | undici-types: 5.26.5 1830 | 1831 | '@types/prop-types@15.7.12': {} 1832 | 1833 | '@types/react-dom@18.2.24': 1834 | dependencies: 1835 | '@types/react': 18.2.75 1836 | 1837 | '@types/react@18.2.75': 1838 | dependencies: 1839 | '@types/prop-types': 15.7.12 1840 | csstype: 3.1.3 1841 | 1842 | '@vitejs/plugin-react-swc@3.6.0(vite@5.2.8(@types/node@20.12.6))': 1843 | dependencies: 1844 | '@swc/core': 1.4.13 1845 | vite: 5.2.8(@types/node@20.12.6) 1846 | transitivePeerDependencies: 1847 | - '@swc/helpers' 1848 | 1849 | abstract-leveldown@6.2.3: 1850 | dependencies: 1851 | buffer: 5.7.1 1852 | immediate: 3.3.0 1853 | level-concat-iterator: 2.0.1 1854 | level-supports: 1.0.1 1855 | xtend: 4.0.2 1856 | optional: true 1857 | 1858 | abstract-leveldown@6.3.0: 1859 | dependencies: 1860 | buffer: 5.7.1 1861 | immediate: 3.3.0 1862 | level-concat-iterator: 2.0.1 1863 | level-supports: 1.0.1 1864 | xtend: 4.0.2 1865 | optional: true 1866 | 1867 | acorn-walk@8.3.2: {} 1868 | 1869 | acorn@8.11.3: {} 1870 | 1871 | anymatch@3.1.3: 1872 | dependencies: 1873 | normalize-path: 3.0.0 1874 | picomatch: 2.3.1 1875 | 1876 | as-table@1.0.55: 1877 | dependencies: 1878 | printable-characters: 1.0.42 1879 | 1880 | async-limiter@1.0.1: 1881 | optional: true 1882 | 1883 | base64-js@1.5.1: 1884 | optional: true 1885 | 1886 | binary-extensions@2.3.0: {} 1887 | 1888 | blake3-wasm@2.1.5: {} 1889 | 1890 | braces@3.0.2: 1891 | dependencies: 1892 | fill-range: 7.0.1 1893 | 1894 | buffer@5.7.1: 1895 | dependencies: 1896 | base64-js: 1.5.1 1897 | ieee754: 1.2.1 1898 | optional: true 1899 | 1900 | capnp-ts@0.7.0: 1901 | dependencies: 1902 | debug: 4.3.4 1903 | tslib: 2.6.2 1904 | transitivePeerDependencies: 1905 | - supports-color 1906 | 1907 | chokidar@3.6.0: 1908 | dependencies: 1909 | anymatch: 3.1.3 1910 | braces: 3.0.2 1911 | glob-parent: 5.1.2 1912 | is-binary-path: 2.1.0 1913 | is-glob: 4.0.3 1914 | normalize-path: 3.0.0 1915 | readdirp: 3.6.0 1916 | optionalDependencies: 1917 | fsevents: 2.3.3 1918 | 1919 | consola@3.2.3: {} 1920 | 1921 | cookie@0.5.0: {} 1922 | 1923 | csstype@3.1.3: {} 1924 | 1925 | data-uri-to-buffer@2.0.2: {} 1926 | 1927 | date-fns@3.6.0: {} 1928 | 1929 | debug@4.3.4: 1930 | dependencies: 1931 | ms: 2.1.2 1932 | 1933 | deferred-leveldown@5.3.0: 1934 | dependencies: 1935 | abstract-leveldown: 6.2.3 1936 | inherits: 2.0.4 1937 | optional: true 1938 | 1939 | defu@6.1.4: {} 1940 | 1941 | encoding-down@6.3.0: 1942 | dependencies: 1943 | abstract-leveldown: 6.3.0 1944 | inherits: 2.0.4 1945 | level-codec: 9.0.2 1946 | level-errors: 2.0.1 1947 | optional: true 1948 | 1949 | errno@0.1.8: 1950 | dependencies: 1951 | prr: 1.0.1 1952 | optional: true 1953 | 1954 | esbuild@0.17.19: 1955 | optionalDependencies: 1956 | '@esbuild/android-arm': 0.17.19 1957 | '@esbuild/android-arm64': 0.17.19 1958 | '@esbuild/android-x64': 0.17.19 1959 | '@esbuild/darwin-arm64': 0.17.19 1960 | '@esbuild/darwin-x64': 0.17.19 1961 | '@esbuild/freebsd-arm64': 0.17.19 1962 | '@esbuild/freebsd-x64': 0.17.19 1963 | '@esbuild/linux-arm': 0.17.19 1964 | '@esbuild/linux-arm64': 0.17.19 1965 | '@esbuild/linux-ia32': 0.17.19 1966 | '@esbuild/linux-loong64': 0.17.19 1967 | '@esbuild/linux-mips64el': 0.17.19 1968 | '@esbuild/linux-ppc64': 0.17.19 1969 | '@esbuild/linux-riscv64': 0.17.19 1970 | '@esbuild/linux-s390x': 0.17.19 1971 | '@esbuild/linux-x64': 0.17.19 1972 | '@esbuild/netbsd-x64': 0.17.19 1973 | '@esbuild/openbsd-x64': 0.17.19 1974 | '@esbuild/sunos-x64': 0.17.19 1975 | '@esbuild/win32-arm64': 0.17.19 1976 | '@esbuild/win32-ia32': 0.17.19 1977 | '@esbuild/win32-x64': 0.17.19 1978 | 1979 | esbuild@0.20.2: 1980 | optionalDependencies: 1981 | '@esbuild/aix-ppc64': 0.20.2 1982 | '@esbuild/android-arm': 0.20.2 1983 | '@esbuild/android-arm64': 0.20.2 1984 | '@esbuild/android-x64': 0.20.2 1985 | '@esbuild/darwin-arm64': 0.20.2 1986 | '@esbuild/darwin-x64': 0.20.2 1987 | '@esbuild/freebsd-arm64': 0.20.2 1988 | '@esbuild/freebsd-x64': 0.20.2 1989 | '@esbuild/linux-arm': 0.20.2 1990 | '@esbuild/linux-arm64': 0.20.2 1991 | '@esbuild/linux-ia32': 0.20.2 1992 | '@esbuild/linux-loong64': 0.20.2 1993 | '@esbuild/linux-mips64el': 0.20.2 1994 | '@esbuild/linux-ppc64': 0.20.2 1995 | '@esbuild/linux-riscv64': 0.20.2 1996 | '@esbuild/linux-s390x': 0.20.2 1997 | '@esbuild/linux-x64': 0.20.2 1998 | '@esbuild/netbsd-x64': 0.20.2 1999 | '@esbuild/openbsd-x64': 0.20.2 2000 | '@esbuild/sunos-x64': 0.20.2 2001 | '@esbuild/win32-arm64': 0.20.2 2002 | '@esbuild/win32-ia32': 0.20.2 2003 | '@esbuild/win32-x64': 0.20.2 2004 | 2005 | escape-string-regexp@4.0.0: {} 2006 | 2007 | estree-walker@0.6.1: {} 2008 | 2009 | exit-hook@2.2.1: {} 2010 | 2011 | fill-range@7.0.1: 2012 | dependencies: 2013 | to-regex-range: 5.0.1 2014 | 2015 | fsevents@2.3.3: 2016 | optional: true 2017 | 2018 | function-bind@1.1.2: {} 2019 | 2020 | get-source@2.0.12: 2021 | dependencies: 2022 | data-uri-to-buffer: 2.0.2 2023 | source-map: 0.6.1 2024 | 2025 | glob-parent@5.1.2: 2026 | dependencies: 2027 | is-glob: 4.0.3 2028 | 2029 | glob-to-regexp@0.4.1: {} 2030 | 2031 | hasown@2.0.2: 2032 | dependencies: 2033 | function-bind: 1.1.2 2034 | 2035 | hono@4.5.3: {} 2036 | 2037 | ieee754@1.2.1: 2038 | optional: true 2039 | 2040 | immediate@3.3.0: 2041 | optional: true 2042 | 2043 | inherits@2.0.4: 2044 | optional: true 2045 | 2046 | is-binary-path@2.1.0: 2047 | dependencies: 2048 | binary-extensions: 2.3.0 2049 | 2050 | is-core-module@2.13.1: 2051 | dependencies: 2052 | hasown: 2.0.2 2053 | 2054 | is-extglob@2.1.1: {} 2055 | 2056 | is-glob@4.0.3: 2057 | dependencies: 2058 | is-extglob: 2.1.1 2059 | 2060 | is-number@7.0.0: {} 2061 | 2062 | isomorphic.js@0.2.5: {} 2063 | 2064 | js-tokens@4.0.0: {} 2065 | 2066 | level-codec@9.0.2: 2067 | dependencies: 2068 | buffer: 5.7.1 2069 | optional: true 2070 | 2071 | level-concat-iterator@2.0.1: 2072 | optional: true 2073 | 2074 | level-errors@2.0.1: 2075 | dependencies: 2076 | errno: 0.1.8 2077 | optional: true 2078 | 2079 | level-iterator-stream@4.0.2: 2080 | dependencies: 2081 | inherits: 2.0.4 2082 | readable-stream: 3.6.2 2083 | xtend: 4.0.2 2084 | optional: true 2085 | 2086 | level-js@5.0.2: 2087 | dependencies: 2088 | abstract-leveldown: 6.2.3 2089 | buffer: 5.7.1 2090 | inherits: 2.0.4 2091 | ltgt: 2.2.1 2092 | optional: true 2093 | 2094 | level-packager@5.1.1: 2095 | dependencies: 2096 | encoding-down: 6.3.0 2097 | levelup: 4.4.0 2098 | optional: true 2099 | 2100 | level-supports@1.0.1: 2101 | dependencies: 2102 | xtend: 4.0.2 2103 | optional: true 2104 | 2105 | level@6.0.1: 2106 | dependencies: 2107 | level-js: 5.0.2 2108 | level-packager: 5.1.1 2109 | leveldown: 5.6.0 2110 | optional: true 2111 | 2112 | leveldown@5.6.0: 2113 | dependencies: 2114 | abstract-leveldown: 6.2.3 2115 | napi-macros: 2.0.0 2116 | node-gyp-build: 4.1.1 2117 | optional: true 2118 | 2119 | levelup@4.4.0: 2120 | dependencies: 2121 | deferred-leveldown: 5.3.0 2122 | level-errors: 2.0.1 2123 | level-iterator-stream: 4.0.2 2124 | level-supports: 1.0.1 2125 | xtend: 4.0.2 2126 | optional: true 2127 | 2128 | lexical@0.13.1: {} 2129 | 2130 | lib0@0.2.93: 2131 | dependencies: 2132 | isomorphic.js: 0.2.5 2133 | 2134 | lib0@0.2.96: 2135 | dependencies: 2136 | isomorphic.js: 0.2.5 2137 | 2138 | lodash.debounce@4.0.8: {} 2139 | 2140 | loose-envify@1.4.0: 2141 | dependencies: 2142 | js-tokens: 4.0.0 2143 | 2144 | ltgt@2.2.1: 2145 | optional: true 2146 | 2147 | magic-string@0.25.9: 2148 | dependencies: 2149 | sourcemap-codec: 1.4.8 2150 | 2151 | mime@3.0.0: {} 2152 | 2153 | miniflare@3.20240725.0: 2154 | dependencies: 2155 | '@cspotcode/source-map-support': 0.8.1 2156 | acorn: 8.11.3 2157 | acorn-walk: 8.3.2 2158 | capnp-ts: 0.7.0 2159 | exit-hook: 2.2.1 2160 | glob-to-regexp: 0.4.1 2161 | stoppable: 1.1.0 2162 | undici: 5.28.4 2163 | workerd: 1.20240725.0 2164 | ws: 8.18.0 2165 | youch: 3.3.3 2166 | zod: 3.22.4 2167 | transitivePeerDependencies: 2168 | - bufferutil 2169 | - supports-color 2170 | - utf-8-validate 2171 | 2172 | ms@2.1.2: {} 2173 | 2174 | mustache@4.2.0: {} 2175 | 2176 | nanoid@3.3.7: {} 2177 | 2178 | napi-macros@2.0.0: 2179 | optional: true 2180 | 2181 | node-fetch-native@1.6.4: {} 2182 | 2183 | node-forge@1.3.1: {} 2184 | 2185 | node-gyp-build@4.1.1: 2186 | optional: true 2187 | 2188 | normalize-path@3.0.0: {} 2189 | 2190 | path-parse@1.0.7: {} 2191 | 2192 | path-to-regexp@6.2.2: {} 2193 | 2194 | pathe@1.1.2: {} 2195 | 2196 | picocolors@1.0.0: {} 2197 | 2198 | picomatch@2.3.1: {} 2199 | 2200 | postcss@8.4.38: 2201 | dependencies: 2202 | nanoid: 3.3.7 2203 | picocolors: 1.0.0 2204 | source-map-js: 1.2.0 2205 | 2206 | printable-characters@1.0.42: {} 2207 | 2208 | prismjs@1.29.0: {} 2209 | 2210 | prr@1.0.1: 2211 | optional: true 2212 | 2213 | react-dom@18.2.0(react@18.2.0): 2214 | dependencies: 2215 | loose-envify: 1.4.0 2216 | react: 18.2.0 2217 | scheduler: 0.23.0 2218 | 2219 | react-error-boundary@3.1.4(react@18.2.0): 2220 | dependencies: 2221 | '@babel/runtime': 7.24.4 2222 | react: 18.2.0 2223 | 2224 | react@18.2.0: 2225 | dependencies: 2226 | loose-envify: 1.4.0 2227 | 2228 | readable-stream@3.6.2: 2229 | dependencies: 2230 | inherits: 2.0.4 2231 | string_decoder: 1.3.0 2232 | util-deprecate: 1.0.2 2233 | optional: true 2234 | 2235 | readdirp@3.6.0: 2236 | dependencies: 2237 | picomatch: 2.3.1 2238 | 2239 | regenerator-runtime@0.14.1: {} 2240 | 2241 | resolve.exports@2.0.2: {} 2242 | 2243 | resolve@1.22.8: 2244 | dependencies: 2245 | is-core-module: 2.13.1 2246 | path-parse: 1.0.7 2247 | supports-preserve-symlinks-flag: 1.0.0 2248 | 2249 | rollup-plugin-inject@3.0.2: 2250 | dependencies: 2251 | estree-walker: 0.6.1 2252 | magic-string: 0.25.9 2253 | rollup-pluginutils: 2.8.2 2254 | 2255 | rollup-plugin-node-polyfills@0.2.1: 2256 | dependencies: 2257 | rollup-plugin-inject: 3.0.2 2258 | 2259 | rollup-pluginutils@2.8.2: 2260 | dependencies: 2261 | estree-walker: 0.6.1 2262 | 2263 | rollup@4.14.1: 2264 | dependencies: 2265 | '@types/estree': 1.0.5 2266 | optionalDependencies: 2267 | '@rollup/rollup-android-arm-eabi': 4.14.1 2268 | '@rollup/rollup-android-arm64': 4.14.1 2269 | '@rollup/rollup-darwin-arm64': 4.14.1 2270 | '@rollup/rollup-darwin-x64': 4.14.1 2271 | '@rollup/rollup-linux-arm-gnueabihf': 4.14.1 2272 | '@rollup/rollup-linux-arm64-gnu': 4.14.1 2273 | '@rollup/rollup-linux-arm64-musl': 4.14.1 2274 | '@rollup/rollup-linux-powerpc64le-gnu': 4.14.1 2275 | '@rollup/rollup-linux-riscv64-gnu': 4.14.1 2276 | '@rollup/rollup-linux-s390x-gnu': 4.14.1 2277 | '@rollup/rollup-linux-x64-gnu': 4.14.1 2278 | '@rollup/rollup-linux-x64-musl': 4.14.1 2279 | '@rollup/rollup-win32-arm64-msvc': 4.14.1 2280 | '@rollup/rollup-win32-ia32-msvc': 4.14.1 2281 | '@rollup/rollup-win32-x64-msvc': 4.14.1 2282 | fsevents: 2.3.3 2283 | 2284 | safe-buffer@5.2.1: 2285 | optional: true 2286 | 2287 | scheduler@0.23.0: 2288 | dependencies: 2289 | loose-envify: 1.4.0 2290 | 2291 | selfsigned@2.4.1: 2292 | dependencies: 2293 | '@types/node-forge': 1.3.11 2294 | node-forge: 1.3.1 2295 | 2296 | source-map-js@1.2.0: {} 2297 | 2298 | source-map@0.6.1: {} 2299 | 2300 | sourcemap-codec@1.4.8: {} 2301 | 2302 | stacktracey@2.1.8: 2303 | dependencies: 2304 | as-table: 1.0.55 2305 | get-source: 2.0.12 2306 | 2307 | stoppable@1.1.0: {} 2308 | 2309 | string_decoder@1.3.0: 2310 | dependencies: 2311 | safe-buffer: 5.2.1 2312 | optional: true 2313 | 2314 | supports-preserve-symlinks-flag@1.0.0: {} 2315 | 2316 | to-regex-range@5.0.1: 2317 | dependencies: 2318 | is-number: 7.0.0 2319 | 2320 | tslib@2.6.2: {} 2321 | 2322 | turbo-darwin-64@1.13.2: 2323 | optional: true 2324 | 2325 | turbo-darwin-arm64@1.13.2: 2326 | optional: true 2327 | 2328 | turbo-linux-64@1.13.2: 2329 | optional: true 2330 | 2331 | turbo-linux-arm64@1.13.2: 2332 | optional: true 2333 | 2334 | turbo-windows-64@1.13.2: 2335 | optional: true 2336 | 2337 | turbo-windows-arm64@1.13.2: 2338 | optional: true 2339 | 2340 | turbo@1.13.2: 2341 | optionalDependencies: 2342 | turbo-darwin-64: 1.13.2 2343 | turbo-darwin-arm64: 1.13.2 2344 | turbo-linux-64: 1.13.2 2345 | turbo-linux-arm64: 1.13.2 2346 | turbo-windows-64: 1.13.2 2347 | turbo-windows-arm64: 1.13.2 2348 | 2349 | typescript@5.4.4: {} 2350 | 2351 | typescript@5.5.4: {} 2352 | 2353 | ufo@1.5.4: {} 2354 | 2355 | undici-types@5.26.5: {} 2356 | 2357 | undici@5.28.4: 2358 | dependencies: 2359 | '@fastify/busboy': 2.1.1 2360 | 2361 | unenv-nightly@1.10.0-1717606461.a117952: 2362 | dependencies: 2363 | consola: 3.2.3 2364 | defu: 6.1.4 2365 | mime: 3.0.0 2366 | node-fetch-native: 1.6.4 2367 | pathe: 1.1.2 2368 | ufo: 1.5.4 2369 | 2370 | util-deprecate@1.0.2: 2371 | optional: true 2372 | 2373 | vite@5.2.8(@types/node@20.12.6): 2374 | dependencies: 2375 | esbuild: 0.20.2 2376 | postcss: 8.4.38 2377 | rollup: 4.14.1 2378 | optionalDependencies: 2379 | '@types/node': 20.12.6 2380 | fsevents: 2.3.3 2381 | 2382 | workerd@1.20240725.0: 2383 | optionalDependencies: 2384 | '@cloudflare/workerd-darwin-64': 1.20240725.0 2385 | '@cloudflare/workerd-darwin-arm64': 1.20240725.0 2386 | '@cloudflare/workerd-linux-64': 1.20240725.0 2387 | '@cloudflare/workerd-linux-arm64': 1.20240725.0 2388 | '@cloudflare/workerd-windows-64': 1.20240725.0 2389 | 2390 | wrangler@3.68.0(@cloudflare/workers-types@4.20240729.0): 2391 | dependencies: 2392 | '@cloudflare/kv-asset-handler': 0.3.4 2393 | '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) 2394 | '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) 2395 | blake3-wasm: 2.1.5 2396 | chokidar: 3.6.0 2397 | date-fns: 3.6.0 2398 | esbuild: 0.17.19 2399 | miniflare: 3.20240725.0 2400 | nanoid: 3.3.7 2401 | path-to-regexp: 6.2.2 2402 | resolve: 1.22.8 2403 | resolve.exports: 2.0.2 2404 | selfsigned: 2.4.1 2405 | source-map: 0.6.1 2406 | unenv: unenv-nightly@1.10.0-1717606461.a117952 2407 | workerd: 1.20240725.0 2408 | xxhash-wasm: 1.0.2 2409 | optionalDependencies: 2410 | '@cloudflare/workers-types': 4.20240729.0 2411 | fsevents: 2.3.3 2412 | transitivePeerDependencies: 2413 | - bufferutil 2414 | - supports-color 2415 | - utf-8-validate 2416 | 2417 | ws@6.2.2: 2418 | dependencies: 2419 | async-limiter: 1.0.1 2420 | optional: true 2421 | 2422 | ws@8.18.0: {} 2423 | 2424 | xtend@4.0.2: 2425 | optional: true 2426 | 2427 | xxhash-wasm@1.0.2: {} 2428 | 2429 | y-durableobjects@1.0.0(@cloudflare/workers-types@4.20240729.0)(hono@4.5.3): 2430 | dependencies: 2431 | '@cloudflare/workers-types': 4.20240729.0 2432 | hono: 4.5.3 2433 | lib0: 0.2.96 2434 | y-protocols: 1.0.6(yjs@13.6.18) 2435 | yjs: 13.6.18 2436 | 2437 | y-leveldb@0.1.2(yjs@13.6.14): 2438 | dependencies: 2439 | level: 6.0.1 2440 | lib0: 0.2.93 2441 | yjs: 13.6.14 2442 | optional: true 2443 | 2444 | y-protocols@1.0.6(yjs@13.6.14): 2445 | dependencies: 2446 | lib0: 0.2.93 2447 | yjs: 13.6.14 2448 | 2449 | y-protocols@1.0.6(yjs@13.6.18): 2450 | dependencies: 2451 | lib0: 0.2.93 2452 | yjs: 13.6.18 2453 | 2454 | y-websocket@1.5.4(yjs@13.6.14): 2455 | dependencies: 2456 | lib0: 0.2.93 2457 | lodash.debounce: 4.0.8 2458 | y-protocols: 1.0.6(yjs@13.6.14) 2459 | yjs: 13.6.14 2460 | optionalDependencies: 2461 | ws: 6.2.2 2462 | y-leveldb: 0.1.2(yjs@13.6.14) 2463 | transitivePeerDependencies: 2464 | - bufferutil 2465 | - utf-8-validate 2466 | 2467 | yjs@13.6.14: 2468 | dependencies: 2469 | lib0: 0.2.93 2470 | 2471 | yjs@13.6.18: 2472 | dependencies: 2473 | lib0: 0.2.96 2474 | 2475 | youch@3.3.3: 2476 | dependencies: 2477 | cookie: 0.5.0 2478 | mustache: 4.2.0 2479 | stacktracey: 2.1.8 2480 | 2481 | zod@3.22.4: {} 2482 | --------------------------------------------------------------------------------