[READ-ONLY] Mirror of https://github.com/flo-bit/contrail. atproto backend in a bottle flo-bit.dev/contrail/
0

Configure Feed

Select the types of activity you want to include in your feed.

add contrail-e2e app with end-to-end tests against a local ATProto stack

New app under apps/contrail-e2e that brings up a full sealed local
ATProto network (PDS, PLC, Jetstream, TAP, Postgres, maildev) via
docker-compose and runs three end-to-end test files against it:

- health.test.ts — service health checks
- ingest-roundtrip.test.ts — publish → index roundtrip, verifying
a calendar event and an RSVP land in the DB and that the grouped
rsvpsGoingCount is populated via an in-process XRPC handler
- cursor-resume.test.ts — regression for runPersistent's durable
cursor. Publishes A, stops the ingester, publishes B + updates B
+ deletes A, restarts, and asserts the saved cursor replays the
gap. The basic roundtrip can't catch a broken cursor because
events always arrive while the ingester is live.

Each test stands up its own runPersistent and XRPC handler
in-process against an isolated pg schema (search_path pinned per
test), so the suite works with just pnpm stack:up and doesn't
fight a dogfooding ingester writing to public.

Tom Scanlan (Apr 24, 2026, 5:53 PM EDT) 2cd5ea3b 62b53642

