[
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# Dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# Local env files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Testing\ncoverage\n\n# Turbo\n.turbo\n\n# Vercel\n.vercel\n\n# Build Outputs\n.next/\nout/\nbuild\ndist\n\n\n# Debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Misc\n.DS_Store\n*.pem\n"
  },
  {
    "path": ".npmrc",
    "content": ""
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"files.watcherExclude\": {\n    \"**/routeTree.gen.ts\": true\n  },\n  \"search.exclude\": {\n    \"**/routeTree.gen.ts\": true\n  },\n  \"files.readonlyInclude\": {\n    \"**/routeTree.gen.ts\": true\n  }\n}\n"
  },
  {
    "path": "README.md",
    "content": "# Sync Engine Web\n\nA local-first, offline-capable web sync engine implementation with CRDT-based synchronization. The project provides a complete solution for data synchronization between multiple devices while maintaining data consistency and offline capabilities.\n\n## Architecture\n\nThe project is structured as a monorepo with three main components:\n\n1. **Client**\n   - Web-based UI implementation\n   - Local-first storage using IndexedDB ([`dexie`](https://dexie.org/))\n   - Background sync using Web Workers\n   - Client-side schema migrations\n   - Offline-first data management\n\n2. **Server**\n   - REST API for data synchronization\n   - JWT-based authentication and authorization\n   - Byte-level data storage (client-agnostic)\n   - Workspace management and backup\n   - Token-based access control\n\n3. **Shared Packages**\n   - Schema definitions\n   - Type definitions\n   - Shared utilities\n   - Migration utilities\n\n## Key Features\n\n- **Local-First Architecture**: Data is primarily stored and managed locally, with server acting as sync + backup\n- **CRDT-Based Sync**: Uses [`loro-crdt`](https://loro.dev/) for conflict-free data synchronization\n- **Offline Support**: Full offline capability with background sync\n- **Multi-Device Access**: Share workspaces across devices using access tokens\n- **Type-Safe Migrations**: Version-controlled schema migrations\n- **Secure Authentication**: JWT-based auth with master/access token system\n\n## Implementation Details\n\n- Client stores and syncs data using CRDT (Conflict-free Replicated Data Type)\n- Server is client-agnostic, storing only byte-level data\n- Master client controls workspace access through token generation\n- Schema migrations are performed client-side\n- Background syncing handled by Web Workers\n- No server-side querying - all data operations happen client-side\n\n## Authentication Flow\n\n1. Client creates local workspace\n2. Server stores workspace and designates client as \"master\"\n3. Master client receives master token\n4. Master client generates access tokens for other devices\n5. Access tokens can be shared via links\n6. Server handles token verification and authorization\n\n## Data Flow\n\n```\nClient (Local Storage) <-> Web Worker (Background Sync) <-> Server (Byte Storage)\n```\n\n- UI reads directly from local storage\n- Sync operations happen in background\n- Server stores encoded CRDT data as bytes\n- Migrations happen during client initialization\n\n## Technologies\n\n- CRDT: `loro-crdt` for data synchronization\n- Storage: IndexedDB (client), Database (server)\n- Authentication: JWT tokens\n- Background Processing: Web Workers\n\n## Dependencies\n\n### Client\n- **UI & Routing**\n  - `react` - UI framework\n  - `@tanstack/react-router` - Type-safe routing\n- **Storage & Sync**\n  - `loro-crdt` - CRDT implementation\n  - `dexie` - IndexedDB wrapper\n  - `dexie-react-hooks` - React hooks for Dexie\n- **Core**\n  - `effect` - Functional programming toolkit\n  - `@effect/platform-browser` - Browser-specific Effect utilities\n\n### Server\n- **Database**\n  - `drizzle-orm` - TypeScript ORM\n  - `@effect/sql` - SQL integration for Effect\n  - `@effect/sql-pg` - PostgreSQL driver\n  - `pg` - PostgreSQL client\n- **Authentication**\n  - `jsonwebtoken` - JWT implementation\n- **Core**\n  - `effect` - Functional programming toolkit\n  - `@effect/platform-node` - Node.js-specific Effect utilities\n\n### Build Tools\n- `turbo` - Monorepo build system\n- `vite` - Frontend build tool\n- `typescript` - Type system\n\n## Project Status\n\nThis is a functional implementation of a web sync engine, optimized for single-user scenarios (not real-time collaboration). The focus is on providing reliable data synchronization while maintaining offline capabilities.\n"
  },
  {
    "path": "apps/client/.gitignore",
    "content": "# Local\n.DS_Store\n*.local\n*.log*\n\n# Dist\nnode_modules\ndist/\n.vinxi\n.output\n.vercel\n.netlify\n.wrangler\n\n# IDE\n.vscode/*\n!.vscode/extensions.json\n.idea\n"
  },
  {
    "path": "apps/client/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>TanStack Router</title>\n    <script src=\"https://unpkg.com/@tailwindcss/browser@4\"></script>\n    <style type=\"text/tailwindcss\">\n      html {\n        color-scheme: light dark;\n      }\n      * {\n        @apply border-gray-200 dark:border-gray-800;\n      }\n      body {\n        @apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;\n      }\n    </style>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/client/package.json",
    "content": "{\n  \"name\": \"client\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"typecheck\": \"tsc --noEmit\",\n    \"dev\": \"vite --port=3001\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\",\n    \"start\": \"vite\"\n  },\n  \"devDependencies\": {\n    \"@tanstack/router-plugin\": \"^1.105.0\",\n    \"@types/react\": \"^19.0.8\",\n    \"@types/react-dom\": \"^19.0.3\",\n    \"@vitejs/plugin-react\": \"^4.3.2\",\n    \"vite\": \"^6.0.3\",\n    \"vite-plugin-top-level-await\": \"^1.5.0\",\n    \"vite-plugin-wasm\": \"^3.4.1\"\n  },\n  \"dependencies\": {\n    \"@effect/platform\": \"^0.77.2\",\n    \"@effect/platform-browser\": \"^0.56.2\",\n    \"@local/sync\": \"workspace:*\",\n    \"@local/schema\": \"workspace:*\",\n    \"@local/client-lib\": \"workspace:*\",\n    \"@tanstack/react-router\": \"^1.105.0\",\n    \"dexie\": \"^4.0.11\",\n    \"dexie-react-hooks\": \"^1.1.7\",\n    \"effect\": \"^3.13.2\",\n    \"loro-crdt\": \"^1.4.2\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\"\n  }\n}\n"
  },
  {
    "path": "apps/client/src/lib/constants.ts",
    "content": "export const WEBSITE_URL = \"http://localhost:3001\";\n"
  },
  {
    "path": "apps/client/src/lib/hooks/use-food.ts",
    "content": "import { Service, useDexieQuery } from \"@local/client-lib\";\nimport { SnapshotSchema } from \"@local/schema\";\nimport { RuntimeClient } from \"../runtime-client\";\n\nexport const useFood = ({ workspaceId }: { workspaceId: string }) => {\n  return useDexieQuery(() =>\n    RuntimeClient.runPromise(\n      Service.LoroStorage.use(({ query }) =>\n        query((doc) => doc.getList(\"food\"), SnapshotSchema.fields.food.value, {\n          workspaceId,\n        })\n      )\n    )\n  );\n};\n"
  },
  {
    "path": "apps/client/src/lib/hooks/use-meal.ts",
    "content": "import { Service, useDexieQuery } from \"@local/client-lib\";\nimport { SnapshotSchema } from \"@local/schema\";\nimport { Effect } from \"effect\";\nimport { RuntimeClient } from \"../runtime-client\";\n\nexport const useMeal = ({ workspaceId }: { workspaceId: string }) => {\n  return useDexieQuery(() =>\n    RuntimeClient.runPromise(\n      Effect.gen(function* () {\n        const { query } = yield* Service.LoroStorage;\n\n        const meals = yield* query(\n          (doc) => doc.getList(\"meal\"),\n          SnapshotSchema.fields.meal.value,\n          { workspaceId }\n        );\n\n        const foods = yield* query(\n          (doc) => doc.getList(\"food\"),\n          SnapshotSchema.fields.food.value,\n          { workspaceId }\n        );\n\n        return meals.map(({ foodId, id, quantity }) => {\n          const food = foods.find((food) => food.id === foodId);\n          return { id, quantity, food };\n        });\n      })\n    )\n  );\n};\n"
  },
  {
    "path": "apps/client/src/lib/runtime-client.ts",
    "content": "import { RuntimeLayer } from \"@local/client-lib\";\nimport { Layer, ManagedRuntime } from \"effect\";\nimport { Storage } from \"./services/storage\";\n\nconst MainLayer = Layer.mergeAll(RuntimeLayer, Storage.Default);\n\nexport const RuntimeClient = ManagedRuntime.make(MainLayer);\n"
  },
  {
    "path": "apps/client/src/lib/services/storage.ts",
    "content": "import { Service } from \"@local/client-lib\";\nimport { CurrentSchema, SnapshotSchema } from \"@local/schema\";\nimport { Effect, Schema } from \"effect\";\nimport { LoroMap, VersionVector } from \"loro-crdt\";\n\nexport class Storage extends Effect.Service<Storage>()(\"Storage\", {\n  dependencies: [Service.TempWorkspace.Default, Service.LoroStorage.Default],\n  effect: Effect.gen(function* () {\n    const temp = yield* Service.TempWorkspace;\n    const { load } = yield* Service.LoroStorage;\n\n    const insert =\n      <T extends typeof SnapshotSchema.Table.Type>(table: T) =>\n      ({\n        workspaceId,\n        value,\n      }: {\n        workspaceId: string;\n        value: Schema.Schema.Encoded<(typeof CurrentSchema.fields)[T]>[number];\n      }) =>\n        Effect.gen(function* () {\n          const { doc, workspace } = yield* load({ workspaceId });\n\n          const list = doc.getList(table);\n\n          const container = list.insertContainer(list.length, new LoroMap());\n\n          const data = yield* Schema.encode(\n            CurrentSchema.fields[table].value as Schema.Schema<typeof value>\n          )(value);\n\n          Object.entries(data).forEach(([key, val]) => {\n            container.set(key, val);\n          });\n\n          const snapshotExport =\n            workspace === undefined\n              ? doc.export({ mode: \"snapshot\" })\n              : doc.export({\n                  mode: \"update\",\n                  from: new VersionVector(workspace.version),\n                });\n\n          return yield* temp.put({\n            workspaceId,\n            snapshot: snapshotExport,\n            snapshotId: crypto.randomUUID(),\n          });\n        });\n\n    return {\n      insertFood: insert(\"food\"),\n      insertMeal: insert(\"meal\"),\n    } as const;\n  }),\n}) {}\n"
  },
  {
    "path": "apps/client/src/main.tsx",
    "content": "import { RouterProvider, createRouter } from \"@tanstack/react-router\";\nimport ReactDOM from \"react-dom/client\";\nimport { routeTree } from \"./routeTree.gen\";\n\n// Set up a Router instance\nconst router = createRouter({\n  routeTree,\n  defaultPreload: \"intent\",\n});\n\n// Register things for typesafety\ndeclare module \"@tanstack/react-router\" {\n  interface Register {\n    router: typeof router;\n  }\n}\n\nconst rootElement = document.getElementById(\"app\")!;\n\nif (!rootElement.innerHTML) {\n  const root = ReactDOM.createRoot(rootElement);\n  root.render(<RouterProvider router={router} />);\n}\n"
  },
  {
    "path": "apps/client/src/routeTree.gen.ts",
    "content": "/* eslint-disable */\n\n// @ts-nocheck\n\n// noinspection JSUnusedGlobalSymbols\n\n// This file was automatically generated by TanStack Router.\n// You should NOT make any changes in this file as it will be overwritten.\n// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.\n\n// Import Routes\n\nimport { Route as rootRoute } from './routes/__root'\nimport { Route as IndexImport } from './routes/index'\nimport { Route as WorkspaceIdIndexImport } from './routes/$workspaceId/index'\nimport { Route as WorkspaceIdTokenImport } from './routes/$workspaceId/token'\nimport { Route as WorkspaceIdJoinImport } from './routes/$workspaceId/join'\n\n// Create/Update Routes\n\nconst IndexRoute = IndexImport.update({\n  id: '/',\n  path: '/',\n  getParentRoute: () => rootRoute,\n} as any)\n\nconst WorkspaceIdIndexRoute = WorkspaceIdIndexImport.update({\n  id: '/$workspaceId/',\n  path: '/$workspaceId/',\n  getParentRoute: () => rootRoute,\n} as any)\n\nconst WorkspaceIdTokenRoute = WorkspaceIdTokenImport.update({\n  id: '/$workspaceId/token',\n  path: '/$workspaceId/token',\n  getParentRoute: () => rootRoute,\n} as any)\n\nconst WorkspaceIdJoinRoute = WorkspaceIdJoinImport.update({\n  id: '/$workspaceId/join',\n  path: '/$workspaceId/join',\n  getParentRoute: () => rootRoute,\n} as any)\n\n// Populate the FileRoutesByPath interface\n\ndeclare module '@tanstack/react-router' {\n  interface FileRoutesByPath {\n    '/': {\n      id: '/'\n      path: '/'\n      fullPath: '/'\n      preLoaderRoute: typeof IndexImport\n      parentRoute: typeof rootRoute\n    }\n    '/$workspaceId/join': {\n      id: '/$workspaceId/join'\n      path: '/$workspaceId/join'\n      fullPath: '/$workspaceId/join'\n      preLoaderRoute: typeof WorkspaceIdJoinImport\n      parentRoute: typeof rootRoute\n    }\n    '/$workspaceId/token': {\n      id: '/$workspaceId/token'\n      path: '/$workspaceId/token'\n      fullPath: '/$workspaceId/token'\n      preLoaderRoute: typeof WorkspaceIdTokenImport\n      parentRoute: typeof rootRoute\n    }\n    '/$workspaceId/': {\n      id: '/$workspaceId/'\n      path: '/$workspaceId'\n      fullPath: '/$workspaceId'\n      preLoaderRoute: typeof WorkspaceIdIndexImport\n      parentRoute: typeof rootRoute\n    }\n  }\n}\n\n// Create and export the route tree\n\nexport interface FileRoutesByFullPath {\n  '/': typeof IndexRoute\n  '/$workspaceId/join': typeof WorkspaceIdJoinRoute\n  '/$workspaceId/token': typeof WorkspaceIdTokenRoute\n  '/$workspaceId': typeof WorkspaceIdIndexRoute\n}\n\nexport interface FileRoutesByTo {\n  '/': typeof IndexRoute\n  '/$workspaceId/join': typeof WorkspaceIdJoinRoute\n  '/$workspaceId/token': typeof WorkspaceIdTokenRoute\n  '/$workspaceId': typeof WorkspaceIdIndexRoute\n}\n\nexport interface FileRoutesById {\n  __root__: typeof rootRoute\n  '/': typeof IndexRoute\n  '/$workspaceId/join': typeof WorkspaceIdJoinRoute\n  '/$workspaceId/token': typeof WorkspaceIdTokenRoute\n  '/$workspaceId/': typeof WorkspaceIdIndexRoute\n}\n\nexport interface FileRouteTypes {\n  fileRoutesByFullPath: FileRoutesByFullPath\n  fullPaths:\n    | '/'\n    | '/$workspaceId/join'\n    | '/$workspaceId/token'\n    | '/$workspaceId'\n  fileRoutesByTo: FileRoutesByTo\n  to: '/' | '/$workspaceId/join' | '/$workspaceId/token' | '/$workspaceId'\n  id:\n    | '__root__'\n    | '/'\n    | '/$workspaceId/join'\n    | '/$workspaceId/token'\n    | '/$workspaceId/'\n  fileRoutesById: FileRoutesById\n}\n\nexport interface RootRouteChildren {\n  IndexRoute: typeof IndexRoute\n  WorkspaceIdJoinRoute: typeof WorkspaceIdJoinRoute\n  WorkspaceIdTokenRoute: typeof WorkspaceIdTokenRoute\n  WorkspaceIdIndexRoute: typeof WorkspaceIdIndexRoute\n}\n\nconst rootRouteChildren: RootRouteChildren = {\n  IndexRoute: IndexRoute,\n  WorkspaceIdJoinRoute: WorkspaceIdJoinRoute,\n  WorkspaceIdTokenRoute: WorkspaceIdTokenRoute,\n  WorkspaceIdIndexRoute: WorkspaceIdIndexRoute,\n}\n\nexport const routeTree = rootRoute\n  ._addFileChildren(rootRouteChildren)\n  ._addFileTypes<FileRouteTypes>()\n\n/* ROUTE_MANIFEST_START\n{\n  \"routes\": {\n    \"__root__\": {\n      \"filePath\": \"__root.tsx\",\n      \"children\": [\n        \"/\",\n        \"/$workspaceId/join\",\n        \"/$workspaceId/token\",\n        \"/$workspaceId/\"\n      ]\n    },\n    \"/\": {\n      \"filePath\": \"index.tsx\"\n    },\n    \"/$workspaceId/join\": {\n      \"filePath\": \"$workspaceId/join.tsx\"\n    },\n    \"/$workspaceId/token\": {\n      \"filePath\": \"$workspaceId/token.tsx\"\n    },\n    \"/$workspaceId/\": {\n      \"filePath\": \"$workspaceId/index.tsx\"\n    }\n  }\n}\nROUTE_MANIFEST_END */\n"
  },
  {
    "path": "apps/client/src/routes/$workspaceId/index.tsx",
    "content": "import { Worker } from \"@effect/platform\";\nimport { BrowserWorker } from \"@effect/platform-browser\";\nimport { Service, SyncWorker, useActionEffect } from \"@local/client-lib\";\nimport { createFileRoute, Link } from \"@tanstack/react-router\";\nimport { Effect } from \"effect\";\nimport { startTransition, useEffect } from \"react\";\nimport { useFood } from \"../../lib/hooks/use-food\";\nimport { useMeal } from \"../../lib/hooks/use-meal\";\nimport { RuntimeClient } from \"../../lib/runtime-client\";\nimport { Storage } from \"../../lib/services/storage\";\n\nconst bootstrap = ({ workspaceId }: { workspaceId: string }) =>\n  Effect.gen(function* () {\n    const pool = yield* Worker.makePoolSerialized({ size: 1 });\n    return yield* pool.broadcast(new SyncWorker.Bootstrap({ workspaceId }));\n  }).pipe(\n    Effect.scoped,\n    Effect.provide(\n      BrowserWorker.layer(\n        () =>\n          new globalThis.Worker(\n            new URL(\"./src/workers/bootstrap.ts\", globalThis.origin),\n            { type: \"module\" }\n          )\n      )\n    ),\n    Effect.catchAll((error) => Effect.logError(\"Bootstrap error\", error))\n  );\n\nexport const Route = createFileRoute(\"/$workspaceId/\")({\n  component: RouteComponent,\n  loader: ({ params: { workspaceId } }) =>\n    RuntimeClient.runPromise(\n      Service.WorkspaceManager.getById({ workspaceId }).pipe(\n        Effect.flatMap(Effect.fromNullable),\n        Effect.tap(({ workspaceId }) => bootstrap({ workspaceId }))\n      )\n    ),\n});\n\nfunction RouteComponent() {\n  const workspace = Route.useLoaderData();\n\n  const { data, error, loading } = useFood({\n    workspaceId: workspace.workspaceId,\n  });\n\n  const { data: meals } = useMeal({\n    workspaceId: workspace.workspaceId,\n  });\n\n  const [, onBootstrap, bootstrapping] = useActionEffect(\n    RuntimeClient,\n    bootstrap\n  );\n\n  const [, onAddFood] = useActionEffect(RuntimeClient, (formData: FormData) =>\n    Effect.gen(function* () {\n      const loroStorage = yield* Storage;\n\n      const name = formData.get(\"name\") as string;\n      const calories = formData.get(\"calories\") as string;\n\n      yield* loroStorage.insertFood({\n        workspaceId: workspace.workspaceId,\n        value: {\n          id: crypto.randomUUID(),\n          name,\n          calories: parseInt(calories, 10),\n        },\n      });\n    })\n  );\n\n  const [, onAddMeal] = useActionEffect(RuntimeClient, (formData: FormData) =>\n    Effect.gen(function* () {\n      const loroStorage = yield* Storage;\n\n      const foodId = formData.get(\"foodId\") as string;\n      const quantity = formData.get(\"quantity\") as string;\n\n      yield* loroStorage.insertMeal({\n        workspaceId: workspace.workspaceId,\n        value: {\n          id: crypto.randomUUID(),\n          foodId,\n          quantity: parseInt(quantity, 10),\n        },\n      });\n    })\n  );\n\n  useEffect(() => {\n    const url = new URL(\"./src/workers/live.ts\", globalThis.origin);\n    const newWorker = new globalThis.Worker(url, { type: \"module\" });\n\n    void RuntimeClient.runPromise(\n      Effect.gen(function* () {\n        const pool = yield* Worker.makePoolSerialized({ size: 1 });\n        return yield* pool.broadcast(\n          new SyncWorker.LiveQuery({ workspaceId: workspace.workspaceId })\n        );\n      }).pipe(\n        Effect.scoped,\n        Effect.provide(BrowserWorker.layer(() => newWorker))\n      )\n    );\n\n    newWorker.onerror = (error) => {\n      console.error(\"Live query worker error\", error);\n    };\n\n    return () => {\n      newWorker.terminate();\n    };\n  }, []);\n\n  return (\n    <div>\n      <Link\n        to=\"/$workspaceId/token\"\n        params={{ workspaceId: workspace.workspaceId }}\n      >\n        Tokens\n      </Link>\n\n      <p>{workspace.workspaceId}</p>\n      <button\n        disabled={bootstrapping}\n        onClick={() =>\n          startTransition(() =>\n            onBootstrap({ workspaceId: workspace.workspaceId })\n          )\n        }\n      >\n        {bootstrapping ? \"Bootstrapping...\" : \"Bootstrap\"}\n      </button>\n\n      <form action={onAddFood}>\n        <input type=\"text\" name=\"name\" />\n        <input type=\"number\" name=\"calories\" min={1} />\n        <button type=\"submit\">Add food</button>\n      </form>\n\n      <div>\n        {loading && <p>Loading...</p>}\n        {error && <pre>{JSON.stringify(error, null, 2)}</pre>}\n        {(data ?? []).map((food) => (\n          <div key={food.id}>\n            <p>Name: {food.name}</p>\n            <p>Calories: {food.calories}</p>\n          </div>\n        ))}\n      </div>\n\n      <form action={onAddMeal}>\n        {(data ?? []).map((food) => (\n          <div key={food.id}>\n            <input type=\"radio\" name=\"foodId\" id={food.id} value={food.id} />\n            <label htmlFor={food.id}>{food.name}</label>\n          </div>\n        ))}\n        <input type=\"number\" name=\"quantity\" min={1} />\n        <button type=\"submit\">Add meal</button>\n      </form>\n\n      <div>\n        {(meals ?? []).map((meal) => (\n          <div key={meal.id}>\n            <p>Food: {meal.food?.name}</p>\n            <p>Quantity: {meal.quantity}</p>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/client/src/routes/$workspaceId/join.tsx",
    "content": "import { Service } from \"@local/client-lib\";\nimport { createFileRoute, redirect } from \"@tanstack/react-router\";\nimport { Effect } from \"effect\";\nimport { RuntimeClient } from \"../../lib/runtime-client\";\n\nexport const Route = createFileRoute(\"/$workspaceId/join\")({\n  component: RouteComponent,\n  loader: ({ params }) =>\n    RuntimeClient.runPromise(\n      Effect.gen(function* () {\n        const { join } = yield* Service.Sync;\n        yield* join({ workspaceId: params.workspaceId });\n        return redirect({\n          to: `/$workspaceId`,\n          params: { workspaceId: params.workspaceId },\n        });\n      })\n    ),\n});\n\nfunction RouteComponent() {\n  return null;\n}\n"
  },
  {
    "path": "apps/client/src/routes/$workspaceId/token.tsx",
    "content": "import { Service, useActionEffect } from \"@local/client-lib\";\nimport { createFileRoute, Link, useRouter } from \"@tanstack/react-router\";\nimport { Duration, Effect } from \"effect\";\nimport { WEBSITE_URL } from \"../../lib/constants\";\nimport { RuntimeClient } from \"../../lib/runtime-client\";\n\nexport const Route = createFileRoute(\"/$workspaceId/token\")({\n  component: RouteComponent,\n  loader: ({ params: { workspaceId } }) =>\n    RuntimeClient.runPromise(\n      Effect.gen(function* () {\n        const api = yield* Service.ApiClient;\n        const token = yield* Service.WorkspaceManager.getById({\n          workspaceId,\n        }).pipe(\n          Effect.flatMap((workspace) => Effect.fromNullable(workspace?.token))\n        );\n\n        const tokens = yield* api.client.syncAuth.listTokens({\n          path: { workspaceId },\n          headers: { \"x-api-key\": token },\n        });\n\n        return { tokens, token };\n      })\n    ),\n});\n\nfunction RouteComponent() {\n  const { workspaceId } = Route.useParams();\n  const { tokens, token } = Route.useLoaderData();\n  const router = useRouter();\n\n  const [, onIssueToken, issuing] = useActionEffect(\n    RuntimeClient,\n    (formData: FormData) =>\n      Effect.gen(function* () {\n        const api = yield* Service.ApiClient;\n\n        const clientId = formData.get(\"clientId\") as string;\n\n        yield* api.client.syncAuth.issueToken({\n          path: { workspaceId },\n          headers: { \"x-api-key\": token },\n          payload: {\n            clientId,\n            expiresIn: Duration.days(30),\n            scope: \"read_write\",\n          },\n        });\n\n        yield* Effect.promise(() => router.invalidate({ sync: true }));\n      })\n  );\n\n  const [, onRevoke, revoking] = useActionEffect(\n    RuntimeClient,\n    (formData: FormData) =>\n      Effect.gen(function* () {\n        const api = yield* Service.ApiClient;\n\n        const clientId = formData.get(\"clientId\") as string;\n\n        yield* api.client.syncAuth.revokeToken({\n          path: { workspaceId, clientId },\n          headers: { \"x-api-key\": token },\n        });\n\n        yield* Effect.promise(() => router.invalidate({ sync: true }));\n      })\n  );\n\n  return (\n    <div>\n      <Link to={`/$workspaceId`} params={{ workspaceId }}>\n        Back\n      </Link>\n      <h1>Tokens</h1>\n      <table>\n        <thead>\n          <tr>\n            <th>Index</th>\n            <th>clientId</th>\n            <th>isMaster</th>\n            <th>scope</th>\n            <th>issuedAt</th>\n            <th>expiresAt</th>\n            <th>revokedAt</th>\n            <th>Share</th>\n          </tr>\n        </thead>\n        <tbody>\n          {tokens.map((token, index) => (\n            <tr key={index}>\n              <td>{index}</td>\n              <td>{token.clientId}</td>\n              <td>{token.isMaster ? \"✔️\" : \"❌\"}</td>\n              <td>{token.scope}</td>\n              <td>\n                {new Date(token.issuedAt).toLocaleDateString(undefined, {\n                  year: \"numeric\",\n                  month: \"long\",\n                  day: \"numeric\",\n                })}\n              </td>\n              <td>\n                {token.expiresAt\n                  ? new Date(token.expiresAt).toLocaleDateString(undefined, {\n                      year: \"numeric\",\n                      month: \"long\",\n                      day: \"numeric\",\n                    })\n                  : \"N/A\"}\n              </td>\n              <td>\n                {token.revokedAt ? (\n                  <span>\n                    {new Date(token.revokedAt).toLocaleDateString(undefined, {\n                      year: \"numeric\",\n                      month: \"long\",\n                      day: \"numeric\",\n                    })}\n                  </span>\n                ) : (\n                  <form action={onRevoke}>\n                    <input\n                      type=\"hidden\"\n                      name=\"clientId\"\n                      value={token.clientId}\n                    />\n                    <button type=\"submit\" disabled={revoking}>\n                      Revoke access\n                    </button>\n                  </form>\n                )}\n              </td>\n              <td>\n                <button\n                  type=\"button\"\n                  onClick={() =>\n                    navigator.clipboard.writeText(\n                      `${WEBSITE_URL}/${workspaceId}/join`\n                    )\n                  }\n                >\n                  Share\n                </button>\n              </td>\n            </tr>\n          ))}\n        </tbody>\n      </table>\n\n      <form action={onIssueToken}>\n        <input type=\"text\" name=\"clientId\" />\n        <button type=\"submit\" disabled={issuing}>\n          Issue token\n        </button>\n      </form>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/client/src/routes/__root.tsx",
    "content": "import { Service } from \"@local/client-lib\";\nimport { Outlet, createRootRoute } from \"@tanstack/react-router\";\nimport { Effect } from \"effect\";\nimport { RuntimeClient } from \"../lib/runtime-client\";\n\nexport const Route = createRootRoute({\n  component: RootComponent,\n  loader: () =>\n    RuntimeClient.runPromise(\n      Service.Migration.pipe(\n        Effect.flatMap((migration) => migration.migrate),\n        Effect.catchAll((error) => Effect.logError(\"Migration error\", error)),\n        Effect.andThen(\n          Effect.gen(function* () {\n            const { initClient } = yield* Service.Dexie;\n            return yield* initClient;\n          })\n        )\n      )\n    ),\n});\n\nfunction RootComponent() {\n  const clientId = Route.useLoaderData();\n  return (\n    <>\n      <nav>\n        <span>{clientId}</span>\n      </nav>\n      <Outlet />\n    </>\n  );\n}\n"
  },
  {
    "path": "apps/client/src/routes/index.tsx",
    "content": "import { Service, useActionEffect } from \"@local/client-lib\";\nimport { createFileRoute, Link, useNavigate } from \"@tanstack/react-router\";\nimport { Effect } from \"effect\";\nimport { RuntimeClient } from \"../lib/runtime-client\";\n\nexport const Route = createFileRoute(\"/\")({\n  component: HomeComponent,\n  loader: () => RuntimeClient.runPromise(Service.WorkspaceManager.getAll),\n});\n\nfunction HomeComponent() {\n  const allWorkspaces = Route.useLoaderData();\n  const navigate = useNavigate();\n\n  const [, joinWorkspace] = useActionEffect(RuntimeClient, () =>\n    Effect.gen(function* () {\n      const workspace = yield* Service.WorkspaceManager.create;\n      yield* Effect.sync(() =>\n        navigate({\n          to: `/$workspaceId`,\n          params: { workspaceId: workspace.workspaceId },\n        })\n      );\n    })\n  );\n\n  return (\n    <div>\n      <p>Select workspace</p>\n      {allWorkspaces.map((workspace) => (\n        <Link\n          key={workspace.workspaceId}\n          to=\"/$workspaceId\"\n          params={{ workspaceId: workspace.workspaceId }}\n        >\n          {workspace.workspaceId}\n        </Link>\n      ))}\n\n      <div>\n        <button type=\"button\" onClick={joinWorkspace}>\n          Create workspace\n        </button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/client/src/workers/bootstrap.ts",
    "content": "import { WorkerRunner } from \"@effect/platform\";\nimport { BrowserWorkerRunner } from \"@effect/platform-browser\";\nimport { SyncWorker } from \"@local/client-lib\";\nimport { Effect, Layer } from \"effect\";\nimport { RuntimeClient } from \"../lib/runtime-client\";\n\nconst WorkerLive = WorkerRunner.layerSerialized(SyncWorker.WorkerMessage, {\n  Bootstrap: (params) =>\n    Effect.gen(function* () {\n      const worker = yield* SyncWorker.SyncWorker;\n      return yield* worker.bootstrap(params);\n    }),\n}).pipe(Layer.provide(BrowserWorkerRunner.layer));\n\nRuntimeClient.runFork(WorkerRunner.launch(WorkerLive));\n"
  },
  {
    "path": "apps/client/src/workers/live.ts",
    "content": "import { WorkerRunner } from \"@effect/platform\";\nimport { BrowserWorkerRunner } from \"@effect/platform-browser\";\nimport { SyncWorker } from \"@local/client-lib\";\nimport { Effect, Layer } from \"effect\";\nimport { RuntimeClient } from \"../lib/runtime-client\";\n\nconst WorkerLive = WorkerRunner.layer((params: SyncWorker.LiveQuery) =>\n  Effect.scoped(\n    Effect.gen(function* () {\n      const worker = yield* SyncWorker.SyncWorker;\n      yield* Effect.fork(worker.liveSync({ workspaceId: params.workspaceId }));\n      yield* Effect.never;\n    })\n  )\n).pipe(Layer.provide(BrowserWorkerRunner.layer));\n\nRuntimeClient.runFork(WorkerRunner.launch(WorkerLive));\n"
  },
  {
    "path": "apps/client/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"react-jsx\",\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"skipLibCheck\": true,\n    \"allowJs\": true,\n    \"resolveJsonModule\": true,\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noUncheckedIndexedAccess\": true,\n    // \"exactOptionalPropertyTypes\": true,\n    \"noImplicitOverride\": true,\n    \"noEmit\": true,\n    \"lib\": [\"es2022\", \"dom\", \"dom.iterable\"],\n    \"types\": [\"vite/client\"]\n  },\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/client/vite.config.ts",
    "content": "import { TanStackRouterVite } from \"@tanstack/router-plugin/vite\";\nimport react from \"@vitejs/plugin-react\";\nimport { defineConfig } from \"vite\";\nimport topLevelAwait from \"vite-plugin-top-level-await\";\nimport wasm from \"vite-plugin-wasm\";\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [TanStackRouterVite({}), react(), wasm(), topLevelAwait()],\n  worker: { format: \"es\" },\n});\n"
  },
  {
    "path": "apps/server/drizzle/0000_supreme_bedlam.sql",
    "content": "CREATE TYPE \"public\".\"scope\" AS ENUM('read', 'read_write');--> statement-breakpoint\nCREATE TABLE \"client\" (\n\t\"clientId\" uuid NOT NULL,\n\t\"createdAt\" timestamp DEFAULT now() NOT NULL\n);\n--> statement-breakpoint\nCREATE TABLE \"token\" (\n\t\"tokenId\" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name \"token_tokenId_seq\" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1),\n\t\"tokenValue\" varchar NOT NULL,\n\t\"clientId\" uuid NOT NULL,\n\t\"workspaceId\" uuid NOT NULL,\n\t\"isMaster\" boolean DEFAULT false NOT NULL,\n\t\"scope\" \"scope\" NOT NULL,\n\t\"issuedAt\" timestamp DEFAULT now() NOT NULL,\n\t\"expiresAt\" timestamp,\n\t\"revokedAt\" timestamp\n);\n--> statement-breakpoint\nCREATE TABLE \"workspace\" (\n\t\"workspaceId\" uuid NOT NULL,\n\t\"ownerClientId\" uuid NOT NULL,\n\t\"clientId\" uuid NOT NULL,\n\t\"snapshotId\" uuid NOT NULL,\n\t\"createdAt\" timestamp DEFAULT now() NOT NULL,\n\t\"snapshot\" \"bytea\" NOT NULL,\n\tCONSTRAINT \"workspace_snapshotId_unique\" UNIQUE(\"snapshotId\")\n);\n"
  },
  {
    "path": "apps/server/drizzle/meta/0000_snapshot.json",
    "content": "{\n  \"id\": \"b089a447-bbae-4d52-b83e-a0ce1cc4b359\",\n  \"prevId\": \"00000000-0000-0000-0000-000000000000\",\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"tables\": {\n    \"public.client\": {\n      \"name\": \"client\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"clientId\": {\n          \"name\": \"clientId\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"createdAt\": {\n          \"name\": \"createdAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"now()\"\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.token\": {\n      \"name\": \"token\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"tokenId\": {\n          \"name\": \"tokenId\",\n          \"type\": \"integer\",\n          \"primaryKey\": true,\n          \"notNull\": true,\n          \"identity\": {\n            \"type\": \"always\",\n            \"name\": \"token_tokenId_seq\",\n            \"schema\": \"public\",\n            \"increment\": \"1\",\n            \"startWith\": \"1\",\n            \"minValue\": \"1\",\n            \"maxValue\": \"2147483647\",\n            \"cache\": \"1\",\n            \"cycle\": false\n          }\n        },\n        \"tokenValue\": {\n          \"name\": \"tokenValue\",\n          \"type\": \"varchar\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"clientId\": {\n          \"name\": \"clientId\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"workspaceId\": {\n          \"name\": \"workspaceId\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"isMaster\": {\n          \"name\": \"isMaster\",\n          \"type\": \"boolean\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": false\n        },\n        \"scope\": {\n          \"name\": \"scope\",\n          \"type\": \"scope\",\n          \"typeSchema\": \"public\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"issuedAt\": {\n          \"name\": \"issuedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"now()\"\n        },\n        \"expiresAt\": {\n          \"name\": \"expiresAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        },\n        \"revokedAt\": {\n          \"name\": \"revokedAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": false\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {},\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    },\n    \"public.workspace\": {\n      \"name\": \"workspace\",\n      \"schema\": \"\",\n      \"columns\": {\n        \"workspaceId\": {\n          \"name\": \"workspaceId\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"ownerClientId\": {\n          \"name\": \"ownerClientId\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"clientId\": {\n          \"name\": \"clientId\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"snapshotId\": {\n          \"name\": \"snapshotId\",\n          \"type\": \"uuid\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        },\n        \"createdAt\": {\n          \"name\": \"createdAt\",\n          \"type\": \"timestamp\",\n          \"primaryKey\": false,\n          \"notNull\": true,\n          \"default\": \"now()\"\n        },\n        \"snapshot\": {\n          \"name\": \"snapshot\",\n          \"type\": \"bytea\",\n          \"primaryKey\": false,\n          \"notNull\": true\n        }\n      },\n      \"indexes\": {},\n      \"foreignKeys\": {},\n      \"compositePrimaryKeys\": {},\n      \"uniqueConstraints\": {\n        \"workspace_snapshotId_unique\": {\n          \"name\": \"workspace_snapshotId_unique\",\n          \"nullsNotDistinct\": false,\n          \"columns\": [\n            \"snapshotId\"\n          ]\n        }\n      },\n      \"policies\": {},\n      \"checkConstraints\": {},\n      \"isRLSEnabled\": false\n    }\n  },\n  \"enums\": {\n    \"public.scope\": {\n      \"name\": \"scope\",\n      \"schema\": \"public\",\n      \"values\": [\n        \"read\",\n        \"read_write\"\n      ]\n    }\n  },\n  \"schemas\": {},\n  \"sequences\": {},\n  \"roles\": {},\n  \"policies\": {},\n  \"views\": {},\n  \"_meta\": {\n    \"columns\": {},\n    \"schemas\": {},\n    \"tables\": {}\n  }\n}"
  },
  {
    "path": "apps/server/drizzle/meta/_journal.json",
    "content": "{\n  \"version\": \"7\",\n  \"dialect\": \"postgresql\",\n  \"entries\": [\n    {\n      \"idx\": 0,\n      \"version\": \"7\",\n      \"when\": 1740889946454,\n      \"tag\": \"0000_supreme_bedlam\",\n      \"breakpoints\": true\n    }\n  ]\n}"
  },
  {
    "path": "apps/server/drizzle.config.ts",
    "content": "import { defineConfig } from \"drizzle-kit\";\n\nexport default defineConfig({\n  out: \"./drizzle\",\n  schema: \"./src/db/schema.ts\",\n  dialect: \"postgresql\",\n});\n"
  },
  {
    "path": "apps/server/package.json",
    "content": "{\n  \"name\": \"@local/server\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"typecheck\": \"tsc\",\n    \"dev\": \"tsx watch src/main.ts\",\n    \"generate\": \"pnpm drizzle-kit generate\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"@types/jsonwebtoken\": \"^9.0.9\",\n    \"@types/node\": \"^22.5.1\",\n    \"drizzle-kit\": \"^0.30.4\",\n    \"tsx\": \"^4.19.2\"\n  },\n  \"dependencies\": {\n    \"@effect/platform\": \"^0.77.2\",\n    \"@effect/platform-node\": \"^0.73.1\",\n    \"@effect/sql\": \"^0.30.2\",\n    \"@effect/sql-pg\": \"^0.31.2\",\n    \"@local/sync\": \"workspace:*\",\n    \"@local/schema\": \"workspace:*\",\n    \"drizzle-orm\": \"^0.39.3\",\n    \"effect\": \"^3.13.2\",\n    \"jsonwebtoken\": \"^9.0.2\",\n    \"pg\": \"^8.13.3\"\n  }\n}\n"
  },
  {
    "path": "apps/server/src/database.ts",
    "content": "import { PgClient } from \"@effect/sql-pg\";\nimport { Config, Effect, Layer, Redacted } from \"effect\";\n\nconst password = Config.redacted(\"POSTGRES_PASSWORD\");\nconst username = Config.string(\"POSTGRES_USERNAME\");\nconst database = Config.string(\"POSTGRES_DATABASE\");\nconst host = Config.string(\"POSTGRES_HOST\");\nconst port = Config.number(\"POSTGRES_PORT\");\n\nexport const DatabaseUrl = Config.all({\n  username,\n  password,\n  host,\n  port,\n  database,\n}).pipe(\n  Config.map(({ username, password, host, port, database }) =>\n    Redacted.make(\n      `postgresql://${username}:${Redacted.value(password)}@${host}:${port}/${database}`\n    )\n  )\n);\n\nexport const DatabaseLive = DatabaseUrl.pipe(\n  Effect.tap((url) =>\n    Effect.log(`Connecting to database: ${Redacted.value(url)}`)\n  ),\n  Effect.map((url) => PgClient.layer({ url })),\n  Layer.unwrapEffect\n);\n"
  },
  {
    "path": "apps/server/src/db/schema.ts",
    "content": "import {\n  boolean,\n  customType,\n  integer,\n  pgEnum,\n  pgTable,\n  timestamp,\n  uuid,\n  varchar,\n} from \"drizzle-orm/pg-core\";\n\nexport const scope = pgEnum(\"scope\", [\"read\", \"read_write\"]);\n\nexport const bytea = customType<{ data: Uint8Array }>({\n  dataType: () => \"bytea\",\n});\n\nexport const workspaceTable = pgTable(\"workspace\", {\n  workspaceId: uuid().notNull(),\n  ownerClientId: uuid().notNull(),\n  clientId: uuid().notNull(),\n  snapshotId: uuid().notNull().unique(),\n  createdAt: timestamp().notNull().defaultNow(),\n  snapshot: bytea().notNull(),\n});\n\nexport const clientTable = pgTable(\"client\", {\n  clientId: uuid().notNull(),\n  createdAt: timestamp().notNull().defaultNow(),\n});\n\nexport const tokenTable = pgTable(\"token\", {\n  tokenId: integer().primaryKey().generatedAlwaysAsIdentity(),\n  tokenValue: varchar().notNull(),\n  clientId: uuid().notNull(),\n  workspaceId: uuid().notNull(),\n  isMaster: boolean().notNull().default(false),\n  scope: scope().notNull(),\n  issuedAt: timestamp().notNull().defaultNow(),\n  expiresAt: timestamp(),\n  revokedAt: timestamp(),\n});\n"
  },
  {
    "path": "apps/server/src/group/sync-auth.ts",
    "content": "import { HttpApiBuilder } from \"@effect/platform\";\nimport { AuthWorkspace, Scope, SyncApi } from \"@local/sync\";\nimport { and, eq, not } from \"drizzle-orm\";\nimport { DateTime, Effect, Layer, Schema } from \"effect\";\nimport { tokenTable, workspaceTable } from \"../db/schema\";\nimport { AuthorizationLive } from \"../middleware/authorization\";\nimport { MasterAuthorizationLive } from \"../middleware/master-authorization\";\nimport { VersionCheckLive } from \"../middleware/version-check\";\nimport { Drizzle } from \"../services/drizzle\";\nimport { Jwt } from \"../services/jwt\";\n\nexport const SyncAuthGroupLive = HttpApiBuilder.group(\n  SyncApi,\n  \"syncAuth\",\n  (handlers) =>\n    Effect.gen(function* () {\n      const jwt = yield* Jwt;\n      const { query } = yield* Drizzle;\n\n      return handlers\n        .handle(\"generateToken\", ({ payload }) =>\n          Effect.gen(function* () {\n            yield* Effect.log(\n              `Generating token for workspace ${payload.workspaceId}`\n            );\n\n            const scope: typeof Scope.Type = \"read_write\";\n            const isMaster = true;\n            const issuedAt = DateTime.toDate(yield* DateTime.now);\n\n            yield* query({\n              Request: Schema.Struct({ workspaceId: Schema.String }),\n              execute: (db, { workspaceId }) =>\n                db\n                  .select()\n                  .from(workspaceTable)\n                  .where(eq(workspaceTable.workspaceId, workspaceId)),\n            })({ workspaceId: payload.workspaceId }).pipe(\n              Effect.flatMap((rows) =>\n                rows.length === 0\n                  ? Effect.void\n                  : Effect.fail({ message: \"Workspace already exists\" })\n              )\n            );\n\n            const token = yield* jwt.sign({\n              clientId: payload.clientId,\n              workspaceId: payload.workspaceId,\n            });\n\n            yield* Effect.all([\n              query({\n                Request: Schema.Struct({\n                  clientId: Schema.String,\n                  workspaceId: Schema.String,\n                  snapshot: Schema.Uint8ArrayFromSelf,\n                }),\n                execute: (db, { clientId, snapshot, workspaceId }) =>\n                  db.insert(workspaceTable).values({\n                    snapshot,\n                    clientId,\n                    workspaceId,\n                    ownerClientId: clientId,\n                    snapshotId: payload.snapshotId,\n                  }),\n              })({\n                clientId: payload.clientId,\n                snapshot: payload.snapshot,\n                workspaceId: payload.workspaceId,\n              }),\n              query({\n                Request: Schema.Struct({\n                  clientId: Schema.String,\n                  workspaceId: Schema.String,\n                  tokenValue: Schema.String,\n                }),\n                execute: (db, { clientId, tokenValue, workspaceId }) =>\n                  db.insert(tokenTable).values({\n                    clientId,\n                    scope,\n                    tokenValue,\n                    workspaceId,\n                    isMaster,\n                    issuedAt,\n                    expiresAt: null,\n                    revokedAt: null,\n                  }),\n              })({\n                clientId: payload.clientId,\n                tokenValue: token,\n                workspaceId: payload.workspaceId,\n              }),\n            ]);\n\n            return {\n              token,\n              workspaceId: payload.workspaceId,\n              snapshot: payload.snapshot,\n              createdAt: issuedAt,\n            };\n          }).pipe(\n            Effect.tapErrorCause(Effect.logError),\n            Effect.mapError((error) => error.message)\n          )\n        )\n        .handle(\"listTokens\", ({ path: { workspaceId } }) =>\n          Effect.gen(function* () {\n            const workspace = yield* AuthWorkspace;\n            const tokens = yield* query({\n              Request: Schema.Struct({\n                workspaceId: Schema.String,\n                clientId: Schema.String,\n              }),\n              execute: (db, { workspaceId, clientId }) =>\n                db\n                  .select()\n                  .from(tokenTable)\n                  .where(\n                    and(\n                      eq(tokenTable.workspaceId, workspaceId),\n                      not(eq(tokenTable.clientId, clientId))\n                    )\n                  ),\n            })({ workspaceId, clientId: workspace.clientId });\n\n            return tokens.map((token) => ({\n              clientId: token.clientId,\n              tokenValue: token.tokenValue,\n              scope: token.scope,\n              isMaster: token.isMaster,\n              issuedAt: token.issuedAt,\n              expiresAt: token.expiresAt,\n              revokedAt: token.revokedAt,\n            }));\n          }).pipe(\n            Effect.tapErrorCause(Effect.logError),\n            Effect.mapError((error) => error.message)\n          )\n        )\n        .handle(\"issueToken\", ({ path: { workspaceId }, payload }) =>\n          Effect.gen(function* () {\n            yield* Effect.log(`Issuing token for workspace ${workspaceId}`);\n\n            const issuedAt = yield* DateTime.now;\n            const expiresAt = issuedAt.pipe(\n              DateTime.addDuration(payload.expiresIn),\n              DateTime.toDate\n            );\n            const token = yield* jwt.sign({\n              clientId: payload.clientId,\n              workspaceId,\n            });\n\n            yield* query({\n              Request: Schema.Struct({\n                clientId: Schema.String,\n                workspaceId: Schema.String,\n                tokenValue: Schema.String,\n                scope: Scope,\n                expiresAt: Schema.DateFromSelf,\n                issuedAt: Schema.DateFromSelf,\n              }),\n              execute: (\n                db,\n                {\n                  clientId,\n                  tokenValue,\n                  workspaceId,\n                  scope,\n                  expiresAt,\n                  issuedAt,\n                }\n              ) =>\n                db.insert(tokenTable).values({\n                  clientId,\n                  scope,\n                  tokenValue,\n                  workspaceId,\n                  issuedAt,\n                  expiresAt,\n                  isMaster: false,\n                  revokedAt: null,\n                }),\n            })({\n              expiresAt,\n              workspaceId,\n              tokenValue: token,\n              scope: payload.scope,\n              clientId: payload.clientId,\n              issuedAt: DateTime.toDate(issuedAt),\n            });\n\n            return {\n              token,\n              expiresAt,\n              scope: payload.scope,\n            };\n          }).pipe(\n            Effect.tapErrorCause(Effect.logError),\n            Effect.mapError((error) => error.message)\n          )\n        )\n        .handle(\"revokeToken\", ({ path: { workspaceId, clientId } }) =>\n          Effect.gen(function* () {\n            yield* Effect.log(`Revoking token for workspace ${workspaceId}`);\n\n            const revokedAt = yield* DateTime.now;\n\n            yield* query({\n              Request: Schema.Struct({\n                workspaceId: Schema.String,\n                clientId: Schema.String,\n              }),\n              execute: (db, { workspaceId, clientId }) =>\n                db\n                  .update(tokenTable)\n                  .set({ revokedAt: DateTime.toDate(revokedAt) })\n                  .where(\n                    and(\n                      eq(tokenTable.workspaceId, workspaceId),\n                      eq(tokenTable.clientId, clientId)\n                    )\n                  ),\n            })({ workspaceId, clientId });\n\n            return true;\n          }).pipe(\n            Effect.tapErrorCause(Effect.logError),\n            Effect.mapError((error) => error.message)\n          )\n        );\n    })\n).pipe(\n  Layer.provide([\n    Drizzle.Default,\n    AuthorizationLive,\n    MasterAuthorizationLive,\n    VersionCheckLive,\n    Jwt.Default,\n  ])\n);\n"
  },
  {
    "path": "apps/server/src/group/sync-data.ts",
    "content": "import { HttpApiBuilder } from \"@effect/platform\";\nimport { SnapshotToLoroDoc } from \"@local/schema\";\nimport { AuthWorkspace, SyncApi } from \"@local/sync\";\nimport { and, desc, eq, gt, isNull, or } from \"drizzle-orm\";\nimport { Array, Data, Effect, Layer, Schema } from \"effect\";\nimport { tokenTable, workspaceTable } from \"../db/schema\";\nimport { AuthorizationLive } from \"../middleware/authorization\";\nimport { VersionCheckLive } from \"../middleware/version-check\";\nimport { Drizzle } from \"../services/drizzle\";\n\nclass InvalidVersionError extends Data.TaggedError(\"InvalidVersionError\")<{\n  reason: \"missing\" | \"outdated\";\n}> {}\n\nexport const SyncDataGroupLive = HttpApiBuilder.group(\n  SyncApi,\n  \"syncData\",\n  (handlers) =>\n    Effect.gen(function* () {\n      const { query } = yield* Drizzle;\n\n      return handlers\n        .handle(\n          \"push\",\n          ({ payload: { snapshot, snapshotId }, path: { workspaceId } }) =>\n            Effect.gen(function* () {\n              const workspace = yield* AuthWorkspace;\n              const doc = yield* Schema.decode(SnapshotToLoroDoc)(snapshot);\n\n              yield* Effect.log(`Pushing workspace ${workspaceId}`);\n\n              doc.import(workspace.snapshot);\n              const newSnapshot = yield* Schema.encode(SnapshotToLoroDoc)(doc);\n\n              const newWorkspace = yield* query({\n                Request: Schema.Struct({\n                  newSnapshot: Schema.Uint8ArrayFromSelf,\n                }),\n                execute: (db, { newSnapshot: snapshot }) =>\n                  db\n                    .insert(workspaceTable)\n                    .values({\n                      snapshot,\n                      snapshotId,\n                      clientId: workspace.clientId,\n                      workspaceId: workspace.workspaceId,\n                      ownerClientId: workspace.ownerClientId,\n                    })\n                    .returning(),\n              })({ newSnapshot }).pipe(Effect.flatMap(Array.head));\n\n              return {\n                workspaceId: newWorkspace.workspaceId,\n                createdAt: newWorkspace.createdAt,\n                snapshot: newWorkspace.snapshot,\n              };\n            }).pipe(\n              Effect.tapErrorCause(Effect.logError),\n              Effect.mapError((error) => error.message)\n            )\n        )\n        .handle(\"pull\", ({ path: { workspaceId } }) =>\n          Effect.gen(function* () {\n            const workspace = yield* query({\n              Request: Schema.Struct({ workspaceId: Schema.String }),\n              execute: (db, { workspaceId }) =>\n                db\n                  .select()\n                  .from(workspaceTable)\n                  .where(eq(workspaceTable.workspaceId, workspaceId))\n                  .orderBy(desc(workspaceTable.createdAt))\n                  .limit(1),\n            })({ workspaceId }).pipe(\n              Effect.flatMap(Array.head),\n              Effect.mapError(() => ({ message: \"Missing workspace\" }))\n            );\n\n            return { snapshot: workspace.snapshot };\n          }).pipe(\n            Effect.tapErrorCause(Effect.logError),\n            Effect.mapError((error) => error.message)\n          )\n        )\n        .handle(\"join\", ({ path: { workspaceId, clientId } }) =>\n          Effect.gen(function* () {\n            const { tokenValue } = yield* query({\n              Request: Schema.Struct({\n                clientId: Schema.String,\n                workspaceId: Schema.String,\n              }),\n              execute: (db, { clientId, workspaceId }) =>\n                db\n                  .select()\n                  .from(tokenTable)\n                  .where(\n                    and(\n                      eq(tokenTable.workspaceId, workspaceId),\n                      eq(tokenTable.clientId, clientId),\n                      or(\n                        isNull(tokenTable.revokedAt),\n                        gt(tokenTable.revokedAt, new Date())\n                      ),\n                      or(\n                        isNull(tokenTable.expiresAt),\n                        gt(tokenTable.expiresAt, new Date())\n                      )\n                    )\n                  )\n                  .orderBy(desc(tokenTable.issuedAt))\n                  .limit(1),\n            })({ clientId, workspaceId }).pipe(\n              Effect.flatMap(Array.head),\n              Effect.mapError(() => ({ message: \"Missing token\" }))\n            );\n\n            const workspace = yield* query({\n              Request: Schema.Struct({ workspaceId: Schema.String }),\n              execute: (db, { workspaceId }) =>\n                db\n                  .select()\n                  .from(workspaceTable)\n                  .where(eq(workspaceTable.workspaceId, workspaceId))\n                  .orderBy(desc(workspaceTable.createdAt))\n                  .limit(1),\n            })({ workspaceId }).pipe(\n              Effect.flatMap(Array.head),\n              Effect.mapError(() => ({ message: \"Missing workspace\" }))\n            );\n\n            return {\n              token: tokenValue,\n              snapshot: workspace.snapshot,\n            };\n          }).pipe(\n            Effect.tapErrorCause(Effect.logError),\n            Effect.mapError((error) => error.message)\n          )\n        );\n    })\n).pipe(Layer.provide([Drizzle.Default, AuthorizationLive, VersionCheckLive]));\n"
  },
  {
    "path": "apps/server/src/main.ts",
    "content": "import {\n  HttpApiBuilder,\n  HttpMiddleware,\n  HttpServer,\n  PlatformConfigProvider,\n} from \"@effect/platform\";\nimport {\n  NodeFileSystem,\n  NodeHttpServer,\n  NodeRuntime,\n} from \"@effect/platform-node\";\nimport { SyncApi } from \"@local/sync\";\nimport { Effect, Layer } from \"effect\";\nimport { createServer } from \"node:http\";\nimport { SyncAuthGroupLive } from \"./group/sync-auth\";\nimport { SyncDataGroupLive } from \"./group/sync-data\";\nimport { Drizzle } from \"./services/drizzle\";\n\nconst EnvProviderLayer = Layer.unwrapEffect(\n  PlatformConfigProvider.fromDotEnv(\".env\").pipe(\n    Effect.map(Layer.setConfigProvider),\n    Effect.provide(NodeFileSystem.layer)\n  )\n);\n\nconst MainApiLive = HttpApiBuilder.api(SyncApi).pipe(\n  Layer.provide([Drizzle.Default, SyncDataGroupLive, SyncAuthGroupLive]),\n  Layer.provide(EnvProviderLayer)\n);\n\nconst HttpLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(\n  Layer.provide(HttpApiBuilder.middlewareCors()),\n  Layer.provide(MainApiLive),\n  HttpServer.withLogAddress,\n  Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))\n);\n\nNodeRuntime.runMain(Layer.launch(HttpLive));\n"
  },
  {
    "path": "apps/server/src/middleware/authorization.ts",
    "content": "import {\n  Authorization,\n  ClientId,\n  DatabaseError,\n  MissingWorkspace,\n  Unauthorized,\n  WorkspaceId,\n} from \"@local/sync\";\nimport { and, desc, eq, gt, isNull, or } from \"drizzle-orm\";\nimport { Array, Effect, Layer, Match, Redacted, Schema } from \"effect\";\nimport { tokenTable, workspaceTable } from \"../db/schema\";\nimport { Drizzle } from \"../services/drizzle\";\nimport { Jwt } from \"../services/jwt\";\n\nexport const AuthorizationLive = Layer.effect(\n  Authorization,\n  Effect.gen(function* () {\n    const jwt = yield* Jwt;\n    const { query } = yield* Drizzle;\n    yield* Effect.log(\"Creating Authorization middleware\");\n    return {\n      apiKey: (apiKey) =>\n        Effect.gen(function* () {\n          yield* Effect.log(\"Api key\", Redacted.value(apiKey));\n\n          const tokenPayload = yield* jwt.decode({ apiKey });\n\n          yield* Effect.log(`Valid auth ${tokenPayload.workspaceId}`);\n\n          yield* query({\n            Request: Schema.Struct({\n              workspaceId: WorkspaceId,\n              clientId: ClientId,\n            }),\n            execute: (db, { workspaceId, clientId }) =>\n              db\n                .select()\n                .from(tokenTable)\n                .where(\n                  and(\n                    eq(tokenTable.workspaceId, workspaceId),\n                    eq(tokenTable.clientId, clientId),\n                    or(\n                      isNull(tokenTable.revokedAt),\n                      gt(tokenTable.revokedAt, new Date())\n                    ),\n                    or(\n                      isNull(tokenTable.expiresAt),\n                      gt(tokenTable.expiresAt, new Date())\n                    )\n                  )\n                )\n                .orderBy(desc(tokenTable.issuedAt))\n                .limit(1),\n          })({\n            workspaceId: tokenPayload.workspaceId,\n            clientId: tokenPayload.sub,\n          }).pipe(\n            Effect.flatMap(Array.head),\n            Effect.tapError(Effect.logError),\n            Effect.mapError(\n              () =>\n                new Unauthorized({\n                  message: \"Missing, expired, or revoked token\",\n                })\n            )\n          );\n\n          return yield* query({\n            Request: Schema.Struct({\n              workspaceId: Schema.UUID,\n            }),\n            execute: (db, { workspaceId }) =>\n              db\n                .select()\n                .from(workspaceTable)\n                .where(eq(workspaceTable.workspaceId, workspaceId))\n                .orderBy(desc(workspaceTable.createdAt))\n                .limit(1),\n          })({ workspaceId: tokenPayload.workspaceId }).pipe(\n            Effect.flatMap(Array.head)\n          );\n        }).pipe(\n          Effect.mapError((error) =>\n            Match.value(error).pipe(\n              Match.tagsExhaustive({\n                NoSuchElementException: () => new MissingWorkspace(),\n                Unauthorized: (error) => error,\n                JwtError: () => new Unauthorized({ message: \"Invalid token\" }),\n                ParseError: () =>\n                  new Unauthorized({ message: \"Invalid parameters\" }),\n                QueryError: () => new DatabaseError(),\n              })\n            )\n          )\n        ),\n    };\n  })\n).pipe(Layer.provide([Drizzle.Default, Jwt.Default]));\n"
  },
  {
    "path": "apps/server/src/middleware/master-authorization.ts",
    "content": "import {\n  DatabaseError,\n  MasterAuthorization,\n  MissingWorkspace,\n  Unauthorized,\n} from \"@local/sync\";\nimport { and, desc, eq } from \"drizzle-orm\";\nimport { Array, Effect, Layer, Match, Redacted, Schema } from \"effect\";\nimport { workspaceTable } from \"../db/schema\";\nimport { Drizzle } from \"../services/drizzle\";\nimport { Jwt } from \"../services/jwt\";\n\nexport const MasterAuthorizationLive = Layer.effect(\n  MasterAuthorization,\n  Effect.gen(function* () {\n    const jwt = yield* Jwt;\n    const { query } = yield* Drizzle;\n    yield* Effect.log(\"Creating Master Authorization middleware\");\n    return {\n      apiKey: (apiKey) =>\n        Effect.gen(function* () {\n          yield* Effect.log(\"Api key\", Redacted.value(apiKey));\n\n          const tokenPayload = yield* jwt.decode({ apiKey });\n\n          if (tokenPayload.isMaster) {\n            return yield* query({\n              Request: Schema.Struct({\n                workspaceId: Schema.UUID,\n                clientId: Schema.UUID,\n              }),\n              execute: (db, { workspaceId, clientId }) =>\n                db\n                  .select()\n                  .from(workspaceTable)\n                  .where(\n                    and(\n                      eq(workspaceTable.workspaceId, workspaceId),\n                      eq(workspaceTable.clientId, clientId)\n                    )\n                  )\n                  .orderBy(desc(workspaceTable.createdAt))\n                  .limit(1),\n            })({\n              clientId: tokenPayload.sub,\n              workspaceId: tokenPayload.workspaceId,\n            }).pipe(Effect.flatMap(Array.head));\n          }\n\n          return yield* new Unauthorized({ message: \"Not master access\" });\n        }).pipe(\n          Effect.mapError((error) =>\n            Match.value(error).pipe(\n              Match.tagsExhaustive({\n                NoSuchElementException: () => new MissingWorkspace(),\n                Unauthorized: (error) => error,\n                JwtError: () => new Unauthorized({ message: \"Invalid token\" }),\n                ParseError: () =>\n                  new Unauthorized({ message: \"Invalid parameters\" }),\n                QueryError: () => new DatabaseError(),\n              })\n            )\n          )\n        ),\n    };\n  })\n).pipe(Layer.provide([Drizzle.Default, Jwt.Default]));\n"
  },
  {
    "path": "apps/server/src/middleware/version-check.ts",
    "content": "import { HttpServerRequest } from \"@effect/platform\";\nimport { SnapshotToLoroDoc, VERSION } from \"@local/schema\";\nimport { Snapshot, VersionCheck, VersionError } from \"@local/sync\";\nimport { Effect, Layer, Schema } from \"effect\";\n\nexport const VersionCheckLive = Layer.effect(\n  VersionCheck,\n  Effect.gen(function* () {\n    yield* Effect.log(\"Creating version check middleware\");\n    return Effect.gen(function* () {\n      const request = yield* HttpServerRequest.HttpServerRequest;\n\n      yield* Effect.log(\"Checking version\");\n\n      const { snapshot } = yield* request.json.pipe(\n        Effect.flatMap(\n          Schema.decodeUnknown(\n            Schema.Struct({\n              snapshot: Snapshot,\n            })\n          )\n        ),\n        Effect.mapError(() => new VersionError({ reason: \"missing-snapshot\" }))\n      );\n\n      const doc = yield* Schema.decode(SnapshotToLoroDoc)(snapshot).pipe(\n        Effect.mapError(() => new VersionError({ reason: \"invalid-doc\" }))\n      );\n\n      yield* Effect.log(\"Checking doc\", doc.toJSON());\n\n      const currentVersion = doc.getMap(\"metadata\").get(\"version\");\n\n      yield* Effect.log(\"Current version\", currentVersion);\n\n      if (typeof currentVersion !== \"number\") {\n        return yield* new VersionError({ reason: \"missing-version\" });\n      } else if (currentVersion !== VERSION) {\n        return yield* new VersionError({ reason: \"outdated-version\" });\n      }\n\n      return snapshot;\n    });\n  })\n);\n"
  },
  {
    "path": "apps/server/src/services/drizzle.ts",
    "content": "import { Data, Effect, Redacted, Schema } from \"effect\";\nimport { DatabaseUrl } from \"../database\";\n\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { migrate } from \"drizzle-orm/node-postgres/migrator\";\nimport { fileURLToPath } from \"node:url\";\n\nclass MigrationError extends Data.TaggedError(\"MigrationError\")<{\n  cause: unknown;\n}> {}\n\nclass QueryError extends Data.TaggedError(\"QueryError\")<{\n  cause: unknown;\n}> {}\n\nexport class Drizzle extends Effect.Service<Drizzle>()(\"Drizzle\", {\n  effect: Effect.gen(function* () {\n    const databaseUrl = yield* DatabaseUrl;\n    const db = drizzle(Redacted.value(databaseUrl));\n\n    yield* Effect.log(\"Applying migrations\").pipe(\n      Effect.andThen(\n        Effect.tryPromise({\n          try: () =>\n            migrate(db, {\n              migrationsFolder: fileURLToPath(\n                new URL(\"../../drizzle\", import.meta.url)\n              ),\n            }),\n          catch: (error) => new MigrationError({ cause: error }),\n        })\n      ),\n      Effect.tap(() => Effect.log(\"Migrations applied\"))\n    );\n\n    const query =\n      <RQI, RQA, RSA>({\n        Request,\n        // Result,\n        execute,\n      }: {\n        Request: Schema.Schema<RQA, RQI>;\n        // Result: Schema.Schema<RSA, RSI>;\n        execute: (_: typeof db, __: RQA) => Promise<RSA>;\n      }) =>\n      (params: RQI) =>\n        Schema.decode(Request)(params).pipe(\n          Effect.flatMap((_) =>\n            Effect.tryPromise({\n              try: () => execute(db, _),\n              catch: (error) => new QueryError({ cause: error }),\n            })\n          )\n          // Effect.flatMap(Schema.decode(Result))\n        );\n\n    return { db, query };\n  }),\n}) {}\n"
  },
  {
    "path": "apps/server/src/services/jwt.ts",
    "content": "import { Scope } from \"@local/sync\";\nimport { Config, Data, Effect, Redacted, Schema } from \"effect\";\nimport * as jwt from \"jsonwebtoken\";\n\nclass TokenPayload extends Schema.Class<TokenPayload>(\"TokenPayload\")({\n  iat: Schema.Number,\n  exp: Schema.optional(Schema.Number),\n  sub: Schema.String,\n  workspaceId: Schema.String,\n  scope: Scope,\n  isMaster: Schema.Boolean,\n}) {}\n\nclass JwtError extends Data.TaggedError(\"JwtError\")<{\n  reason: \"missing\" | \"invalid\";\n}> {}\n\nexport class Jwt extends Effect.Service<Jwt>()(\"Jwt\", {\n  effect: Effect.gen(function* () {\n    const secretKey = yield* Config.redacted(\"JWT_SECRET\");\n    return {\n      sign: ({\n        clientId,\n        workspaceId,\n      }: {\n        clientId: string;\n        workspaceId: string;\n      }) =>\n        Schema.encode(TokenPayload)(\n          new TokenPayload({\n            iat: Math.floor(Date.now() / 1000),\n            sub: clientId,\n            workspaceId,\n            scope: \"read_write\",\n            isMaster: true,\n          })\n        ).pipe(\n          Effect.flatMap((payload) =>\n            Effect.try({\n              try: () =>\n                jwt.sign(payload, Redacted.value(secretKey), {\n                  algorithm: \"HS256\",\n                }),\n              catch: () => new JwtError({ reason: \"invalid\" }),\n            })\n          )\n        ),\n\n      decode: ({ apiKey }: { apiKey: Redacted.Redacted<string> }) =>\n        Effect.gen(function* () {\n          const decoded = jwt.decode(Redacted.value(apiKey));\n\n          if (decoded === null) {\n            return yield* new JwtError({ reason: \"missing\" });\n          }\n\n          return yield* Schema.decodeUnknown(TokenPayload)(decoded).pipe(\n            Effect.mapError(() => new JwtError({ reason: \"invalid\" }))\n          );\n        }),\n    };\n  }),\n}) {}\n"
  },
  {
    "path": "apps/server/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"target\": \"es2022\",\n    \"allowJs\": true,\n    \"resolveJsonModule\": true,\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitOverride\": true,\n    \"module\": \"preserve\",\n    \"noEmit\": true,\n    \"lib\": [\"es2022\"]\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "version: \"0.2\"\nname: app_docker\n\nservices:\n  postgres:\n    env_file: .env\n    container_name: postgres\n    image: postgres:16-alpine\n    environment:\n      - POSTGRES_USER=${POSTGRES_USER}\n      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}\n      - POSTGRES_DB=${POSTGRES_DATABASE}\n    ports:\n      - 5435:5432\n\n  pgadmin:\n    env_file: .env\n    container_name: pgadmin\n    image: dpage/pgadmin4:latest\n    environment:\n      - PGADMIN_DEFAULT_EMAIL=${PGADMIN_MAIL}\n      - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_PW}\n    ports:\n      - 5050:80\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"sql-database-local-config\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"turbo run build\",\n    \"dev\": \"turbo run dev\",\n    \"typecheck\": \"turbo run typecheck\",\n    \"generate\": \"turbo run generate\",\n    \"format\": \"prettier --write \\\"**/*.{ts,tsx,md}\\\"\"\n  },\n  \"devDependencies\": {\n    \"prettier\": \"^3.5.0\",\n    \"turbo\": \"^2.4.2\",\n    \"typescript\": \"5.7.3\"\n  },\n  \"packageManager\": \"pnpm@9.0.0\",\n  \"engines\": {\n    \"node\": \">=18\"\n  }\n}\n"
  },
  {
    "path": "packages/client-lib/package.json",
    "content": "{\n  \"name\": \"@local/client-lib\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"typecheck\": \"tsc\"\n  },\n  \"exports\": {\n    \".\": \"./src/main.ts\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^19.0.8\",\n    \"@types/react-dom\": \"^19.0.3\"\n  },\n  \"peerDependencies\": {\n    \"@effect/platform\": \"^0.77.2\",\n    \"effect\": \"^3.13.2\",\n    \"@local/sync\": \"workspace:*\",\n    \"@local/schema\": \"workspace:*\",\n    \"dexie\": \"^4.0.11\",\n    \"dexie-react-hooks\": \"^1.1.7\",\n    \"loro-crdt\": \"^1.4.2\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "packages/client-lib/src/main.ts",
    "content": "import { RuntimeLayer } from \"./runtime-layer\";\nimport * as Service from \"./services\";\nimport * as SyncWorker from \"./sync-worker\";\nimport { useActionEffect } from \"./use-action-effect\";\nimport { useDexieQuery } from \"./use-dexie-query\";\n\nexport {\n  RuntimeLayer,\n  Service,\n  SyncWorker,\n  useActionEffect as useActionEffect,\n  useDexieQuery,\n};\n"
  },
  {
    "path": "packages/client-lib/src/runtime-layer.ts",
    "content": "import { Layer } from \"effect\";\nimport { ApiClient } from \"./services/api-client\";\nimport { Dexie } from \"./services/dexie\";\nimport { LoroStorage } from \"./services/loro-storage\";\nimport { Migration } from \"./services/migration\";\nimport { Sync } from \"./services/sync\";\nimport { TempWorkspace } from \"./services/temp-workspace\";\nimport { WorkspaceManager } from \"./services/workspace-manager\";\nimport { SyncWorker } from \"./sync-worker\";\n\nexport const RuntimeLayer = Layer.mergeAll(\n  Dexie.Default,\n  ApiClient.Default,\n  WorkspaceManager.Default,\n  TempWorkspace.Default,\n  LoroStorage.Default,\n  Sync.Default,\n  Migration.Default,\n  SyncWorker.Default\n);\n"
  },
  {
    "path": "packages/client-lib/src/schema.ts",
    "content": "import { ClientId, Snapshot, SnapshotId, WorkspaceId } from \"@local/sync\";\nimport { Schema } from \"effect\";\n\nexport class ClientTable extends Schema.Class<ClientTable>(\"ClientTable\")({\n  clientId: ClientId,\n}) {}\n\nexport class WorkspaceTable extends Schema.Class<WorkspaceTable>(\n  \"WorkspaceTable\"\n)({\n  workspaceId: WorkspaceId,\n  snapshot: Snapshot,\n  token: Schema.NullOr(Schema.String),\n\n  version: Schema.NullOr(Schema.Uint8Array),\n}) {}\n\nexport class TempWorkspaceTable extends Schema.Class<TempWorkspaceTable>(\n  \"TempWorkspaceTable\"\n)({\n  workspaceId: WorkspaceId,\n  snapshot: Snapshot,\n  snapshotId: SnapshotId,\n}) {}\n"
  },
  {
    "path": "packages/client-lib/src/services/api-client.ts",
    "content": "import { FetchHttpClient, HttpApiClient } from \"@effect/platform\";\nimport { SyncApi } from \"@local/sync\";\nimport { Effect } from \"effect\";\n\nexport class ApiClient extends Effect.Service<ApiClient>()(\"ApiClient\", {\n  dependencies: [FetchHttpClient.layer],\n  effect: Effect.gen(function* () {\n    const client = yield* HttpApiClient.make(SyncApi, {\n      baseUrl: \"http://localhost:3000\",\n    });\n\n    return { client };\n  }),\n}) {}\n"
  },
  {
    "path": "packages/client-lib/src/services/dexie.ts",
    "content": "import * as _Dexie from \"dexie\";\nimport { Data, Effect, Schema } from \"effect\";\nimport {\n  type ClientTable,\n  type TempWorkspaceTable,\n  type WorkspaceTable,\n} from \"../schema\";\n\nclass QueryApiError extends Data.TaggedError(\"QueryApiError\")<{\n  cause: unknown;\n}> {}\n\nclass WriteApiError extends Data.TaggedError(\"WriteApiError\")<{\n  cause: unknown;\n}> {}\n\nconst formDataToRecord = (formData: FormData): Record<string, string> => {\n  const record: Record<string, string> = {};\n  for (const [key, value] of formData.entries()) {\n    if (typeof value === \"string\") {\n      record[key] = value;\n    }\n  }\n  return record;\n};\n\nexport class Dexie extends Effect.Service<Dexie>()(\"Dexie\", {\n  accessors: true,\n  effect: Effect.gen(function* () {\n    const db = new _Dexie.Dexie(\"_db\") as _Dexie.Dexie & {\n      client: _Dexie.EntityTable<typeof ClientTable.Encoded, \"clientId\">;\n      workspace: _Dexie.EntityTable<\n        typeof WorkspaceTable.Encoded,\n        \"workspaceId\"\n      >;\n      temp_workspace: _Dexie.EntityTable<\n        typeof TempWorkspaceTable.Encoded,\n        \"workspaceId\"\n      >;\n    };\n\n    db.version(1).stores({\n      client: \"clientId\",\n      workspace: \"workspaceId\",\n      temp_workspace: \"workspaceId\",\n    });\n\n    const query = <T>(execute: (_: typeof db) => Promise<T>) =>\n      Effect.tryPromise({\n        try: () => execute(db),\n        catch: (error) => new QueryApiError({ cause: error }),\n      }).pipe(Effect.tapErrorCause(Effect.logError));\n\n    const initClient = query((_) => _.client.toCollection().last()).pipe(\n      Effect.map((client) => client?.clientId),\n      Effect.flatMap(Effect.fromNullable),\n      Effect.orElse(() =>\n        query((_) => _.client.add({ clientId: crypto.randomUUID() }))\n      )\n    );\n\n    const formAction =\n      <const R extends string, I, T>(\n        source: Schema.Schema<I, Record<R, string>>,\n        exec: (values: Readonly<I>) => Promise<T>\n      ) =>\n      (formData: FormData) =>\n        Schema.decodeUnknown(source)(formDataToRecord(formData)).pipe(\n          Effect.mapError((error) => new WriteApiError({ cause: error })),\n          Effect.flatMap((values) =>\n            Effect.tryPromise({\n              try: () => exec(values),\n              catch: (error) => new WriteApiError({ cause: error }),\n            })\n          )\n        );\n\n    const changeAction =\n      <A, I, T>(\n        source: Schema.Schema<A, I>,\n        exec: (values: Readonly<A>) => Promise<T>\n      ) =>\n      (params: I) =>\n        Schema.decode(source)(params).pipe(\n          Effect.tap(Effect.log),\n          Effect.mapError((error) => new WriteApiError({ cause: error })),\n          Effect.flatMap((values) =>\n            Effect.tryPromise({\n              try: () => exec(values),\n              catch: (error) => new WriteApiError({ cause: error }),\n            })\n          )\n        );\n\n    return { db, initClient, query };\n  }),\n}) {}\n"
  },
  {
    "path": "packages/client-lib/src/services/index.ts",
    "content": "import { ApiClient } from \"./api-client\";\nimport { Dexie } from \"./dexie\";\nimport { LoroStorage } from \"./loro-storage\";\nimport { Migration } from \"./migration\";\nimport { Sync } from \"./sync\";\nimport { TempWorkspace } from \"./temp-workspace\";\nimport { WorkspaceManager } from \"./workspace-manager\";\n\nexport {\n  ApiClient,\n  Dexie,\n  LoroStorage,\n  Migration,\n  Sync,\n  TempWorkspace,\n  WorkspaceManager,\n};\n"
  },
  {
    "path": "packages/client-lib/src/services/loro-storage.ts",
    "content": "import { SnapshotSchema, type LoroSchema } from \"@local/schema\";\nimport { Effect, Schema } from \"effect\";\nimport { LoroDoc, type LoroList, type LoroMap } from \"loro-crdt\";\nimport { TempWorkspace } from \"./temp-workspace\";\nimport { WorkspaceManager } from \"./workspace-manager\";\n\nexport class LoroStorage extends Effect.Service<LoroStorage>()(\"LoroStorage\", {\n  accessors: true,\n  dependencies: [TempWorkspace.Default, WorkspaceManager.Default],\n  effect: Effect.gen(function* () {\n    const manager = yield* WorkspaceManager;\n    const temp = yield* TempWorkspace;\n\n    const load = ({ workspaceId }: { workspaceId: string }) =>\n      Effect.all(\n        {\n          workspace: manager.getById({ workspaceId }),\n          tempWorkspace: temp.getById({ workspaceId }),\n        },\n        { concurrency: \"unbounded\" }\n      ).pipe(\n        Effect.map(({ workspace, tempWorkspace }) => {\n          const doc = SnapshotSchema.EmptyDoc();\n\n          if (workspace !== undefined) {\n            doc.import(workspace.snapshot);\n          }\n\n          if (tempWorkspace !== undefined) {\n            doc.import(tempWorkspace.snapshot);\n          }\n\n          return { doc, workspace };\n        })\n      );\n\n    const query = <A extends Record<string, unknown>>(\n      extract: (doc: LoroDoc<LoroSchema>) => LoroList<LoroMap<A>>,\n      schema: Schema.Schema<A>,\n      { workspaceId }: { workspaceId: string }\n    ) =>\n      Effect.gen(function* () {\n        const { doc } = yield* load({ workspaceId });\n        const data = extract(doc);\n        const list = data.toArray();\n        return yield* Effect.all(\n          list.map((item) => Schema.decode(schema)(item.toJSON() as A)),\n          { concurrency: 10 }\n        );\n      });\n\n    return { load, query } as const;\n  }),\n}) {}\n"
  },
  {
    "path": "packages/client-lib/src/services/migration.ts",
    "content": "import { SnapshotToLoroDoc } from \"@local/schema\";\nimport { LoroDocMigration } from \"@local/schema/migrations\";\nimport { Data, Effect, Schema, type ParseResult } from \"effect\";\nimport { TempWorkspace } from \"./temp-workspace\";\n\nclass MigrationError extends Data.TaggedError(\"MigrationError\")<{\n  parseError: ParseResult.ParseError;\n}> {}\n\nexport class Migration extends Effect.Service<Migration>()(\"Migration\", {\n  dependencies: [TempWorkspace.Default],\n  effect: Effect.gen(function* () {\n    const temp = yield* TempWorkspace;\n    return {\n      migrate: temp.getAll.pipe(\n        Effect.tap((workspaces) =>\n          Effect.log(`Migrating ${workspaces.length} workspaces`)\n        ),\n        Effect.flatMap((workspaces) =>\n          Effect.all(\n            workspaces.map((workspace) =>\n              Effect.gen(function* () {\n                const doc = yield* Schema.decode(SnapshotToLoroDoc)(\n                  workspace.snapshot\n                );\n\n                yield* Effect.log(doc.toJSON());\n\n                const newDoc = yield* Schema.decode(LoroDocMigration)(doc).pipe(\n                  Effect.catchTag(\n                    \"ParseError\",\n                    (parseError) => new MigrationError({ parseError })\n                  )\n                );\n\n                const newSnapshot =\n                  yield* Schema.encode(SnapshotToLoroDoc)(newDoc);\n\n                yield* temp.put({\n                  workspaceId: workspace.workspaceId,\n                  snapshot: newSnapshot,\n                  snapshotId: workspace.snapshotId,\n                });\n              })\n            )\n          )\n        ),\n        Effect.tap(() =>\n          Effect.log(\"Migration completed successfully for all workspaces\")\n        )\n      ),\n    };\n  }),\n}) {}\n"
  },
  {
    "path": "packages/client-lib/src/services/sync.ts",
    "content": "import type { LoroSchema } from \"@local/schema\";\nimport { Effect, flow, Option } from \"effect\";\nimport { LoroDoc } from \"loro-crdt\";\nimport { ApiClient } from \"./api-client\";\nimport { Dexie } from \"./dexie\";\nimport { TempWorkspace } from \"./temp-workspace\";\nimport { WorkspaceManager } from \"./workspace-manager\";\n\nexport class Sync extends Effect.Service<Sync>()(\"Sync\", {\n  dependencies: [\n    TempWorkspace.Default,\n    WorkspaceManager.Default,\n    ApiClient.Default,\n    Dexie.Default,\n  ],\n  effect: Effect.gen(function* () {\n    const { client } = yield* ApiClient;\n    const { initClient } = yield* Dexie;\n    const manager = yield* WorkspaceManager;\n    const temp = yield* TempWorkspace;\n\n    return {\n      push: ({\n        snapshot,\n        workspaceId,\n        snapshotId,\n      }: {\n        workspaceId: string;\n        snapshotId: string;\n        snapshot: globalThis.Uint8Array;\n      }) =>\n        manager.getById({ workspaceId }).pipe(\n          Effect.flatMap(\n            flow(\n              Option.fromNullable,\n              Option.match({\n                onNone: () => Effect.log(\"No workspace found\"),\n                onSome: (workspace) =>\n                  Effect.gen(function* () {\n                    const clientId = yield* initClient;\n                    yield* Effect.log(`Pushing snapshot ${snapshotId}`);\n\n                    const response = yield* Effect.fromNullable(\n                      workspace.token\n                    ).pipe(\n                      Effect.flatMap((token) =>\n                        client.syncData\n                          .push({\n                            headers: { \"x-api-key\": token },\n                            path: { workspaceId: workspace.workspaceId },\n                            payload: { snapshot, snapshotId },\n                          })\n                          .pipe(\n                            Effect.map((response) => ({\n                              ...response,\n                              token,\n                            }))\n                          )\n                      ),\n                      Effect.catchTag(\"NoSuchElementException\", () =>\n                        client.syncAuth\n                          .generateToken({\n                            payload: {\n                              clientId,\n                              snapshot,\n                              snapshotId,\n                              workspaceId: workspace.workspaceId,\n                            },\n                          })\n                          .pipe(\n                            Effect.tap(({ token }) =>\n                              manager.setToken({\n                                workspaceId: workspace.workspaceId,\n                                token,\n                              })\n                            )\n                          )\n                      )\n                    );\n\n                    const doc = new LoroDoc<LoroSchema>();\n                    doc.import(response.snapshot);\n                    yield* Effect.all([\n                      manager.put({\n                        workspaceId: response.workspaceId,\n                        snapshot: response.snapshot,\n                        token: response.token,\n                        version: doc.version().encode(),\n                      }),\n                      temp.clean({\n                        workspaceId: workspace.workspaceId,\n                      }),\n                    ]);\n                  }),\n              })\n            )\n          )\n        ),\n\n      pull: ({ workspaceId }: { workspaceId: string }) =>\n        manager.getById({ workspaceId }).pipe(\n          Effect.flatMap(\n            flow(\n              Option.fromNullable,\n              Option.flatMap((workspace) =>\n                Option.fromNullable(workspace.token)\n              ),\n              Option.match({\n                onNone: () =>\n                  Effect.log(\"No token found\").pipe(Effect.map(() => null)),\n                onSome: (token) =>\n                  Effect.gen(function* () {\n                    yield* Effect.log(`Pulling from ${workspaceId}`);\n\n                    const response = yield* client.syncData.pull({\n                      headers: { \"x-api-key\": token },\n                      path: { workspaceId },\n                    });\n\n                    const doc = new LoroDoc();\n                    doc.import(response.snapshot);\n                    yield* manager.put({\n                      token,\n                      workspaceId,\n                      snapshot: response.snapshot,\n                      version: doc.version().encode(),\n                    });\n\n                    return response;\n                  }),\n              })\n            )\n          )\n        ),\n\n      join: ({ workspaceId }: { workspaceId: string }) =>\n        Effect.gen(function* () {\n          const clientId = yield* initClient;\n          const response = yield* client.syncData.join({\n            path: { clientId, workspaceId },\n          });\n\n          const doc = new LoroDoc();\n          doc.import(response.snapshot);\n          yield* manager.put({\n            workspaceId,\n            token: response.token,\n            snapshot: response.snapshot,\n            version: doc.version().encode(),\n          });\n\n          return response;\n        }),\n    };\n  }),\n}) {}\n"
  },
  {
    "path": "packages/client-lib/src/services/temp-workspace.ts",
    "content": "import { Effect, Schema } from \"effect\";\nimport { TempWorkspaceTable } from \"../schema\";\nimport { Dexie } from \"./dexie\";\n\nexport class TempWorkspace extends Effect.Service<TempWorkspace>()(\n  \"TempWorkspace\",\n  {\n    dependencies: [Dexie.Default],\n    effect: Effect.gen(function* () {\n      const { query } = yield* Dexie;\n      return {\n        getAll: query((_) => _.temp_workspace.toArray()).pipe(\n          Effect.flatMap(Schema.decode(Schema.Array(TempWorkspaceTable)))\n        ),\n\n        put: (params: typeof TempWorkspaceTable.Type) =>\n          Schema.encode(TempWorkspaceTable)(params).pipe(\n            Effect.flatMap((data) => query((_) => _.temp_workspace.put(data)))\n          ),\n\n        getById: ({ workspaceId }: { workspaceId: string }) =>\n          query((_) =>\n            _.temp_workspace\n              .where(\"workspaceId\")\n              .equals(workspaceId)\n              .limit(1)\n              .first()\n          ).pipe(\n            Effect.flatMap((workspace) =>\n              workspace === undefined\n                ? Effect.succeed(undefined)\n                : Schema.decode(TempWorkspaceTable)(workspace)\n            )\n          ),\n\n        clean: ({ workspaceId }: { workspaceId: string }) =>\n          query((_) =>\n            _.temp_workspace.where(\"workspaceId\").equals(workspaceId).delete()\n          ),\n      };\n    }),\n  }\n) {}\n"
  },
  {
    "path": "packages/client-lib/src/services/workspace-manager.ts",
    "content": "import { SnapshotSchema } from \"@local/schema\";\nimport { Snapshot } from \"@local/sync\";\nimport { Effect, Schema } from \"effect\";\nimport { WorkspaceTable } from \"../schema\";\nimport { Dexie } from \"./dexie\";\n\nexport class WorkspaceManager extends Effect.Service<WorkspaceManager>()(\n  \"WorkspaceManager\",\n  {\n    accessors: true,\n    dependencies: [Dexie.Default],\n    effect: Effect.gen(function* () {\n      const { query } = yield* Dexie;\n\n      return {\n        setToken: ({\n          token,\n          workspaceId,\n        }: {\n          workspaceId: typeof WorkspaceTable.Type.workspaceId;\n          token: NonNullable<typeof WorkspaceTable.Type.token>;\n        }) => query((_) => _.workspace.update(workspaceId, { token })),\n\n        put: (update: typeof WorkspaceTable.Type) =>\n          Schema.encode(WorkspaceTable)(update).pipe(\n            Effect.flatMap((data) => query((_) => _.workspace.put(data)))\n          ),\n\n        getAll: query((_) => _.workspace.toArray()),\n\n        getById: ({ workspaceId }: { workspaceId: string }) =>\n          query((_) =>\n            _.workspace\n              .where(\"workspaceId\")\n              .equals(workspaceId)\n              .limit(1)\n              .first()\n          ).pipe(\n            Effect.flatMap((workspace) =>\n              workspace === undefined\n                ? Effect.succeed(undefined)\n                : Schema.decode(WorkspaceTable)(workspace)\n            )\n          ),\n\n        create: query((_) =>\n          _.workspace.toCollection().modify({ current: false })\n        ).pipe(\n          Effect.andThen(\n            Schema.encode(Snapshot)(\n              SnapshotSchema.EmptyDoc().export({\n                mode: \"snapshot\",\n              })\n            )\n          ),\n          Effect.flatMap((snapshot) =>\n            query((_) =>\n              _.workspace.put({\n                snapshot,\n                token: null,\n                version: null,\n                workspaceId: crypto.randomUUID(),\n              })\n            )\n          ),\n          Effect.map((workspaceId) => ({ workspaceId }))\n        ),\n      };\n    }),\n  }\n) {}\n"
  },
  {
    "path": "packages/client-lib/src/sync-worker.ts",
    "content": "import { Snapshot } from \"@local/sync\";\nimport { liveQuery } from \"dexie\";\nimport { Array, Effect, Number, Schema, Stream, SynchronizedRef } from \"effect\";\nimport { Dexie, Sync, TempWorkspace, WorkspaceManager } from \"./services\";\n\nexport class LiveQuery extends Schema.TaggedRequest<LiveQuery>()(\"LiveQuery\", {\n  failure: Schema.String,\n  payload: { workspaceId: Schema.String },\n  success: Schema.Boolean,\n}) {}\n\nexport class Bootstrap extends Schema.TaggedRequest<Bootstrap>()(\"Bootstrap\", {\n  failure: Schema.String,\n  payload: { workspaceId: Schema.String },\n  success: Schema.Boolean,\n}) {}\n\nexport const WorkerMessage = Schema.Union(Bootstrap);\n\nexport class SyncWorker extends Effect.Service<SyncWorker>()(\"SyncWorker\", {\n  effect: Effect.gen(function* () {\n    return {\n      liveSync: (params: { workspaceId: string }) =>\n        Effect.gen(function* () {\n          const manager = yield* WorkspaceManager;\n          const { db } = yield* Dexie;\n          const { push } = yield* Sync;\n\n          const snapshotEq = Array.getEquivalence(Number.Equivalence);\n\n          yield* Effect.log(`Live query workspace '${params.workspaceId}'`);\n\n          const workspace = yield* manager\n            .getById({ workspaceId: params.workspaceId })\n            .pipe(Effect.flatMap(Effect.fromNullable));\n\n          const live = liveQuery(() =>\n            db.temp_workspace\n              .where(\"workspaceId\")\n              .equals(params.workspaceId)\n              .toArray()\n          );\n\n          yield* Effect.forkScoped(\n            Effect.acquireRelease(\n              Effect.gen(function* () {\n                yield* Effect.log(\"Subscribing\");\n\n                const ref = yield* SynchronizedRef.make(0);\n                return live.subscribe((payload) =>\n                  Effect.runPromise(\n                    Effect.gen(function* () {\n                      yield* Effect.log(`Change detected`);\n\n                      const id = yield* ref.pipe(\n                        SynchronizedRef.updateAndGet((n) => n + 1)\n                      );\n\n                      yield* Stream.runDrain(\n                        Stream.make(...payload).pipe(\n                          Stream.changesWith((a, b) =>\n                            snapshotEq(a.snapshot, b.snapshot)\n                          ),\n                          Stream.debounce(\"3 seconds\"),\n                          Stream.tap((message) =>\n                            Effect.gen(function* () {\n                              const streamId = yield* ref.get;\n                              if (streamId === id) {\n                                yield* Effect.log(\n                                  `Syncing ${payload.length} changes`\n                                );\n\n                                const snapshot = yield* Schema.decode(Snapshot)(\n                                  message.snapshot\n                                );\n\n                                yield* push({\n                                  snapshot,\n                                  snapshotId: message.snapshotId,\n                                  workspaceId: workspace.workspaceId,\n                                });\n                              }\n                            })\n                          )\n                        )\n                      );\n                    })\n                  )\n                );\n              }),\n              (subscription) =>\n                Effect.gen(function* () {\n                  yield* Effect.log(\"Live query unsubscribing\");\n                  return subscription.unsubscribe();\n                })\n            )\n          );\n\n          return true;\n        }),\n\n      bootstrap: (params: Bootstrap) =>\n        Effect.gen(function* () {\n          const { push, pull } = yield* Sync;\n\n          const manager = yield* WorkspaceManager;\n          const temp = yield* TempWorkspace;\n\n          yield* Effect.log(`Running workspace '${params.workspaceId}'`);\n\n          const workspace = yield* manager\n            .getById({ workspaceId: params.workspaceId })\n            .pipe(Effect.flatMap(Effect.fromNullable));\n\n          const tempUpdates = yield* temp.getById({\n            workspaceId: workspace.workspaceId,\n          });\n\n          if (tempUpdates !== undefined) {\n            yield* push({\n              workspaceId: workspace.workspaceId,\n              snapshot: tempUpdates.snapshot,\n              snapshotId: tempUpdates.snapshotId,\n            });\n            yield* Effect.log(\"Push sync completed\");\n          } else {\n            yield* pull({ workspaceId: workspace.workspaceId });\n            yield* Effect.log(\"Pull sync completed\");\n          }\n\n          return true;\n        }).pipe(\n          Effect.mapError(\n            (error) => `Bootstrap error: ${JSON.stringify(error)}`\n          )\n        ),\n    };\n  }),\n}) {}\n"
  },
  {
    "path": "packages/client-lib/src/use-action-effect.ts",
    "content": "import { Effect, type ManagedRuntime } from \"effect\";\nimport { useActionState } from \"react\";\n\nexport const useActionEffect = <Payload, A, E, R>(\n  runtime: ManagedRuntime.ManagedRuntime<R, never>,\n  effect: (payload: Payload) => Effect.Effect<A, E, R>\n) => {\n  return useActionState<\n    | { error: E; data: null }\n    | { error: null; data: A }\n    | { error: null; data: null },\n    Payload\n  >(\n    (_, payload) =>\n      runtime.runPromise(\n        effect(payload).pipe(\n          Effect.match({\n            onFailure: (error) => ({ error, data: null }),\n            onSuccess: (data) => ({ error: null, data }),\n          })\n        )\n      ),\n    { error: null, data: null }\n  );\n};\n"
  },
  {
    "path": "packages/client-lib/src/use-dexie-query.ts",
    "content": "import { useLiveQuery } from \"dexie-react-hooks\";\nimport { Data, Effect, Either, Function, Match, pipe } from \"effect\";\nimport { Dexie } from \"./services/dexie\";\n\nclass MissingData extends Data.TaggedError(\"MissingData\")<{}> {}\nclass DexieError extends Data.TaggedError(\"DexieError\")<{\n  reason: \"invalid-data\" | \"query-error\";\n  cause: unknown;\n}> {}\n\nexport const useDexieQuery = <A, I>(\n  query: (db: (typeof Dexie.Service)[\"db\"]) => Promise<I>,\n  deps: unknown[] = []\n) => {\n  const results = useLiveQuery(\n    () =>\n      Effect.runPromise(\n        Effect.gen(function* () {\n          const { db } = yield* Dexie;\n          return yield* Effect.tryPromise({\n            try: () => query(db),\n            catch: (cause) => new DexieError({ reason: \"query-error\", cause }),\n          });\n        }).pipe(Effect.either, Effect.provide(Dexie.Default))\n      ),\n    deps\n  );\n\n  return pipe(\n    results,\n    Either.fromNullable(() => new MissingData()),\n    Either.flatMap(Function.identity),\n    Either.match({\n      onLeft: (_) =>\n        Match.value(_).pipe(\n          Match.tagsExhaustive({\n            DexieError: (error) => ({\n              error,\n              loading: false as const,\n              data: undefined,\n            }),\n            MissingData: (_) => ({\n              loading: true as const,\n              data: undefined,\n              error: undefined,\n            }),\n          })\n        ),\n      onRight: (rows) => ({\n        data: rows,\n        loading: false as const,\n        error: undefined,\n      }),\n    })\n  );\n};\n"
  },
  {
    "path": "packages/client-lib/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"target\": \"es2022\",\n    \"allowJs\": true,\n    \"resolveJsonModule\": true,\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitOverride\": true,\n    \"module\": \"preserve\",\n    \"noEmit\": true,\n    \"lib\": [\"es2022\", \"DOM\", \"DOM.Iterable\"]\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/schema/package.json",
    "content": "{\n  \"name\": \"@local/schema\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"typecheck\": \"tsc\"\n  },\n  \"exports\": {\n    \".\": \"./src/main.ts\",\n    \"./migrations\": \"./src/migrations.ts\"\n  },\n  \"devDependencies\": {},\n  \"peerDependencies\": {\n    \"loro-crdt\": \"^1.4.2\",\n    \"effect\": \"^3.13.2\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "packages/schema/src/main.ts",
    "content": "import { Schema } from \"effect\";\nimport { LoroDoc, LoroList, LoroMap } from \"loro-crdt\";\nimport { AnyLoroDocSchema, Table, VersioningSchema } from \"./schema\";\nimport type { Version } from \"./versioning\";\n\nexport const VERSION = 1 satisfies Version;\nexport const CurrentSchema = VersioningSchema[VERSION];\n\nconst Metadata = Schema.Struct({ version: Schema.Number });\n\nexport type LoroSchema = {\n  metadata: LoroMap<Partial<typeof Metadata.Encoded>>;\n  food: LoroList<LoroMap<typeof CurrentSchema.fields.food.value.Encoded>>;\n  meal: LoroList<LoroMap<typeof CurrentSchema.fields.meal.value.Encoded>>;\n};\n\nexport class SnapshotSchema extends Schema.Class<SnapshotSchema>(\n  \"SnapshotSchema\"\n)({\n  metadata: Metadata,\n  ...CurrentSchema.fields,\n}) {\n  static readonly Table = Table;\n\n  static readonly EmptyDoc = () => {\n    const doc = new LoroDoc<LoroSchema>();\n    doc.getMap(\"metadata\").set(\"version\", VERSION);\n    Table.literals.map((key) => {\n      doc.getList(key);\n    });\n    return doc;\n  };\n}\n\nexport const SnapshotToLoroDoc = Schema.Uint8ArrayFromSelf.pipe(\n  Schema.transform(AnyLoroDocSchema, {\n    decode: (from) => {\n      const doc = new LoroDoc();\n      doc.import(from);\n      return doc;\n    },\n    encode: (to) => to.export({ mode: \"snapshot\" }),\n  })\n);\n"
  },
  {
    "path": "packages/schema/src/migrations.ts",
    "content": "import { ParseResult, Schema } from \"effect\";\nimport { LoroDoc } from \"loro-crdt\";\nimport { SnapshotSchema } from \"./main\";\nimport { AnyLoroDocSchema } from \"./schema\";\nimport { Version } from \"./versioning\";\n\nconst migrations = {\n  1: (_) => SnapshotSchema.EmptyDoc(),\n} satisfies Record<Version, (doc: LoroDoc) => LoroDoc>;\n\nexport const LoroDocMigration = AnyLoroDocSchema.pipe(\n  Schema.transformOrFail(AnyLoroDocSchema, {\n    decode: (from, _, ast) => {\n      const doc = new LoroDoc();\n      doc.import(from.export({ mode: \"snapshot\" }));\n      const currentVersion = doc.getMap(\"metadata\").get(\"version\");\n\n      if (typeof currentVersion === \"number\") {\n        Version.forEach((version) => {\n          doc.import(migrations[version](doc).export({ mode: \"snapshot\" }));\n        });\n      } else {\n        return ParseResult.fail(\n          new ParseResult.Type(ast, from, \"Invalid version number in metadata\")\n        );\n      }\n\n      return ParseResult.succeed(doc);\n    },\n\n    encode: (to, _, ast) =>\n      ParseResult.fail(\n        new ParseResult.Forbidden(\n          ast,\n          to,\n          \"Encoding LoroDoc migration is not allowed (should not happen...)\"\n        )\n      ),\n  })\n);\n"
  },
  {
    "path": "packages/schema/src/schema.ts",
    "content": "import { Schema } from \"effect\";\nimport { LoroDoc } from \"loro-crdt\";\nimport { type Version } from \"./versioning\";\n\nexport const AnyLoroDocSchema = Schema.instanceOf(LoroDoc);\n\nexport class FoodV1 extends Schema.Class<FoodV1>(\"FoodV1\")({\n  id: Schema.UUID,\n  name: Schema.String,\n  calories: Schema.Number.pipe(Schema.positive()),\n}) {}\n\nexport class MealV1 extends Schema.Class<MealV1>(\"MealV1\")({\n  id: Schema.UUID,\n  foodId: FoodV1.fields.id,\n  quantity: Schema.Number.pipe(Schema.positive()),\n}) {}\n\nexport const Table = Schema.Literal(\"food\", \"meal\");\nexport const VersioningSchema = {\n  1: Schema.Struct({\n    [Table.literals[0]]: Schema.Array(FoodV1),\n    [Table.literals[1]]: Schema.Array(MealV1),\n  }),\n} as const satisfies Record<Version, Schema.Schema.AnyNoContext>;\n"
  },
  {
    "path": "packages/schema/src/versioning.ts",
    "content": "export const Version = [1] as const;\nexport type Version = (typeof Version)[number];\n"
  },
  {
    "path": "packages/schema/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"target\": \"es2022\",\n    \"allowJs\": true,\n    \"resolveJsonModule\": true,\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitOverride\": true,\n    \"module\": \"preserve\",\n    \"noEmit\": true,\n    \"lib\": [\"es2022\"]\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/sync/package.json",
    "content": "{\n  \"name\": \"@local/sync\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"typecheck\": \"tsc\"\n  },\n  \"exports\": {\n    \".\": \"./src/main.ts\"\n  },\n  \"devDependencies\": {},\n  \"peerDependencies\": {\n    \"@effect/platform\": \"^0.77.2\",\n    \"effect\": \"^3.13.2\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "packages/sync/src/main.ts",
    "content": "import {\n  HttpApi,\n  HttpApiEndpoint,\n  HttpApiGroup,\n  HttpApiMiddleware,\n  HttpApiSchema,\n  HttpApiSecurity,\n} from \"@effect/platform\";\nimport { Context, Schema } from \"effect\";\n\nexport class Unauthorized extends Schema.TaggedError<Unauthorized>()(\n  \"Unauthorized\",\n  { message: Schema.String },\n  HttpApiSchema.annotations({ status: 401 })\n) {}\n\nexport class MissingWorkspace extends Schema.TaggedError<MissingWorkspace>()(\n  \"MissingWorkspace\",\n  {},\n  HttpApiSchema.annotations({ status: 404 })\n) {}\n\nexport class DatabaseError extends Schema.TaggedError<DatabaseError>()(\n  \"DatabaseError\",\n  {},\n  HttpApiSchema.annotations({ status: 500 })\n) {}\n\nexport class VersionError extends Schema.TaggedError<VersionError>()(\n  \"VersionError\",\n  {\n    reason: Schema.Literal(\n      \"missing-snapshot\",\n      \"invalid-doc\",\n      \"missing-version\",\n      \"outdated-version\"\n    ),\n  },\n  HttpApiSchema.annotations({ status: 400 })\n) {}\n\nexport const ClientId = Schema.UUID;\nexport const WorkspaceId = Schema.UUID;\n\nexport const Snapshot = Schema.Uint8Array;\nexport const SnapshotId = Schema.UUID;\nexport const Scope = Schema.Literal(\"read\", \"read_write\");\n\nexport class ClientTable extends Schema.Class<ClientTable>(\"ClientTable\")({\n  clientId: ClientId,\n  createdAt: Schema.DateFromString,\n}) {}\n\nexport class WorkspaceTable extends Schema.Class<WorkspaceTable>(\n  \"WorkspaceTable\"\n)({\n  workspaceId: WorkspaceId,\n  ownerClientId: ClientId,\n  createdAt: Schema.DateFromString,\n  clientId: ClientId,\n  snapshotId: SnapshotId,\n  snapshot: Snapshot,\n}) {}\n\nexport class TokenTable extends Schema.Class<TokenTable>(\"TokenTable\")({\n  tokenId: Schema.Number,\n  tokenValue: Schema.String,\n  clientId: ClientId,\n  workspaceId: WorkspaceId,\n  isMaster: Schema.Boolean,\n  scope: Scope,\n  issuedAt: Schema.DateFromString,\n  expiresAt: Schema.NullOr(Schema.DateFromString),\n  revokedAt: Schema.NullOr(Schema.DateFromString),\n}) {}\n\nexport class AuthWorkspace extends Context.Tag(\"AuthWorkspace\")<\n  AuthWorkspace,\n  WorkspaceTable\n>() {}\n\nexport class ValidDoc extends Context.Tag(\"ValidDoc\")<\n  ValidDoc,\n  typeof Snapshot.Type\n>() {}\n\nconst authKey = \"x-api-key\";\nexport const ApiKey = HttpApiSecurity.apiKey({\n  in: \"header\",\n  key: authKey,\n});\n\nconst ApiKeyHeader = Schema.Struct({\n  [authKey]: Schema.String,\n});\n\nexport class VersionCheck extends HttpApiMiddleware.Tag<VersionCheck>()(\n  \"VersionCheck\",\n  {\n    failure: VersionError,\n    provides: ValidDoc,\n  }\n) {}\n\nexport class Authorization extends HttpApiMiddleware.Tag<Authorization>()(\n  \"Authorization\",\n  {\n    failure: Schema.Union(Unauthorized, MissingWorkspace, DatabaseError),\n    provides: AuthWorkspace,\n    security: { apiKey: ApiKey },\n  }\n) {}\n\nexport class MasterAuthorization extends HttpApiMiddleware.Tag<MasterAuthorization>()(\n  \"MasterAuthorization\",\n  {\n    failure: Schema.Union(Unauthorized, MissingWorkspace, DatabaseError),\n    provides: AuthWorkspace,\n    security: { apiKey: ApiKey },\n  }\n) {}\n\nexport class SyncAuthGroup extends HttpApiGroup.make(\"syncAuth\")\n  .add(\n    /**\n     Allows a client to create a new workshop and upload its initial data to the server. The server marks the client as the owner and issues a master token for full control.\n     */\n    HttpApiEndpoint.post(\"generateToken\")`/`\n      .setPayload(\n        Schema.Struct({\n          clientId: ClientId,\n          workspaceId: WorkspaceId,\n          snapshotId: SnapshotId,\n          snapshot: WorkspaceTable.fields.snapshot,\n        })\n      )\n      .addError(Schema.String)\n      .addSuccess(\n        Schema.Struct({\n          token: Schema.String,\n          workspaceId: WorkspaceTable.fields.workspaceId,\n          createdAt: WorkspaceTable.fields.createdAt,\n          snapshot: WorkspaceTable.fields.snapshot,\n        })\n      )\n      .middleware(VersionCheck)\n  )\n  .add(\n    /**\n     Allows the owner (via master token) to generate an access token for another client, specifying permissions and expiration. The owner shares this token with the client securely.\n     */\n    HttpApiEndpoint.post(\n      \"issueToken\"\n    )`/${HttpApiSchema.param(\"workspaceId\", Schema.UUID)}/token`\n      .setPayload(\n        Schema.Struct({\n          clientId: ClientId,\n          scope: Scope,\n          expiresIn: Schema.Duration,\n        })\n      )\n      .addError(Schema.String)\n      .addSuccess(\n        Schema.Struct({\n          token: Schema.String,\n          scope: Scope,\n          expiresAt: Schema.DateFromString,\n        })\n      )\n      .setHeaders(ApiKeyHeader)\n      .middleware(MasterAuthorization)\n  )\n  .add(\n    /**\n     Lets the owner revoke access for a specific client by invalidating their access token. Requires the master token and targets the `clientId` tied to the token.\n     */\n    HttpApiEndpoint.del(\n      \"revokeToken\"\n    )`/${HttpApiSchema.param(\"workspaceId\", Schema.UUID)}/token/${HttpApiSchema.param(\"clientId\", Schema.UUID)}`\n      .addError(Schema.String)\n      .addSuccess(Schema.Boolean)\n      .setHeaders(ApiKeyHeader)\n      .middleware(MasterAuthorization)\n  )\n  .add(\n    /**\n     Provides the owner with a list of all active tokens (master and access) for a workshop, showing their status. Useful for managing access.\n     */\n    HttpApiEndpoint.get(\n      \"listTokens\"\n    )`/${HttpApiSchema.param(\"workspaceId\", Schema.UUID)}/tokens`\n      .addError(Schema.String)\n      .addSuccess(\n        Schema.Array(\n          TokenTable.pipe(\n            Schema.pick(\n              \"clientId\",\n              \"tokenValue\",\n              \"scope\",\n              \"isMaster\",\n              \"issuedAt\",\n              \"expiresAt\",\n              \"revokedAt\"\n            )\n          )\n        )\n      )\n      .setHeaders(ApiKeyHeader)\n      .middleware(MasterAuthorization)\n  )\n  .prefix(\"/workspaces\") {}\n\nexport class SyncDataGroup extends HttpApiGroup.make(\"syncData\")\n  .add(\n    /**\n     Updates the workshop data on the server with changes from a client. Requires a valid token with `read_write` scope.\n     */\n    HttpApiEndpoint.put(\n      \"push\"\n    )`/${HttpApiSchema.param(\"workspaceId\", Schema.UUID)}/push`\n      .setPayload(WorkspaceTable.pipe(Schema.pick(\"snapshot\", \"snapshotId\")))\n      .addError(Schema.String)\n      .addSuccess(\n        WorkspaceTable.pipe(Schema.pick(\"workspaceId\", \"createdAt\", \"snapshot\"))\n      )\n      .setHeaders(ApiKeyHeader)\n      .middleware(Authorization)\n      .middleware(VersionCheck)\n  )\n  .add(\n    /**\n     Retrieves the current workshop data for a client (owner or authorized user). Requires a valid token (master or access) with at least `read` scope. Used for initial download or sync verification.\n     */\n    HttpApiEndpoint.get(\n      \"pull\"\n    )`/${HttpApiSchema.param(\"workspaceId\", Schema.UUID)}/pull`\n      .addError(Schema.String)\n      .addSuccess(Schema.Struct({ snapshot: WorkspaceTable.fields.snapshot }))\n      .setHeaders(ApiKeyHeader)\n      .middleware(Authorization)\n  )\n  .add(\n    /**\n     Client opens link to join another workspace. The server issues a token for the workspace and returns the workspace data.\n     */\n    HttpApiEndpoint.get(\n      \"join\"\n    )`/${HttpApiSchema.param(\"workspaceId\", Schema.UUID)}/join/${HttpApiSchema.param(\"clientId\", Schema.UUID)}`\n      .addError(Schema.String)\n      .addSuccess(\n        Schema.Struct({\n          snapshot: WorkspaceTable.fields.snapshot,\n          token: TokenTable.fields.tokenValue,\n        })\n      )\n  )\n  .prefix(\"/workspaces\") {}\n\nexport class SyncApi extends HttpApi.make(\"SyncApi\")\n  .add(SyncAuthGroup)\n  .add(SyncDataGroup) {}\n"
  },
  {
    "path": "packages/sync/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"target\": \"es2022\",\n    \"allowJs\": true,\n    \"resolveJsonModule\": true,\n    \"moduleDetection\": \"force\",\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitOverride\": true,\n    \"module\": \"preserve\",\n    \"noEmit\": true,\n    \"lib\": [\"es2022\"]\n  },\n  \"include\": [\"**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - \"apps/*\"\n  - \"packages/*\"\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"ui\": \"tui\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"inputs\": [\"$TURBO_DEFAULT$\", \".env*\"],\n      \"outputs\": [\".next/**\", \"!.next/cache/**\"]\n    },\n    \"typecheck\": {\n      \"dependsOn\": [\"^typecheck\"]\n    },\n    \"generate\": {\n      \"dependsOn\": [\"^generate\"]\n    },\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    }\n  }\n}\n"
  }
]