diff --git a/README.md b/README.md index 6d621de..0adcc0f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ Zep is a self-hosted, real-time communication platform built with Svelte 5 and SpacetimeDB. It provides text, voice, and video messaging functionality designed for small groups and private communities. +## Core Tenets + +* **Low Dependencies:** We strive to keep the project's dependency graph as lean as possible, favoring native web capabilities over third-party libraries for better long-term maintainability and security. +* **Frictionless Deployment:** Self-hosting should be trivial. Getting your own Zep server running must require the lowest number of steps possible (ideally just one command). +* **Small Scale Focus:** The project is intended to be deployed amongst a small group of friends or team members. It is not currently designed to scale to massive numbers of concurrent users, although achieving higher scalability remains a stretch goal. + ## Data Ownership & Privacy * **Self-Hosted:** The application is designed to be hosted by the user. Data is stored in a SpacetimeDB instance controlled by the host. diff --git a/package.json b/package.json index c806cd2..d6fd13f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --module-path spacetimedb", "spacetime:publish": "spacetime publish --module-path spacetimedb --server maincloud", "spacetime:publish:local": "spacetime publish --module-path spacetimedb --server local", + "stress": "tsx src-stress/index.ts", "deploy:local": "docker compose -f docker-compose.local.yml up --build", "deploy:cloudflare": "pnpm run build && wrangler deploy" }, @@ -41,6 +42,7 @@ "jsdom": "^26.0.0", "prettier": "^3.3.3", "svelte-eslint-parser": "^1.6.0", + "tsx": "^4.21.0", "typescript": "~5.6.2", "typescript-eslint": "^8.18.2", "vite": "^8.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a4226a..6b33200 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 7.2.0 '@sveltejs/vite-plugin-svelte': specifier: ^7.0.0 - version: 7.0.0(svelte@5.55.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)) + version: 7.0.0(svelte@5.55.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0)) '@testing-library/svelte': specifier: ^5.3.1 - version: 5.3.1(svelte@5.55.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4))(vitest@3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0)) + version: 5.3.1(svelte@5.55.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0))(vitest@3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0)) oidc-client-ts: specifier: ^3.5.0 version: 3.5.0 @@ -32,7 +32,7 @@ importers: devDependencies: '@cloudflare/vite-plugin': specifier: ^1.31.0 - version: 1.31.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4))(workerd@1.20260401.1)(wrangler@4.80.0) + version: 1.31.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0))(workerd@1.20260401.1)(wrangler@4.80.0) '@eslint/js': specifier: ^9.17.0 version: 9.39.4 @@ -56,7 +56,7 @@ importers: version: 8.57.2(eslint@10.2.0)(typescript@5.6.3) '@vitejs/plugin-basic-ssl': specifier: ^2.3.0 - version: 2.3.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)) + version: 2.3.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0)) eslint: specifier: ^10.2.0 version: 10.2.0 @@ -75,6 +75,9 @@ importers: svelte-eslint-parser: specifier: ^1.6.0 version: 1.6.0(svelte@5.55.1) + tsx: + specifier: ^4.21.0 + version: 4.21.0 typescript: specifier: ~5.6.2 version: 5.6.3 @@ -83,10 +86,10 @@ importers: version: 8.57.2(eslint@10.2.0)(typescript@5.6.3) vite: specifier: ^8.0.3 - version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4) + version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0) vitest: specifier: 3.2.4 - version: 3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0) + version: 3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0) wrangler: specifier: ^4.80.0 version: 4.80.0 @@ -1523,6 +1526,9 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-tsconfig@4.13.7: + resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -1880,6 +1886,9 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + rolldown@1.0.0-rc.12: resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2046,6 +2055,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2333,12 +2347,12 @@ snapshots: optionalDependencies: workerd: 1.20260401.1 - '@cloudflare/vite-plugin@1.31.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4))(workerd@1.20260401.1)(wrangler@4.80.0)': + '@cloudflare/vite-plugin@1.31.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0))(workerd@1.20260401.1)(wrangler@4.80.0)': dependencies: '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260401.1) miniflare: 4.20260401.0 unenv: 2.0.0-rc.24 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0) wrangler: 4.80.0 ws: 8.18.0 transitivePeerDependencies: @@ -2878,14 +2892,14 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4))': + '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 svelte: 5.55.1 - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4) - vitefu: 1.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0) + vitefu: 1.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0)) '@tauri-apps/cli-darwin-arm64@2.10.1': optional: true @@ -2958,14 +2972,14 @@ snapshots: dependencies: svelte: 5.55.1 - '@testing-library/svelte@5.3.1(svelte@5.55.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4))(vitest@3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0))': + '@testing-library/svelte@5.3.1(svelte@5.55.1)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0))(vitest@3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0))': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/svelte-core': 1.0.0(svelte@5.55.1) svelte: 5.55.1 optionalDependencies: - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4) - vitest: 3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0) + vitest: 3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -3088,9 +3102,9 @@ snapshots: '@typescript-eslint/types': 8.57.2 eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-basic-ssl@2.3.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4))': + '@vitejs/plugin-basic-ssl@2.3.0(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0))': dependencies: - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0) '@vitest/expect@3.2.4': dependencies: @@ -3100,13 +3114,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0))': + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0)(tsx@4.21.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0) + vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0)(tsx@4.21.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -3440,6 +3454,10 @@ snapshots: fsevents@2.3.3: optional: true + get-tsconfig@4.13.7: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -3743,6 +3761,8 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + resolve-pkg-maps@1.0.0: {} + rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1): dependencies: '@oxc-project/types': 0.122.0 @@ -3966,6 +3986,13 @@ snapshots: tslib@2.8.1: optional: true + tsx@4.21.0: + dependencies: + esbuild: 0.27.4 + get-tsconfig: 4.13.7 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -3999,13 +4026,13 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@3.2.4(@types/node@25.5.0)(lightningcss@1.32.0): + vite-node@3.2.4(@types/node@25.5.0)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0) + vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0)(tsx@4.21.0) transitivePeerDependencies: - '@types/node' - jiti @@ -4020,7 +4047,7 @@ snapshots: - tsx - yaml - vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0): + vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) @@ -4032,8 +4059,9 @@ snapshots: '@types/node': 25.5.0 fsevents: 2.3.3 lightningcss: 1.32.0 + tsx: 4.21.0 - vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4): + vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -4044,19 +4072,20 @@ snapshots: '@types/node': 25.5.0 esbuild: 0.27.4 fsevents: 2.3.3 + tsx: 4.21.0 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' - vitefu@1.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)): + vitefu@1.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0)): optionalDependencies: - vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4) + vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)(tsx@4.21.0) - vitest@3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0): + vitest@3.2.4(@types/node@25.5.0)(jsdom@26.1.0)(lightningcss@1.32.0)(tsx@4.21.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@25.5.0)(lightningcss@1.32.0)(tsx@4.21.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -4074,8 +4103,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0) - vite-node: 3.2.4(@types/node@25.5.0)(lightningcss@1.32.0) + vite: 7.3.1(@types/node@25.5.0)(lightningcss@1.32.0)(tsx@4.21.0) + vite-node: 3.2.4(@types/node@25.5.0)(lightningcss@1.32.0)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.5.0 diff --git a/spacetimedb/src/index.ts b/spacetimedb/src/index.ts index abd29ec..ac75c17 100644 --- a/spacetimedb/src/index.ts +++ b/spacetimedb/src/index.ts @@ -1,4 +1,4 @@ -import { schema, t, table, SenderError, type ReducerCtx } from "spacetimedb/server"; +import { schema, t, table, SenderError } from "spacetimedb/server"; const channel_kind = t.enum("ChannelKind", { Text: t.unit(), Voice: t.unit() }); @@ -589,7 +589,7 @@ export const delete_server = spacetimedb.reducer( (ctx, { serverId }) => { const s = ctx.db.server.id.find(serverId); if (!s) throw new SenderError("Server not found"); - + /* owner check temporarily disabled */ /* if (s.owner?.toHexString() !== ctx.sender.toHexString()) { throw new SenderError("Only the server owner can delete the server"); diff --git a/src/chat/components/MessageItem.svelte b/src/chat/components/MessageItem.svelte index 25a40a2..3eb5868 100644 --- a/src/chat/components/MessageItem.svelte +++ b/src/chat/components/MessageItem.svelte @@ -27,11 +27,11 @@ let collapsedImages = $state(false); let showPicker = $state(false); - let pickerPos = $state<{ - x: number, - y: number, - vertical: 'top' | 'bottom', - horizontal: 'left' | 'right' + let pickerPos = $state<{ + x: number, + y: number, + vertical: 'top' | 'bottom', + horizontal: 'left' | 'right' } | null>(null); let tooltip = $state<{ @@ -135,13 +135,13 @@ const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); const pickerWidth = 320; const pickerHeight = 400; - + const spaceBelow = window.innerHeight - rect.bottom; const vertical = spaceBelow < pickerHeight ? 'top' : 'bottom'; - + // Default: thread grows right, main grows left let horizontal: 'left' | 'right' = isThread ? 'right' : 'left'; - + if (isThread) { const spaceRight = window.innerWidth - rect.left; if (spaceRight < pickerWidth) { diff --git a/src/chat/components/MessageList.svelte b/src/chat/components/MessageList.svelte index 515f006..04802f9 100644 --- a/src/chat/components/MessageList.svelte +++ b/src/chat/components/MessageList.svelte @@ -75,7 +75,7 @@ if (isAtBottom) { scrollContainer!.scrollTop = target.scrollHeight; } - + lastHeight = newHeight; } } diff --git a/src/chat/components/ProfileModal.svelte b/src/chat/components/ProfileModal.svelte index c185724..8692269 100644 --- a/src/chat/components/ProfileModal.svelte +++ b/src/chat/components/ProfileModal.svelte @@ -67,8 +67,8 @@ {:else} -
isMe && (isEditingStatus = true)} > {user.status || (isMe ? "Click to set a status..." : "")} diff --git a/src/chat/components/RichText.svelte b/src/chat/components/RichText.svelte index bdaed89..87da8d5 100644 --- a/src/chat/components/RichText.svelte +++ b/src/chat/components/RichText.svelte @@ -136,7 +136,7 @@ chat.ui.embedCollapsedStates.set(key, !currentState); } - function onImageLoad(e: Event) { + function onImageLoad(_e: Event) { if (onLoad) { onLoad(); } diff --git a/src/chat/components/ServerSettingsPanel.svelte b/src/chat/components/ServerSettingsPanel.svelte index 2e085a3..2b30632 100644 --- a/src/chat/components/ServerSettingsPanel.svelte +++ b/src/chat/components/ServerSettingsPanel.svelte @@ -205,7 +205,7 @@