+966 -66
+132 -66
pnpm-lock.yaml
··· 46 46 specifier: ^4.63.0 47 47 version: 4.84.1(@cloudflare/workers-types@4.20260424.1) 48 48 49 + apps/contrail-e2e: 50 + dependencies: 51 + '@atmo-dev/contrail': 52 + specifier: workspace:* 53 + version: link:../../packages/contrail 54 + pg: 55 + specifier: ^8.20.0 56 + version: 8.20.0 57 + devDependencies: 58 + '@atcute/atproto': 59 + specifier: ^3.1.10 60 + version: 3.1.11 61 + '@atcute/client': 62 + specifier: ^4.2.1 63 + version: 4.2.1 64 + '@types/pg': 65 + specifier: ^8.20.0 66 + version: 8.20.0 67 + typescript: 68 + specifier: ^5.9.3 69 + version: 5.9.3 70 + vitest: 71 + specifier: ^4.1.0 72 + version: 4.1.5(@types/node@25.6.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 73 + 49 74 apps/group-chat: 50 75 dependencies: 51 76 '@atcute/jetstream': ··· 62 87 version: link:../../packages/sync 63 88 '@foxui/core': 64 89 specifier: ^0.9.1 65 - version: 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 90 + version: 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 66 91 '@foxui/social': 67 92 specifier: ^0.8.10 68 - version: 0.8.10(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 93 + version: 0.8.10(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 69 94 '@foxui/time': 70 95 specifier: ^0.8.5 71 - version: 0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 96 + version: 0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 72 97 valibot: 73 98 specifier: ^1.3.1 74 99 version: 1.3.1(typescript@6.0.3) ··· 111 136 version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) 112 137 '@sveltejs/adapter-cloudflare': 113 138 specifier: ^7.2.8 114 - version: 7.2.8(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(wrangler@4.84.1(@cloudflare/workers-types@4.20260424.1)) 139 + version: 7.2.8(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(wrangler@4.84.1(@cloudflare/workers-types@4.20260424.1)) 115 140 '@sveltejs/kit': 116 141 specifier: ^2.55.0 117 - version: 2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 142 + version: 2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 118 143 '@sveltejs/vite-plugin-svelte': 119 144 specifier: ^7.0.0 120 - version: 7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 145 + version: 7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 121 146 '@tailwindcss/forms': 122 147 specifier: ^0.5.11 123 148 version: 0.5.11(tailwindcss@4.2.4) 124 149 '@tailwindcss/vite': 125 150 specifier: ^4.2.2 126 - version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 151 + version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 127 152 bits-ui: 128 153 specifier: ^2.16.4 129 - version: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 154 + version: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 130 155 eslint: 131 156 specifier: ^10.1.0 132 157 version: 10.2.1(jiti@2.6.1) ··· 168 193 version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) 169 194 vite: 170 195 specifier: ^8.0.3 171 - version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3) 196 + version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) 172 197 wrangler: 173 198 specifier: ^4.78.0 174 199 version: 4.84.1(@cloudflare/workers-types@4.20260424.1) ··· 245 270 version: link:../../packages/lexicons 246 271 '@foxui/core': 247 272 specifier: ^0.9.1 248 - version: 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 273 + version: 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 249 274 '@foxui/social': 250 275 specifier: ^0.8.10 251 - version: 0.8.10(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 276 + version: 0.8.10(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 252 277 '@foxui/time': 253 278 specifier: ^0.8.5 254 - version: 0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 279 + version: 0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 255 280 valibot: 256 281 specifier: ^1.3.1 257 282 version: 1.3.1(typescript@6.0.3) ··· 294 319 version: 10.0.1(eslint@10.2.1(jiti@2.6.1)) 295 320 '@sveltejs/adapter-cloudflare': 296 321 specifier: ^7.2.8 297 - version: 7.2.8(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(wrangler@4.84.1(@cloudflare/workers-types@4.20260424.1)) 322 + version: 7.2.8(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(wrangler@4.84.1(@cloudflare/workers-types@4.20260424.1)) 298 323 '@sveltejs/kit': 299 324 specifier: ^2.55.0 300 - version: 2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 325 + version: 2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 301 326 '@sveltejs/vite-plugin-svelte': 302 327 specifier: ^7.0.0 303 - version: 7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 328 + version: 7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 304 329 '@tailwindcss/forms': 305 330 specifier: ^0.5.11 306 331 version: 0.5.11(tailwindcss@4.2.4) 307 332 '@tailwindcss/vite': 308 333 specifier: ^4.2.2 309 - version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 334 + version: 4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 310 335 bits-ui: 311 336 specifier: ^2.16.4 312 - version: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 337 + version: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 313 338 eslint: 314 339 specifier: ^10.1.0 315 340 version: 10.2.1(jiti@2.6.1) ··· 351 376 version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) 352 377 vite: 353 378 specifier: ^8.0.3 354 - version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3) 379 + version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) 355 380 wrangler: 356 381 specifier: ^4.78.0 357 382 version: 4.84.1(@cloudflare/workers-types@4.20260424.1) ··· 400 425 version: 8.20.0 401 426 tsup: 402 427 specifier: ^8.5.0 403 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@1.10.3) 428 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3) 404 429 typescript: 405 430 specifier: ^5.7.3 406 431 version: 5.9.3 407 432 vitest: 408 433 specifier: ^4.1.0 409 - version: 4.1.5(@types/node@25.6.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 434 + version: 4.1.5(@types/node@25.6.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 410 435 411 436 packages/lexicons: 412 437 dependencies: ··· 422 447 version: 25.6.0 423 448 tsup: 424 449 specifier: ^8.5.0 425 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@1.10.3) 450 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3) 426 451 typescript: 427 452 specifier: ^5.7.3 428 453 version: 5.9.3 429 454 vitest: 430 455 specifier: ^4.1.0 431 - version: 4.1.5(@types/node@25.6.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 456 + version: 4.1.5(@types/node@25.6.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 432 457 433 458 packages/sync: 434 459 devDependencies: 435 460 tsup: 436 461 specifier: ^8.5.0 437 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@1.10.3) 462 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3) 438 463 typescript: 439 464 specifier: ^5.7.3 440 465 version: 5.9.3 ··· 1105 1130 resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} 1106 1131 cpu: [arm64] 1107 1132 os: [linux] 1133 + libc: [glibc] 1108 1134 1109 1135 '@img/sharp-libvips-linux-arm@1.2.4': 1110 1136 resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} 1111 1137 cpu: [arm] 1112 1138 os: [linux] 1139 + libc: [glibc] 1113 1140 1114 1141 '@img/sharp-libvips-linux-ppc64@1.2.4': 1115 1142 resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} 1116 1143 cpu: [ppc64] 1117 1144 os: [linux] 1145 + libc: [glibc] 1118 1146 1119 1147 '@img/sharp-libvips-linux-riscv64@1.2.4': 1120 1148 resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} 1121 1149 cpu: [riscv64] 1122 1150 os: [linux] 1151 + libc: [glibc] 1123 1152 1124 1153 '@img/sharp-libvips-linux-s390x@1.2.4': 1125 1154 resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} 1126 1155 cpu: [s390x] 1127 1156 os: [linux] 1157 + libc: [glibc] 1128 1158 1129 1159 '@img/sharp-libvips-linux-x64@1.2.4': 1130 1160 resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} 1131 1161 cpu: [x64] 1132 1162 os: [linux] 1163 + libc: [glibc] 1133 1164 1134 1165 '@img/sharp-libvips-linuxmusl-arm64@1.2.4': 1135 1166 resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} 1136 1167 cpu: [arm64] 1137 1168 os: [linux] 1169 + libc: [musl] 1138 1170 1139 1171 '@img/sharp-libvips-linuxmusl-x64@1.2.4': 1140 1172 resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} 1141 1173 cpu: [x64] 1142 1174 os: [linux] 1175 + libc: [musl] 1143 1176 1144 1177 '@img/sharp-linux-arm64@0.34.5': 1145 1178 resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} 1146 1179 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1147 1180 cpu: [arm64] 1148 1181 os: [linux] 1182 + libc: [glibc] 1149 1183 1150 1184 '@img/sharp-linux-arm@0.34.5': 1151 1185 resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} 1152 1186 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1153 1187 cpu: [arm] 1154 1188 os: [linux] 1189 + libc: [glibc] 1155 1190 1156 1191 '@img/sharp-linux-ppc64@0.34.5': 1157 1192 resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} 1158 1193 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1159 1194 cpu: [ppc64] 1160 1195 os: [linux] 1196 + libc: [glibc] 1161 1197 1162 1198 '@img/sharp-linux-riscv64@0.34.5': 1163 1199 resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} 1164 1200 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1165 1201 cpu: [riscv64] 1166 1202 os: [linux] 1203 + libc: [glibc] 1167 1204 1168 1205 '@img/sharp-linux-s390x@0.34.5': 1169 1206 resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} 1170 1207 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1171 1208 cpu: [s390x] 1172 1209 os: [linux] 1210 + libc: [glibc] 1173 1211 1174 1212 '@img/sharp-linux-x64@0.34.5': 1175 1213 resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} 1176 1214 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1177 1215 cpu: [x64] 1178 1216 os: [linux] 1217 + libc: [glibc] 1179 1218 1180 1219 '@img/sharp-linuxmusl-arm64@0.34.5': 1181 1220 resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} 1182 1221 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1183 1222 cpu: [arm64] 1184 1223 os: [linux] 1224 + libc: [musl] 1185 1225 1186 1226 '@img/sharp-linuxmusl-x64@0.34.5': 1187 1227 resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} 1188 1228 engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1189 1229 cpu: [x64] 1190 1230 os: [linux] 1231 + libc: [musl] 1191 1232 1192 1233 '@img/sharp-wasm32@0.34.5': 1193 1234 resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} ··· 1349 1390 engines: {node: ^20.19.0 || >=22.12.0} 1350 1391 cpu: [arm64] 1351 1392 os: [linux] 1393 + libc: [glibc] 1352 1394 1353 1395 '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': 1354 1396 resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} 1355 1397 engines: {node: ^20.19.0 || >=22.12.0} 1356 1398 cpu: [arm64] 1357 1399 os: [linux] 1400 + libc: [musl] 1358 1401 1359 1402 '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': 1360 1403 resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} 1361 1404 engines: {node: ^20.19.0 || >=22.12.0} 1362 1405 cpu: [ppc64] 1363 1406 os: [linux] 1407 + libc: [glibc] 1364 1408 1365 1409 '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': 1366 1410 resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} 1367 1411 engines: {node: ^20.19.0 || >=22.12.0} 1368 1412 cpu: [s390x] 1369 1413 os: [linux] 1414 + libc: [glibc] 1370 1415 1371 1416 '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': 1372 1417 resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} 1373 1418 engines: {node: ^20.19.0 || >=22.12.0} 1374 1419 cpu: [x64] 1375 1420 os: [linux] 1421 + libc: [glibc] 1376 1422 1377 1423 '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': 1378 1424 resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} 1379 1425 engines: {node: ^20.19.0 || >=22.12.0} 1380 1426 cpu: [x64] 1381 1427 os: [linux] 1428 + libc: [musl] 1382 1429 1383 1430 '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': 1384 1431 resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} ··· 1440 1487 resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} 1441 1488 cpu: [arm] 1442 1489 os: [linux] 1490 + libc: [glibc] 1443 1491 1444 1492 '@rollup/rollup-linux-arm-musleabihf@4.60.2': 1445 1493 resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} 1446 1494 cpu: [arm] 1447 1495 os: [linux] 1496 + libc: [musl] 1448 1497 1449 1498 '@rollup/rollup-linux-arm64-gnu@4.60.2': 1450 1499 resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} 1451 1500 cpu: [arm64] 1452 1501 os: [linux] 1502 + libc: [glibc] 1453 1503 1454 1504 '@rollup/rollup-linux-arm64-musl@4.60.2': 1455 1505 resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} 1456 1506 cpu: [arm64] 1457 1507 os: [linux] 1508 + libc: [musl] 1458 1509 1459 1510 '@rollup/rollup-linux-loong64-gnu@4.60.2': 1460 1511 resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} 1461 1512 cpu: [loong64] 1462 1513 os: [linux] 1514 + libc: [glibc] 1463 1515 1464 1516 '@rollup/rollup-linux-loong64-musl@4.60.2': 1465 1517 resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} 1466 1518 cpu: [loong64] 1467 1519 os: [linux] 1520 + libc: [musl] 1468 1521 1469 1522 '@rollup/rollup-linux-ppc64-gnu@4.60.2': 1470 1523 resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} 1471 1524 cpu: [ppc64] 1472 1525 os: [linux] 1526 + libc: [glibc] 1473 1527 1474 1528 '@rollup/rollup-linux-ppc64-musl@4.60.2': 1475 1529 resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} 1476 1530 cpu: [ppc64] 1477 1531 os: [linux] 1532 + libc: [musl] 1478 1533 1479 1534 '@rollup/rollup-linux-riscv64-gnu@4.60.2': 1480 1535 resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} 1481 1536 cpu: [riscv64] 1482 1537 os: [linux] 1538 + libc: [glibc] 1483 1539 1484 1540 '@rollup/rollup-linux-riscv64-musl@4.60.2': 1485 1541 resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} 1486 1542 cpu: [riscv64] 1487 1543 os: [linux] 1544 + libc: [musl] 1488 1545 1489 1546 '@rollup/rollup-linux-s390x-gnu@4.60.2': 1490 1547 resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} 1491 1548 cpu: [s390x] 1492 1549 os: [linux] 1550 + libc: [glibc] 1493 1551 1494 1552 '@rollup/rollup-linux-x64-gnu@4.60.2': 1495 1553 resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} 1496 1554 cpu: [x64] 1497 1555 os: [linux] 1556 + libc: [glibc] 1498 1557 1499 1558 '@rollup/rollup-linux-x64-musl@4.60.2': 1500 1559 resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} 1501 1560 cpu: [x64] 1502 1561 os: [linux] 1562 + libc: [musl] 1503 1563 1504 1564 '@rollup/rollup-openbsd-x64@4.60.2': 1505 1565 resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} ··· 1621 1681 engines: {node: '>= 20'} 1622 1682 cpu: [arm64] 1623 1683 os: [linux] 1684 + libc: [glibc] 1624 1685 1625 1686 '@tailwindcss/oxide-linux-arm64-musl@4.2.4': 1626 1687 resolution: {integrity: sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==} 1627 1688 engines: {node: '>= 20'} 1628 1689 cpu: [arm64] 1629 1690 os: [linux] 1691 + libc: [musl] 1630 1692 1631 1693 '@tailwindcss/oxide-linux-x64-gnu@4.2.4': 1632 1694 resolution: {integrity: sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==} 1633 1695 engines: {node: '>= 20'} 1634 1696 cpu: [x64] 1635 1697 os: [linux] 1698 + libc: [glibc] 1636 1699 1637 1700 '@tailwindcss/oxide-linux-x64-musl@4.2.4': 1638 1701 resolution: {integrity: sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==} 1639 1702 engines: {node: '>= 20'} 1640 1703 cpu: [x64] 1641 1704 os: [linux] 1705 + libc: [musl] 1642 1706 1643 1707 '@tailwindcss/oxide-wasm32-wasi@4.2.4': 1644 1708 resolution: {integrity: sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==} ··· 2551 2615 engines: {node: '>= 12.0.0'} 2552 2616 cpu: [arm64] 2553 2617 os: [linux] 2618 + libc: [glibc] 2554 2619 2555 2620 lightningcss-linux-arm64-musl@1.32.0: 2556 2621 resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} 2557 2622 engines: {node: '>= 12.0.0'} 2558 2623 cpu: [arm64] 2559 2624 os: [linux] 2625 + libc: [musl] 2560 2626 2561 2627 lightningcss-linux-x64-gnu@1.32.0: 2562 2628 resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} 2563 2629 engines: {node: '>= 12.0.0'} 2564 2630 cpu: [x64] 2565 2631 os: [linux] 2632 + libc: [glibc] 2566 2633 2567 2634 lightningcss-linux-x64-musl@1.32.0: 2568 2635 resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} 2569 2636 engines: {node: '>= 12.0.0'} 2570 2637 cpu: [x64] 2571 2638 os: [linux] 2639 + libc: [musl] 2572 2640 2573 2641 lightningcss-win32-arm64-msvc@1.32.0: 2574 2642 resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} ··· 4152 4220 4153 4221 '@fontsource-variable/geist@5.2.8': {} 4154 4222 4155 - '@foxui/core@0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4)': 4223 + '@foxui/core@0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4)': 4156 4224 dependencies: 4157 4225 '@fontsource-variable/geist': 5.2.8 4158 4226 '@fontsource-variable/geist-mono': 5.2.7 4159 4227 '@jis3r/icons': 2.7.0(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4160 4228 '@lucide/svelte': 1.9.0(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4161 4229 '@number-flow/svelte': 0.4.0(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4162 - bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4230 + bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4163 4231 clsx: 2.1.1 4164 4232 dompurify: 3.4.1 4165 4233 mode-watcher: 1.1.0(svelte@5.55.5(@typescript-eslint/types@8.59.0)) ··· 4176 4244 - react-dom 4177 4245 - vue 4178 4246 4179 - '@foxui/social@0.8.10(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4)': 4247 + '@foxui/social@0.8.10(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4)': 4180 4248 dependencies: 4181 4249 '@atcute/bluesky': 3.3.3 4182 4250 '@atcute/bluesky-richtext-segmenter': 3.0.0 4183 - '@foxui/core': 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4184 - '@foxui/text': 0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4185 - '@foxui/time': 0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4251 + '@foxui/core': 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4252 + '@foxui/text': 0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4253 + '@foxui/time': 0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4186 4254 '@use-gesture/vanilla': 10.3.1 4187 - bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4255 + bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4188 4256 emoji-picker-element: 1.29.1 4189 4257 hls.js: 1.6.16 4190 4258 is-emoji-supported: 0.0.5 ··· 4201 4269 - react-dom 4202 4270 - vue 4203 4271 4204 - '@foxui/text@0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4)': 4272 + '@foxui/text@0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/extensions@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(highlight.js@11.11.1)(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4)': 4205 4273 dependencies: 4206 4274 '@floating-ui/dom': 1.7.6 4207 - '@foxui/core': 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4275 + '@foxui/core': 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4208 4276 '@tiptap/core': 3.22.4(@tiptap/pm@3.22.4) 4209 4277 '@tiptap/extension-bubble-menu': 3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4) 4210 4278 '@tiptap/extension-code-block-lowlight': 3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/extension-code-block@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4)(highlight.js@11.11.1)(lowlight@3.3.0) ··· 4224 4292 '@tiptap/pm': 3.22.4 4225 4293 '@tiptap/starter-kit': 3.22.4 4226 4294 '@tiptap/suggestion': 3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.22.4))(@tiptap/pm@3.22.4) 4227 - bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4295 + bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4228 4296 lowlight: 3.3.0 4229 4297 state: link:@tiptap/pm/state 4230 4298 svelte: 5.55.5(@typescript-eslint/types@8.59.0) ··· 4240 4308 - react-dom 4241 4309 - vue 4242 4310 4243 - '@foxui/time@0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4)': 4311 + '@foxui/time@0.8.5(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4)': 4244 4312 dependencies: 4245 - '@foxui/core': 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4313 + '@foxui/core': 0.9.1(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(tailwindcss@4.2.4) 4246 4314 '@number-flow/svelte': 0.4.0(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4247 - bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4315 + bits-ui: 2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 4248 4316 svelte: 5.55.5(@typescript-eslint/types@8.59.0) 4249 4317 tailwindcss: 4.2.4 4250 4318 transitivePeerDependencies: ··· 4616 4684 dependencies: 4617 4685 acorn: 8.16.0 4618 4686 4619 - '@sveltejs/adapter-cloudflare@7.2.8(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(wrangler@4.84.1(@cloudflare/workers-types@4.20260424.1))': 4687 + '@sveltejs/adapter-cloudflare@7.2.8(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(wrangler@4.84.1(@cloudflare/workers-types@4.20260424.1))': 4620 4688 dependencies: 4621 4689 '@cloudflare/workers-types': 4.20260424.1 4622 - '@sveltejs/kit': 2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 4690 + '@sveltejs/kit': 2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 4623 4691 worktop: 0.8.0-next.18 4624 4692 wrangler: 4.84.1(@cloudflare/workers-types@4.20260424.1) 4625 4693 4626 - '@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3))': 4694 + '@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))': 4627 4695 dependencies: 4628 4696 '@standard-schema/spec': 1.1.0 4629 4697 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) 4630 - '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 4698 + '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 4631 4699 '@types/cookie': 0.6.0 4632 4700 acorn: 8.16.0 4633 4701 cookie: 0.6.0 ··· 4639 4707 set-cookie-parser: 3.1.0 4640 4708 sirv: 3.0.2 4641 4709 svelte: 5.55.5(@typescript-eslint/types@8.59.0) 4642 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3) 4710 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) 4643 4711 optionalDependencies: 4644 4712 typescript: 6.0.3 4645 4713 4646 - '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3))': 4714 + '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))': 4647 4715 dependencies: 4648 4716 deepmerge: 4.3.1 4649 4717 magic-string: 0.30.21 4650 4718 obug: 2.1.1 4651 4719 svelte: 5.55.5(@typescript-eslint/types@8.59.0) 4652 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3) 4653 - vitefu: 1.1.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 4720 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) 4721 + vitefu: 1.1.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 4654 4722 4655 4723 '@swc/helpers@0.5.21': 4656 4724 dependencies: ··· 4722 4790 '@tailwindcss/oxide-win32-arm64-msvc': 4.2.4 4723 4791 '@tailwindcss/oxide-win32-x64-msvc': 4.2.4 4724 4792 4725 - '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3))': 4793 + '@tailwindcss/vite@4.2.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))': 4726 4794 dependencies: 4727 4795 '@tailwindcss/node': 4.2.4 4728 4796 '@tailwindcss/oxide': 4.2.4 4729 4797 tailwindcss: 4.2.4 4730 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3) 4798 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) 4731 4799 4732 4800 '@tiptap/core@3.22.4(@tiptap/pm@3.22.4)': 4733 4801 dependencies: ··· 5080 5148 chai: 6.2.2 5081 5149 tinyrainbow: 3.1.0 5082 5150 5083 - '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3))': 5151 + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))': 5084 5152 dependencies: 5085 5153 '@vitest/spy': 4.1.5 5086 5154 estree-walker: 3.0.3 5087 5155 magic-string: 0.30.21 5088 5156 optionalDependencies: 5089 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3) 5157 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) 5090 5158 5091 5159 '@vitest/pretty-format@4.1.5': 5092 5160 dependencies: ··· 5151 5219 dependencies: 5152 5220 is-windows: 1.0.2 5153 5221 5154 - bits-ui@2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)): 5222 + bits-ui@2.18.0(@internationalized/date@3.12.1)(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)): 5155 5223 dependencies: 5156 5224 '@floating-ui/core': 1.7.5 5157 5225 '@floating-ui/dom': 1.7.6 5158 5226 '@internationalized/date': 3.12.1 5159 5227 esm-env: 1.2.2 5160 - runed: 0.35.1(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 5228 + runed: 0.35.1(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 5161 5229 svelte: 5.55.5(@typescript-eslint/types@8.59.0) 5162 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 5230 + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 5163 5231 tabbable: 6.4.0 5164 5232 transitivePeerDependencies: 5165 5233 - '@sveltejs/kit' ··· 5885 5953 optionalDependencies: 5886 5954 postcss: 8.5.10 5887 5955 5888 - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(yaml@1.10.3): 5956 + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0): 5889 5957 dependencies: 5890 5958 lilconfig: 3.1.3 5891 5959 optionalDependencies: 5892 5960 jiti: 2.6.1 5893 5961 postcss: 8.5.10 5894 5962 tsx: 4.21.0 5895 - yaml: 1.10.3 5896 5963 5897 5964 postcss-safe-parser@7.0.1(postcss@8.5.10): 5898 5965 dependencies: ··· 6107 6174 esm-env: 1.2.2 6108 6175 svelte: 5.55.5(@typescript-eslint/types@8.59.0) 6109 6176 6110 - runed@0.35.1(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)): 6177 + runed@0.35.1(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)): 6111 6178 dependencies: 6112 6179 dequal: 2.0.3 6113 6180 esm-env: 1.2.2 6114 6181 lz-string: 1.5.0 6115 6182 svelte: 5.55.5(@typescript-eslint/types@8.59.0) 6116 6183 optionalDependencies: 6117 - '@sveltejs/kit': 2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 6184 + '@sveltejs/kit': 2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 6118 6185 6119 6186 sade@1.8.1: 6120 6187 dependencies: ··· 6252 6319 '@tiptap/pm': 3.22.4 6253 6320 svelte: 5.55.5(@typescript-eslint/types@8.59.0) 6254 6321 6255 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)): 6322 + svelte-toolbelt@0.10.6(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)): 6256 6323 dependencies: 6257 6324 clsx: 2.1.1 6258 - runed: 0.35.1(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 6325 + runed: 0.35.1(@sveltejs/kit@2.58.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.5(@typescript-eslint/types@8.59.0))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0))(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.55.5(@typescript-eslint/types@8.59.0)) 6259 6326 style-to-object: 1.0.14 6260 6327 svelte: 5.55.5(@typescript-eslint/types@8.59.0) 6261 6328 transitivePeerDependencies: ··· 6342 6409 6343 6410 tslib@2.8.1: {} 6344 6411 6345 - tsup@8.5.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3)(yaml@1.10.3): 6412 + tsup@8.5.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(typescript@5.9.3): 6346 6413 dependencies: 6347 6414 bundle-require: 5.1.0(esbuild@0.27.7) 6348 6415 cac: 6.7.14 ··· 6353 6420 fix-dts-default-cjs-exports: 1.0.1 6354 6421 joycon: 3.1.1 6355 6422 picocolors: 1.1.1 6356 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0)(yaml@1.10.3) 6423 + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.10)(tsx@4.21.0) 6357 6424 resolve-from: 5.0.0 6358 6425 rollup: 4.60.2 6359 6426 source-map: 0.7.6 ··· 6433 6500 optionalDependencies: 6434 6501 typescript: 6.0.3 6435 6502 6436 - vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3): 6503 + vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0): 6437 6504 dependencies: 6438 6505 lightningcss: 1.32.0 6439 6506 picomatch: 4.0.4 ··· 6446 6513 fsevents: 2.3.3 6447 6514 jiti: 2.6.1 6448 6515 tsx: 4.21.0 6449 - yaml: 1.10.3 6450 6516 6451 - vitefu@1.1.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)): 6517 + vitefu@1.1.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)): 6452 6518 optionalDependencies: 6453 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3) 6519 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) 6454 6520 6455 - vitest@4.1.5(@types/node@25.6.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)): 6521 + vitest@4.1.5(@types/node@25.6.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)): 6456 6522 dependencies: 6457 6523 '@vitest/expect': 4.1.5 6458 - '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3)) 6524 + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)) 6459 6525 '@vitest/pretty-format': 4.1.5 6460 6526 '@vitest/runner': 4.1.5 6461 6527 '@vitest/snapshot': 4.1.5 ··· 6472 6538 tinyexec: 1.1.1 6473 6539 tinyglobby: 0.2.16 6474 6540 tinyrainbow: 3.1.0 6475 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)(yaml@1.10.3) 6541 + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0) 6476 6542 why-is-node-running: 2.3.0 6477 6543 optionalDependencies: 6478 6544 '@types/node': 25.6.0
+29
apps/contrail-e2e/.env.example
··· 1 + # Ports (override to avoid conflicts with other local services) 2 + DEVNET_PDS_PORT=4000 3 + DEVNET_PLC_PORT=2582 4 + DEVNET_JETSTREAM_PORT=6008 5 + DEVNET_JETSTREAM_METRICS_PORT=6009 6 + DEVNET_TAP_PORT=2480 7 + PG_PORT=5433 8 + MAILDEV_WEB_PORT=1080 9 + MAILDEV_SMTP_PORT=1025 10 + 11 + # Devnet database (PLC server) — uses the shared postgres service 12 + DEVNET_DB_HOST=postgres 13 + DEVNET_DB_PORT=5432 14 + DEVNET_DB_USER=postgres 15 + DEVNET_DB_PASSWORD=postgres 16 + DEVNET_DB_NAME=plc 17 + 18 + # Devnet SMTP (PDS email verification) — uses the shared maildev service 19 + DEVNET_SMTP_URL=smtp://maildev:1025 20 + 21 + # Devnet handle config 22 + DEVNET_PDS_HOSTNAME=devnet.test 23 + DEVNET_PDS_ADMIN_PASSWORD=devnet-admin-password 24 + DEVNET_HANDLE_DOMAIN=.devnet.test 25 + DEVNET_SEED_ACCOUNTS=true 26 + 27 + # Contrail runtime (consumed by the e2e suite) 28 + DATABASE_URL=postgresql://postgres:postgres@localhost:5433/contrail 29 + JETSTREAM_URL=ws://localhost:6008/subscribe
+2
apps/contrail-e2e/.gitignore
··· 1 + .env 2 + node_modules
+90
apps/contrail-e2e/README.md
··· 1 + # contrail-e2e — end-to-end tests against a local ATProto stack 2 + 3 + End-to-end test suite for Contrail against a sealed local ATProto stack. 4 + Nothing touches the public ATProto — writes go to a local PDS, the local 5 + Jetstream reads that PDS's firehose, and each test stands up its own 6 + in-process Contrail ingester + XRPC handler to consume it. 7 + 8 + ## What's inside 9 + 10 + | Service | Port | Purpose | 11 + |---------|------|---------| 12 + | PDS | 4000 | Personal Data Server (user repos) | 13 + | PLC | 2582 | DID registry | 14 + | Jetstream | 6008 | Firehose relay in JSON | 15 + | TAP | 2480 | Sync/backfill relay | 16 + | PostgreSQL | 5433 | Shared by PLC + Contrail | 17 + | Maildev | 1080 | PDS email verification catcher | 18 + 19 + ## Prerequisites 20 + 21 + Clone `atproto-devnet` as a sibling of this contrail repo: 22 + 23 + ``` 24 + some-workspace/ 25 + ├── contrail/ (this repo) 26 + └── atproto-devnet/ 27 + ``` 28 + 29 + ```bash 30 + git clone https://github.com/OpenMeet-Team/atproto-devnet.git /path/to/atproto-devnet 31 + ``` 32 + 33 + ## Running the tests 34 + 35 + ```bash 36 + # From the monorepo root (one-time build of @atmo-dev/contrail) 37 + pnpm install && pnpm build 38 + 39 + # Bring the devnet stack up (creates .env from .env.example on first run) 40 + cd apps/contrail-e2e 41 + pnpm stack:up 42 + 43 + # Run the suite 44 + pnpm test:e2e # once 45 + pnpm test:e2e:watch # re-run on change 46 + ``` 47 + 48 + > **Note:** `up` and `down` are reserved by pnpm (aliases for `update`), so 49 + > the stack scripts are prefixed `stack:` to disambiguate. The test scripts 50 + > are named `test:e2e` so the monorepo-wide `pnpm test` skips them in CI 51 + > (they need the live docker stack). 52 + 53 + ## Tests 54 + 55 + - `tests/health.test.ts` — service health checks (PLC, PDS, TAP, Jetstream) 56 + - `tests/ingest-roundtrip.test.ts` — publish → index roundtrip. Creates a 57 + fresh PDS account, publishes a calendar event and an RSVP, and verifies 58 + both the record and its `rsvpsGoingCount` via an in-process XRPC handler. 59 + - `tests/cursor-resume.test.ts` — regression for `runPersistent`'s durable 60 + cursor. Starts the ingester, publishes A, stops the ingester, publishes 61 + B + updates B + deletes A, restarts, and verifies the saved cursor 62 + replays the gap. 63 + 64 + Each test spins up its own `runPersistent` in-process against an isolated 65 + postgres schema, so tests don't interfere with each other or with any 66 + dogfooding ingester running in another terminal. 67 + 68 + ## Teardown 69 + 70 + ```bash 71 + pnpm stack:down # stops containers AND wipes volumes for a clean slate 72 + ``` 73 + 74 + ## Namespace 75 + 76 + These tests use `rsvp.atmo` as the Contrail namespace — same as 77 + [atmo-events](https://github.com/flo-bit/atmo-events), so the fixtures are 78 + compatible with that frontend. If you're running your own namespace, edit 79 + `config.ts`. 80 + 81 + ## Why not use `apps/postgres/`? 82 + 83 + `apps/postgres/` connects Contrail to the public Bluesky Jetstream — useful 84 + for indexing real events, unusable for writing test fixtures. This suite 85 + gives a sealed environment where the tests control every record. 86 + 87 + ## Overrides 88 + 89 + Copy `.env.example` to `.env` and edit. All ports, database credentials, and 90 + devnet hostnames are overridable.
+51
apps/contrail-e2e/config.ts
··· 1 + import type { ContrailConfig } from "@atmo-dev/contrail"; 2 + 3 + // Namespace matches atmo.rsvp so the same atmo-events frontend can point at 4 + // this devnet-backed Contrail without patching its hardcoded namespace. 5 + // See https://github.com/flo-bit/atmo-events/issues/26 for making this 6 + // configurable upstream. 7 + export const config: ContrailConfig = { 8 + namespace: "rsvp.atmo", 9 + jetstreams: [ 10 + process.env.JETSTREAM_URL ?? "ws://localhost:6008/subscribe", 11 + ], 12 + collections: { 13 + event: { 14 + collection: "community.lexicon.calendar.event", 15 + queryable: { 16 + mode: {}, 17 + name: {}, 18 + status: {}, 19 + startsAt: { type: "range" }, 20 + endsAt: { type: "range" }, 21 + createdAt: { type: "range" }, 22 + }, 23 + searchable: ["name", "description"], 24 + relations: { 25 + rsvps: { 26 + collection: "rsvp", 27 + groupBy: "status", 28 + count: true, 29 + groups: { 30 + interested: "community.lexicon.calendar.rsvp#interested", 31 + going: "community.lexicon.calendar.rsvp#going", 32 + notgoing: "community.lexicon.calendar.rsvp#notgoing", 33 + }, 34 + }, 35 + }, 36 + }, 37 + rsvp: { 38 + collection: "community.lexicon.calendar.rsvp", 39 + queryable: { 40 + status: {}, 41 + "subject.uri": {}, 42 + }, 43 + references: { 44 + event: { 45 + collection: "event", 46 + field: "subject.uri", 47 + }, 48 + }, 49 + }, 50 + }, 51 + };
+55
apps/contrail-e2e/docker-compose.yml
··· 1 + # contrail-e2e: Contrail + atproto-devnet for local testing. 2 + # 3 + # Brings up a full local ATProto network (PDS, PLC, Jetstream, TAP) alongside 4 + # the PostgreSQL instance Contrail indexes into. No traffic leaves your laptop. 5 + # 6 + # Prerequisites: 7 + # - atproto-devnet cloned as a sibling of this contrail repo: 8 + # some-workspace/ 9 + # ├── contrail/ (this repo) 10 + # └── atproto-devnet/ 11 + # - Copy .env.example to .env first (scripts/up.sh does this automatically) 12 + # 13 + # Usage: 14 + # pnpm up # bring everything up, wait for health 15 + # pnpm down # stop + wipe volumes (clean slate) 16 + # pnpm smoke # verify services are healthy 17 + 18 + include: 19 + - path: ../../../atproto-devnet/docker-compose.yml 20 + 21 + services: 22 + # PostgreSQL — shared by PLC (devnet) and Contrail (this example). 23 + # NOTE: included services like PLC can't be modified via `include:` merge, 24 + # so scripts/up.sh brings postgres up first, then the rest. 25 + postgres: 26 + image: postgres:17 27 + restart: unless-stopped 28 + environment: 29 + POSTGRES_USER: ${DEVNET_DB_USER:-postgres} 30 + POSTGRES_PASSWORD: ${DEVNET_DB_PASSWORD:-postgres} 31 + ports: 32 + - "${PG_PORT:-5433}:5432" 33 + volumes: 34 + - pgdata:/var/lib/postgresql/data 35 + - ./init-db:/docker-entrypoint-initdb.d:ro 36 + networks: 37 + - atproto-devnet 38 + healthcheck: 39 + test: ["CMD-SHELL", "pg_isready -U ${DEVNET_DB_USER:-postgres}"] 40 + interval: 3s 41 + timeout: 3s 42 + retries: 20 43 + 44 + # Maildev — catches PDS email verification messages 45 + maildev: 46 + image: maildev/maildev:latest 47 + restart: unless-stopped 48 + ports: 49 + - "${MAILDEV_WEB_PORT:-1080}:1080" 50 + - "${MAILDEV_SMTP_PORT:-1025}:1025" 51 + networks: 52 + - atproto-devnet 53 + 54 + volumes: 55 + pgdata:
+23
apps/contrail-e2e/package.json
··· 1 + { 2 + "name": "contrail-e2e", 3 + "version": "0.0.1", 4 + "private": true, 5 + "type": "module", 6 + "scripts": { 7 + "stack:up": "./scripts/up.sh", 8 + "stack:down": "./scripts/down.sh", 9 + "test:e2e": "vitest run", 10 + "test:e2e:watch": "vitest" 11 + }, 12 + "dependencies": { 13 + "@atmo-dev/contrail": "workspace:*", 14 + "pg": "^8.20.0" 15 + }, 16 + "devDependencies": { 17 + "@atcute/atproto": "^3.1.10", 18 + "@atcute/client": "^4.2.1", 19 + "@types/pg": "^8.20.0", 20 + "typescript": "^5.9.3", 21 + "vitest": "^4.1.0" 22 + } 23 + }
+13
apps/contrail-e2e/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ES2022", 5 + "moduleResolution": "bundler", 6 + "lib": ["ES2022"], 7 + "strict": true, 8 + "noEmit": true, 9 + "skipLibCheck": true, 10 + "isolatedModules": true 11 + }, 12 + "include": ["."] 13 + }
+11
apps/contrail-e2e/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + include: ["tests/**/*.test.ts"], 6 + // Live-stack tests mutate a shared devnet — can't run in parallel. 7 + fileParallelism: false, 8 + // Network round-trips can be slow on a cold stack. 9 + testTimeout: 30_000, 10 + }, 11 + });
+5
apps/contrail-e2e/init-db/01-databases.sql
··· 1 + -- Create both databases used by this example: 2 + -- plc — DID registry state for the devnet PLC server 3 + -- contrail — Contrail's own indexed records 4 + CREATE DATABASE plc; 5 + CREATE DATABASE contrail;
+9
apps/contrail-e2e/scripts/down.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + cd "$(dirname "$0")/.." 5 + 6 + echo "→ Tearing down (removes volumes — clean slate)..." 7 + docker compose down -v 8 + 9 + echo "✓ Down. All state wiped."
+39
apps/contrail-e2e/scripts/up.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + cd "$(dirname "$0")/.." 5 + 6 + if [ ! -f .env ]; then 7 + echo "→ First run: copying .env.example to .env" 8 + cp .env.example .env 9 + fi 10 + 11 + DEVNET_COMPOSE="../../../atproto-devnet/docker-compose.yml" 12 + if [ ! -f "$DEVNET_COMPOSE" ]; then 13 + echo "Error: atproto-devnet not found at $DEVNET_COMPOSE" 14 + echo "" 15 + echo "Clone it as a sibling of the contrail repo:" 16 + echo " git clone https://github.com/OpenMeet-Team/atproto-devnet.git ../../../../atproto-devnet" 17 + exit 1 18 + fi 19 + 20 + echo "→ Bringing up postgres (PLC depends on it)..." 21 + docker compose up -d --wait postgres maildev 22 + 23 + echo "→ Bringing up devnet (PLC, PDS, Jetstream, TAP, init)..." 24 + docker compose up -d --wait 25 + 26 + # shellcheck disable=SC1091 27 + source .env 28 + 29 + echo "" 30 + echo "Services:" 31 + echo " PDS: http://localhost:${DEVNET_PDS_PORT:-4000}" 32 + echo " PLC: http://localhost:${DEVNET_PLC_PORT:-2582}" 33 + echo " Jetstream: ws://localhost:${DEVNET_JETSTREAM_PORT:-6008}" 34 + echo " TAP: http://localhost:${DEVNET_TAP_PORT:-2480}" 35 + echo " Postgres: localhost:${PG_PORT:-5433}" 36 + echo " MailDev UI: http://localhost:${MAILDEV_WEB_PORT:-1080}" 37 + echo "" 38 + echo "Next:" 39 + echo " pnpm test # run the e2e suite (ingester + XRPC handler run in-process)"
+192
apps/contrail-e2e/tests/cursor-resume.test.ts
··· 1 + /** 2 + * End-to-end: the ingester must resume from its persisted cursor after a 3 + * restart, capturing events that arrived on the firehose while it was down. 4 + * 5 + * Flow: 6 + * 1. Start runPersistent in-process against an isolated pg schema. 7 + * 2. Publish event A → wait for it to be indexed → abort the ingester and 8 + * await the promise. The finally-block's final flush must persist the 9 + * cursor or this test fails at step 5. 10 + * 3. While the ingester is DOWN: publish event B, update B, delete A. 11 + * 4. Restart runPersistent against the SAME schema (same cursor row). 12 + * 5. Assert: A is gone, B is indexed with its updated name. Those three 13 + * commits can only be seen by replaying from the saved cursor — Jetstream 14 + * is "live tail" without it, and the events already happened. 15 + * 16 + * Why this matters: runPersistent's whole reason to exist is durable state. 17 + * A silently broken cursor would pass the basic roundtrip test because there's 18 + * nothing to replay — every event arrives while the ingester is live. This 19 + * test is the only one that exercises the replay path. 20 + * 21 + * Requires `pnpm stack:up` only. Ingester runs in-process. 22 + */ 23 + import { describe, it, expect, beforeAll, afterAll } from "vitest"; 24 + import pg from "pg"; 25 + import { CredentialManager, Client } from "@atcute/client"; 26 + import "@atcute/atproto"; 27 + import { Contrail } from "@atmo-dev/contrail"; 28 + import { createPostgresDatabase } from "@atmo-dev/contrail/postgres"; 29 + import { config as baseConfig } from "../config"; 30 + import { 31 + createTestAccount, 32 + createIsolatedSchema, 33 + waitFor, 34 + PDS_URL, 35 + type TestAccount, 36 + } from "./helpers"; 37 + 38 + const EVENT_NSID = "community.lexicon.calendar.event"; 39 + 40 + describe("cursor resume (ingester stops, events pile up, ingester restarts)", () => { 41 + let account: TestAccount; 42 + let client: Client; 43 + let pool: pg.Pool; 44 + let cleanupSchema: () => Promise<void>; 45 + 46 + beforeAll(async () => { 47 + account = await createTestAccount(); 48 + const creds = new CredentialManager({ service: PDS_URL }); 49 + await creds.login({ identifier: account.handle, password: account.password }); 50 + client = new Client({ handler: creds }); 51 + 52 + const iso = await createIsolatedSchema("test_cursor"); 53 + pool = iso.pool; 54 + cleanupSchema = iso.cleanup; 55 + }); 56 + 57 + afterAll(async () => { 58 + await cleanupSchema?.(); 59 + }); 60 + 61 + async function queryRecord(uri: string): Promise<{ record: any } | undefined> { 62 + const res = await pool.query(`SELECT uri, record FROM records_event WHERE uri = $1`, [uri]); 63 + if (res.rows.length === 0) return undefined; 64 + const row = res.rows[0]; 65 + return { record: typeof row.record === "string" ? JSON.parse(row.record) : row.record }; 66 + } 67 + 68 + async function getCursor(): Promise<number | null> { 69 + const res = await pool.query(`SELECT time_us FROM cursor WHERE id = 1`); 70 + if (res.rows.length === 0) return null; 71 + return Number(res.rows[0].time_us); 72 + } 73 + 74 + it("resumes from saved cursor and picks up commits issued while down", async () => { 75 + const db = createPostgresDatabase(pool); 76 + // Go through the Contrail wrapper so the config is resolved; raw 77 + // runPersistent expects a resolved config. 78 + const contrail = new Contrail({ ...baseConfig, db }); 79 + await contrail.init(); 80 + const runOpts = { batchSize: 50, flushIntervalMs: 500 }; 81 + 82 + // ---- Phase 1: start ingester, publish A, verify indexed ---- 83 + const c1 = new AbortController(); 84 + const ingest1 = contrail.runPersistent({ ...runOpts, signal: c1.signal }); 85 + 86 + const nameA = `A-${Date.now()}`; 87 + const aRes = await client.post("com.atproto.repo.createRecord", { 88 + input: { 89 + repo: account.did, 90 + collection: EVENT_NSID, 91 + record: { 92 + $type: EVENT_NSID, 93 + name: nameA, 94 + createdAt: new Date().toISOString(), 95 + startsAt: new Date(Date.now() + 60 * 60_000).toISOString(), 96 + mode: `${EVENT_NSID}#inperson`, 97 + status: `${EVENT_NSID}#scheduled`, 98 + }, 99 + }, 100 + }); 101 + expect(aRes.ok).toBe(true); 102 + if (!aRes.ok) throw new Error("unreachable"); 103 + const uriA = aRes.data.uri; 104 + 105 + await waitFor(() => queryRecord(uriA), { label: `A indexed (${uriA})` }); 106 + const cursorAfterA = await getCursor(); 107 + expect(cursorAfterA, "cursor must be persisted after first flush").not.toBeNull(); 108 + 109 + // ---- Phase 2: stop ingester ---- 110 + c1.abort(); 111 + await ingest1; 112 + 113 + const cursorAfterStop = await getCursor(); 114 + expect(cursorAfterStop).toBe(cursorAfterA); 115 + 116 + // ---- Phase 3: mutate while ingester is DOWN ---- 117 + const nameB = `B-${Date.now()}`; 118 + const bRes = await client.post("com.atproto.repo.createRecord", { 119 + input: { 120 + repo: account.did, 121 + collection: EVENT_NSID, 122 + record: { 123 + $type: EVENT_NSID, 124 + name: nameB, 125 + createdAt: new Date().toISOString(), 126 + startsAt: new Date(Date.now() + 2 * 60 * 60_000).toISOString(), 127 + mode: `${EVENT_NSID}#inperson`, 128 + status: `${EVENT_NSID}#scheduled`, 129 + }, 130 + }, 131 + }); 132 + expect(bRes.ok).toBe(true); 133 + if (!bRes.ok) throw new Error("unreachable"); 134 + const uriB = bRes.data.uri; 135 + const rkeyB = uriB.split("/").pop()!; 136 + const rkeyA = uriA.split("/").pop()!; 137 + 138 + const nameBUpdated = `${nameB}-renamed`; 139 + const putRes = await client.post("com.atproto.repo.putRecord", { 140 + input: { 141 + repo: account.did, 142 + collection: EVENT_NSID, 143 + rkey: rkeyB, 144 + record: { 145 + $type: EVENT_NSID, 146 + name: nameBUpdated, 147 + createdAt: new Date().toISOString(), 148 + startsAt: new Date(Date.now() + 2 * 60 * 60_000).toISOString(), 149 + mode: `${EVENT_NSID}#inperson`, 150 + status: `${EVENT_NSID}#scheduled`, 151 + }, 152 + }, 153 + }); 154 + expect(putRes.ok).toBe(true); 155 + 156 + const delRes = await client.post("com.atproto.repo.deleteRecord", { 157 + input: { repo: account.did, collection: EVENT_NSID, rkey: rkeyA }, 158 + }); 159 + expect(delRes.ok).toBe(true); 160 + 161 + // Confirm the DB didn't move while the ingester was down. 162 + expect(await queryRecord(uriA), "A must still be indexed — nobody is consuming").toBeDefined(); 163 + expect(await queryRecord(uriB), "B should not be indexed yet — ingester is down").toBeUndefined(); 164 + 165 + // ---- Phase 4: restart ingester, assert replay ---- 166 + const c2 = new AbortController(); 167 + const ingest2 = contrail.runPersistent({ ...runOpts, signal: c2.signal }); 168 + 169 + try { 170 + const indexedB = await waitFor( 171 + async () => { 172 + const r = await queryRecord(uriB); 173 + return r && r.record.name === nameBUpdated ? r : undefined; 174 + }, 175 + { label: `B replayed with updated name` }, 176 + ); 177 + expect(indexedB.record.name).toBe(nameBUpdated); 178 + 179 + await waitFor( 180 + async () => ((await queryRecord(uriA)) === undefined ? true : undefined), 181 + { label: `A deletion replayed` }, 182 + ); 183 + 184 + const cursorAfterReplay = await getCursor(); 185 + expect(cursorAfterReplay).not.toBeNull(); 186 + expect(cursorAfterReplay!).toBeGreaterThan(cursorAfterStop!); 187 + } finally { 188 + c2.abort(); 189 + await ingest2; 190 + } 191 + }, 60_000); 192 + });
+58
apps/contrail-e2e/tests/health.test.ts
··· 1 + /** 2 + * Health checks for the running devnet stack. 3 + * 4 + * Assumes `pnpm stack:up` has completed. Reads ports from the environment 5 + * with fallbacks matching `.env.example` so tests work out of the box. 6 + * 7 + * The Contrail handler is covered by ingest-roundtrip.test.ts in-process — 8 + * this file only verifies the external devnet services are reachable. 9 + */ 10 + import { describe, it, expect } from "vitest"; 11 + import net from "node:net"; 12 + 13 + const PDS_PORT = Number(process.env.DEVNET_PDS_PORT ?? 4000); 14 + const PLC_PORT = Number(process.env.DEVNET_PLC_PORT ?? 2582); 15 + const TAP_PORT = Number(process.env.DEVNET_TAP_PORT ?? 2480); 16 + const JS_PORT = Number(process.env.DEVNET_JETSTREAM_PORT ?? 6008); 17 + 18 + async function get(url: string): Promise<Response> { 19 + return fetch(url); 20 + } 21 + 22 + function tcpConnect(port: number, host = "127.0.0.1"): Promise<void> { 23 + return new Promise((resolve, reject) => { 24 + const socket = new net.Socket(); 25 + socket.setTimeout(3000); 26 + socket.once("connect", () => { 27 + socket.destroy(); 28 + resolve(); 29 + }); 30 + socket.once("timeout", () => { 31 + socket.destroy(); 32 + reject(new Error(`TCP connect to ${host}:${port} timed out`)); 33 + }); 34 + socket.once("error", reject); 35 + socket.connect(port, host); 36 + }); 37 + } 38 + 39 + describe("devnet services", () => { 40 + it("PLC responds to /_health", async () => { 41 + const res = await get(`http://localhost:${PLC_PORT}/_health`); 42 + expect(res.ok).toBe(true); 43 + }); 44 + 45 + it("PDS responds to /xrpc/_health", async () => { 46 + const res = await get(`http://localhost:${PDS_PORT}/xrpc/_health`); 47 + expect(res.ok).toBe(true); 48 + }); 49 + 50 + it("TAP responds to /health", async () => { 51 + const res = await get(`http://localhost:${TAP_PORT}/health`); 52 + expect(res.ok).toBe(true); 53 + }); 54 + 55 + it("Jetstream accepts TCP connections", async () => { 56 + await expect(tcpConnect(JS_PORT)).resolves.toBeUndefined(); 57 + }); 58 + });
+107
apps/contrail-e2e/tests/helpers.ts
··· 1 + /** 2 + * Shared test infrastructure for the contrail-e2e suite. 3 + * 4 + * The ingester and XRPC handler run in-process against an isolated pg schema 5 + * per test — no external processes required, and no collision with a 6 + * dogfooding ingester the developer might have running in another terminal. 7 + */ 8 + import pg from "pg"; 9 + 10 + export type Did = `did:${string}:${string}`; 11 + 12 + export const PDS_PORT = Number(process.env.DEVNET_PDS_PORT ?? 4000); 13 + export const PDS_URL = `http://localhost:${PDS_PORT}`; 14 + export const HANDLE_DOMAIN = process.env.DEVNET_HANDLE_DOMAIN ?? ".devnet.test"; 15 + export const PDS_ADMIN_PASSWORD = process.env.DEVNET_PDS_ADMIN_PASSWORD ?? "devnet-admin-password"; 16 + export const DATABASE_URL = 17 + process.env.DATABASE_URL ?? "postgresql://postgres:postgres@localhost:5433/contrail"; 18 + 19 + export type TestAccount = { handle: string; password: string; did: Did }; 20 + 21 + export async function createTestAccount(): Promise<TestAccount> { 22 + const inviteRes = await fetch(`${PDS_URL}/xrpc/com.atproto.server.createInviteCode`, { 23 + method: "POST", 24 + headers: { 25 + "content-type": "application/json", 26 + authorization: `Basic ${Buffer.from(`admin:${PDS_ADMIN_PASSWORD}`).toString("base64")}`, 27 + }, 28 + body: JSON.stringify({ useCount: 1 }), 29 + }); 30 + if (!inviteRes.ok) { 31 + throw new Error(`createInviteCode → ${inviteRes.status}: ${await inviteRes.text()}`); 32 + } 33 + const { code: inviteCode } = (await inviteRes.json()) as { code: string }; 34 + 35 + const suffix = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; 36 + const handle = `test-${suffix}${HANDLE_DOMAIN}`; 37 + const password = `pw-${suffix}`; 38 + 39 + const accountRes = await fetch(`${PDS_URL}/xrpc/com.atproto.server.createAccount`, { 40 + method: "POST", 41 + headers: { "content-type": "application/json" }, 42 + body: JSON.stringify({ handle, email: `${suffix}@devnet.test`, password, inviteCode }), 43 + }); 44 + if (!accountRes.ok) { 45 + throw new Error(`createAccount → ${accountRes.status}: ${await accountRes.text()}`); 46 + } 47 + const { did } = (await accountRes.json()) as { did: string }; 48 + return { handle, password, did: did as Did }; 49 + } 50 + 51 + /** 52 + * Create a fresh pg schema and return a Pool pinned to it via search_path. 53 + * Call the returned `cleanup` in afterAll to drop the schema. 54 + */ 55 + export async function createIsolatedSchema( 56 + prefix = "test", 57 + ): Promise<{ pool: pg.Pool; schemaName: string; cleanup: () => Promise<void> }> { 58 + const schemaName = `${prefix}_${Date.now()}${Math.floor(Math.random() * 1000)}`; 59 + const bootstrap = new pg.Pool({ connectionString: DATABASE_URL }); 60 + await bootstrap.query(`CREATE SCHEMA "${schemaName}"`); 61 + await bootstrap.end(); 62 + 63 + const pool = new pg.Pool({ 64 + connectionString: DATABASE_URL, 65 + options: `-c search_path="${schemaName}"`, 66 + }); 67 + 68 + const cleanup = async () => { 69 + await pool.end(); 70 + const c = new pg.Pool({ connectionString: DATABASE_URL }); 71 + await c.query(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`); 72 + await c.end(); 73 + }; 74 + 75 + return { pool, schemaName, cleanup }; 76 + } 77 + 78 + /** 79 + * Poll `fn` until it returns a defined value, or time out. 80 + * 81 + * Thrown errors count as "not yet" (last is included in the timeout message) 82 + * so transient 404s during startup don't abort. Use `label` to say what was 83 + * missing. Defaults (15s / 250ms) cover Contrail's 500ms test-mode flush plus 84 + * firehose propagation with plenty of headroom. 85 + */ 86 + export async function waitFor<T>( 87 + fn: () => Promise<T | undefined>, 88 + { timeoutMs = 15_000, intervalMs = 250, label }: { timeoutMs?: number; intervalMs?: number; label: string }, 89 + ): Promise<T> { 90 + const deadline = Date.now() + timeoutMs; 91 + let lastErr: unknown; 92 + let attempts = 0; 93 + while (Date.now() < deadline) { 94 + attempts++; 95 + try { 96 + const v = await fn(); 97 + if (v !== undefined) return v; 98 + } catch (err) { 99 + lastErr = err; 100 + } 101 + await new Promise((r) => setTimeout(r, intervalMs)); 102 + } 103 + throw new Error( 104 + `waitFor(${label}) timed out after ${timeoutMs}ms (${attempts} attempts)` + 105 + (lastErr ? `: ${(lastErr as Error).message}` : ""), 106 + ); 107 + }
+150
apps/contrail-e2e/tests/ingest-roundtrip.test.ts
··· 1 + /** 2 + * End-to-end: publish a record via the devnet PDS, verify Contrail indexes it 3 + * via Jetstream. 4 + * 5 + * Flow: 6 + * 1. Stand up an in-process Contrail against an isolated pg schema — no 7 + * collision with a dogfooding `pnpm ingest` and no external processes 8 + * required. 9 + * 2. Create a fresh PDS account + auth (unique handle per run). 10 + * 3. Publish records through `com.atproto.repo.createRecord` on the PDS. 11 + * 4. Poll the in-process XRPC handler's `rsvp.atmo.event.getRecord` until 12 + * the record appears. Flush cadence is 500ms in tests, so publish → 13 + * indexed is bounded by firehose propagation plus one flush. 14 + * 5. Assert indexed fields match what we published. 15 + * 16 + * Requires `pnpm stack:up` only. The ingester and XRPC handler run in-process. 17 + */ 18 + import { describe, it, expect, beforeAll, afterAll } from "vitest"; 19 + import pg from "pg"; 20 + import { CredentialManager, Client } from "@atcute/client"; 21 + import "@atcute/atproto"; 22 + import { Contrail } from "@atmo-dev/contrail"; 23 + import { createHandler } from "@atmo-dev/contrail/server"; 24 + import { createPostgresDatabase } from "@atmo-dev/contrail/postgres"; 25 + import { config as baseConfig } from "../config"; 26 + import { 27 + createTestAccount, 28 + createIsolatedSchema, 29 + waitFor, 30 + PDS_URL, 31 + type TestAccount, 32 + } from "./helpers"; 33 + 34 + const EVENT_NSID = "community.lexicon.calendar.event"; 35 + const RSVP_NSID = "community.lexicon.calendar.rsvp"; 36 + 37 + describe("ingest roundtrip (devnet PDS → Jetstream → Contrail)", () => { 38 + let account: TestAccount; 39 + let client: Client; 40 + let pool: pg.Pool; 41 + let cleanupSchema: () => Promise<void>; 42 + let handle: (req: Request) => Promise<Response>; 43 + let ingestController: AbortController; 44 + let ingestPromise: Promise<void>; 45 + 46 + let eventUri: string; 47 + let eventCid: string; 48 + const eventName = `devnet roundtrip ${Date.now()}`; 49 + const startsAt = new Date(Date.now() + 60 * 60_000).toISOString(); 50 + 51 + beforeAll(async () => { 52 + // PDS auth 53 + account = await createTestAccount(); 54 + const creds = new CredentialManager({ service: PDS_URL }); 55 + await creds.login({ identifier: account.handle, password: account.password }); 56 + client = new Client({ handler: creds }); 57 + 58 + // Isolated schema + in-process Contrail 59 + const iso = await createIsolatedSchema("test_roundtrip"); 60 + pool = iso.pool; 61 + cleanupSchema = iso.cleanup; 62 + const db = createPostgresDatabase(pool); 63 + const contrail = new Contrail({ ...baseConfig, db }); 64 + await contrail.init(); 65 + handle = createHandler(contrail); 66 + 67 + // In-process ingester via Contrail so the config is resolved (raw 68 + // runPersistent expects a resolved config — grouped counts silently 69 + // don't update otherwise). 70 + ingestController = new AbortController(); 71 + ingestPromise = contrail.runPersistent({ 72 + batchSize: 50, 73 + flushIntervalMs: 500, 74 + signal: ingestController.signal, 75 + }); 76 + }); 77 + 78 + afterAll(async () => { 79 + ingestController?.abort(); 80 + await ingestPromise?.catch(() => {}); 81 + await cleanupSchema?.(); 82 + }); 83 + 84 + async function getIndexedRecord(uri: string): Promise<any | undefined> { 85 + const url = `http://test/xrpc/rsvp.atmo.event.getRecord?uri=${encodeURIComponent(uri)}`; 86 + const res = await handle(new Request(url)); 87 + if (res.status === 404) return undefined; 88 + if (!res.ok) throw new Error(`getRecord ${uri} → ${res.status}: ${await res.text()}`); 89 + // Contrail's getRecord returns the formatted row — uri/did/cid/time_us/record 90 + // plus flattened relation counts (e.g. rsvpsGoingCount). 91 + return await res.json(); 92 + } 93 + 94 + it("indexes a community.lexicon.calendar.event published to devnet PDS", async () => { 95 + const res = await client.post("com.atproto.repo.createRecord", { 96 + input: { 97 + repo: account.did, 98 + collection: EVENT_NSID, 99 + record: { 100 + $type: EVENT_NSID, 101 + name: eventName, 102 + createdAt: new Date().toISOString(), 103 + startsAt, 104 + mode: `${EVENT_NSID}#inperson`, 105 + status: `${EVENT_NSID}#scheduled`, 106 + }, 107 + }, 108 + }); 109 + expect(res.ok, `createRecord failed: ${JSON.stringify(res.data)}`).toBe(true); 110 + if (!res.ok) throw new Error("unreachable"); 111 + eventUri = res.data.uri; 112 + eventCid = res.data.cid; 113 + expect(eventUri).toMatch(new RegExp(`^at://${account.did}/${EVENT_NSID}/`)); 114 + 115 + const indexed = await waitFor(() => getIndexedRecord(eventUri), { label: `event ${eventUri}` }); 116 + 117 + expect(indexed.uri).toBe(eventUri); 118 + expect(indexed.did).toBe(account.did); 119 + expect(indexed.record.name).toBe(eventName); 120 + expect(indexed.record.startsAt).toBe(startsAt); 121 + }); 122 + 123 + it("increments rsvpsGoingCount when an RSVP is published", async () => { 124 + expect(eventUri, "event must be published by the previous test").toBeTruthy(); 125 + 126 + const res = await client.post("com.atproto.repo.createRecord", { 127 + input: { 128 + repo: account.did, 129 + collection: RSVP_NSID, 130 + record: { 131 + $type: RSVP_NSID, 132 + subject: { uri: eventUri, cid: eventCid }, 133 + status: `${RSVP_NSID}#going`, 134 + createdAt: new Date().toISOString(), 135 + }, 136 + }, 137 + }); 138 + expect(res.ok, `createRecord failed: ${JSON.stringify(res.data)}`).toBe(true); 139 + 140 + const indexed = await waitFor( 141 + async () => { 142 + const r = await getIndexedRecord(eventUri); 143 + return r && r.rsvpsGoingCount >= 1 ? r : undefined; 144 + }, 145 + { label: `rsvpsGoingCount for ${eventUri}` }, 146 + ); 147 + 148 + expect(indexed.rsvpsGoingCount).toBe(1); 149 + }); 150 + });