@vex-chat/spire 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CLA.md +38 -0
  2. package/LICENSE-COMMERCIAL +10 -0
  3. package/LICENSING.md +15 -0
  4. package/README.md +15 -13
  5. package/dist/ClientManager.d.ts +5 -0
  6. package/dist/ClientManager.js +6 -2
  7. package/dist/ClientManager.js.map +1 -1
  8. package/dist/Database.d.ts +29 -1
  9. package/dist/Database.js +206 -64
  10. package/dist/Database.js.map +1 -1
  11. package/dist/Spire.d.ts +5 -0
  12. package/dist/Spire.js +86 -19
  13. package/dist/Spire.js.map +1 -1
  14. package/dist/db/schema.d.ts +5 -0
  15. package/dist/db/schema.js +5 -0
  16. package/dist/db/schema.js.map +1 -1
  17. package/dist/index.d.ts +5 -0
  18. package/dist/index.js +5 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/middleware/validate.d.ts +5 -0
  21. package/dist/middleware/validate.js +5 -0
  22. package/dist/middleware/validate.js.map +1 -1
  23. package/dist/migrations/2026-04-06_initial-schema.d.ts +5 -0
  24. package/dist/migrations/2026-04-06_initial-schema.js +5 -0
  25. package/dist/migrations/2026-04-06_initial-schema.js.map +1 -1
  26. package/dist/migrations/2026-04-14_argon2id-password-hashing.d.ts +5 -0
  27. package/dist/migrations/2026-04-14_argon2id-password-hashing.js +5 -0
  28. package/dist/migrations/2026-04-14_argon2id-password-hashing.js.map +1 -1
  29. package/dist/run.d.ts +5 -0
  30. package/dist/run.js +5 -0
  31. package/dist/run.js.map +1 -1
  32. package/dist/server/avatar.d.ts +5 -0
  33. package/dist/server/avatar.js +5 -0
  34. package/dist/server/avatar.js.map +1 -1
  35. package/dist/server/errors.d.ts +5 -0
  36. package/dist/server/errors.js +5 -0
  37. package/dist/server/errors.js.map +1 -1
  38. package/dist/server/file.d.ts +5 -0
  39. package/dist/server/file.js +5 -0
  40. package/dist/server/file.js.map +1 -1
  41. package/dist/server/index.d.ts +5 -0
  42. package/dist/server/index.js +20 -2
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/invite.d.ts +5 -0
  45. package/dist/server/invite.js +5 -0
  46. package/dist/server/invite.js.map +1 -1
  47. package/dist/server/openapi.d.ts +5 -0
  48. package/dist/server/openapi.js +5 -0
  49. package/dist/server/openapi.js.map +1 -1
  50. package/dist/server/permissions.d.ts +5 -0
  51. package/dist/server/permissions.js +5 -0
  52. package/dist/server/permissions.js.map +1 -1
  53. package/dist/server/rateLimit.d.ts +15 -0
  54. package/dist/server/rateLimit.js +56 -0
  55. package/dist/server/rateLimit.js.map +1 -1
  56. package/dist/server/user.d.ts +5 -0
  57. package/dist/server/user.js +12 -3
  58. package/dist/server/user.js.map +1 -1
  59. package/dist/server/utils.d.ts +5 -0
  60. package/dist/server/utils.js +5 -0
  61. package/dist/server/utils.js.map +1 -1
  62. package/dist/types/express.d.ts +5 -0
  63. package/dist/types/express.js +5 -0
  64. package/dist/types/express.js.map +1 -1
  65. package/dist/utils/createUint8UUID.d.ts +5 -0
  66. package/dist/utils/createUint8UUID.js +5 -0
  67. package/dist/utils/createUint8UUID.js.map +1 -1
  68. package/dist/utils/jwtSecret.d.ts +5 -0
  69. package/dist/utils/jwtSecret.js +5 -0
  70. package/dist/utils/jwtSecret.js.map +1 -1
  71. package/dist/utils/loadEnv.d.ts +5 -0
  72. package/dist/utils/loadEnv.js +5 -0
  73. package/dist/utils/loadEnv.js.map +1 -1
  74. package/dist/utils/msgpack.d.ts +5 -0
  75. package/dist/utils/msgpack.js +5 -0
  76. package/dist/utils/msgpack.js.map +1 -1
  77. package/package.json +18 -8
  78. package/src/ClientManager.ts +7 -2
  79. package/src/Database.ts +231 -68
  80. package/src/Spire.ts +91 -23
  81. package/src/__tests__/Database.spec.ts +6 -0
  82. package/src/ambient-modules.d.ts +6 -0
  83. package/src/db/schema.ts +6 -0
  84. package/src/index.ts +6 -0
  85. package/src/middleware/validate.ts +6 -0
  86. package/src/migrations/2026-04-06_initial-schema.ts +6 -0
  87. package/src/migrations/2026-04-14_argon2id-password-hashing.ts +6 -0
  88. package/src/run.ts +6 -0
  89. package/src/server/avatar.ts +6 -0
  90. package/src/server/errors.ts +6 -0
  91. package/src/server/file.ts +6 -0
  92. package/src/server/index.ts +22 -2
  93. package/src/server/invite.ts +6 -0
  94. package/src/server/openapi.ts +6 -0
  95. package/src/server/permissions.ts +6 -0
  96. package/src/server/rateLimit.ts +43 -3
  97. package/src/server/user.ts +13 -3
  98. package/src/server/utils.ts +6 -0
  99. package/src/types/express.ts +6 -0
  100. package/src/utils/createUint8UUID.ts +6 -0
  101. package/src/utils/jwtSecret.ts +6 -0
  102. package/src/utils/loadEnv.ts +6 -0
  103. package/src/utils/msgpack.ts +6 -0
  104. package/dist/__tests__/Database.spec.d.ts +0 -1
  105. package/dist/__tests__/Database.spec.js +0 -122
  106. package/dist/__tests__/Database.spec.js.map +0 -1
package/CLA.md ADDED
@@ -0,0 +1,38 @@
1
+ # Contributor License Agreement (Individual)
2
+
3
+ Thank you for your interest in contributing to this project (“**Project**”).
4
+
5
+ This Contributor License Agreement (“**Agreement**”) documents the rights granted by contributors to the Project’s copyright holders and maintainers (collectively, the “**Maintainers**”).
6
+
7
+ ## Definitions
8
+
9
+ **“Contribution”** means any original work of authorship, including any modifications or additions to the Project, that you submit or have submitted to the Project in any form (for example, pull requests, issues with attached patches, or mailing-list posts).
10
+
11
+ **“Submit”** means any form of electronic or written communication sent to the Maintainers or Project repositories.
12
+
13
+ ## Grant of rights
14
+
15
+ You hereby grant the Maintainers:
16
+
17
+ 1. **Copyright license** — A perpetual, worldwide, non-exclusive, royalty-free, irrevocable license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute your Contributions under the **AGPL-3.0-or-later** license that applies to the Project.
18
+
19
+ 2. **Relicensing for dual licensing** — The same rights as in (1), but under **other license terms** chosen by the Maintainers, solely as needed to offer **commercial or proprietary licenses** to third parties, without owing you royalties for such sublicensing.
20
+
21
+ 3. **Patent license** — If your Contribution includes a patent claim that you have the right to license, you grant the Maintainers and users of the Project a perpetual, worldwide, non-exclusive, royalty-free license under those claims to make, use, sell, offer for sale, import, and otherwise dispose of the Contribution and works including it.
22
+
23
+ ## Representations
24
+
25
+ You represent that:
26
+
27
+ - Each Contribution is your original creation, or you have sufficient rights to submit it under this Agreement.
28
+ - To your knowledge, your Contribution does not violate anyone else’s rights.
29
+
30
+ ## Miscellaneous
31
+
32
+ This Agreement does not obligate the Maintainers to use or merge your Contributions.
33
+
34
+ If any part of this Agreement is held invalid, the remainder stays in effect.
35
+
36
+ ---
37
+
38
+ _This document is a practical starting point for open-source contribution. It is **not** legal advice. Have qualified counsel review and adapt it for your jurisdiction and entity before relying on it for government, procurement, or high-stakes decisions._
@@ -0,0 +1,10 @@
1
+ # Commercial licensing
2
+
3
+ This software is also available under **commercial license terms** from:
4
+
5
+ **Vex Heavy Industries LLC**
6
+
7
+ - **Email:** [licensing@vex.wtf](mailto:yuki@vex.wtf)
8
+ - **Web:** [vex.wtf](https://vex.wtf)
9
+
10
+ The default **public** license for open source use is the **GNU Affero General Public License v3.0** — see the [`LICENSE`](./LICENSE) file in this repository.
package/LICENSING.md ADDED
@@ -0,0 +1,15 @@
1
+ # Licensing
2
+
3
+ ## Open source (default)
4
+
5
+ The default **public** license is the **GNU Affero General Public License v3.0** — see the full text in [`LICENSE`](./LICENSE). The SPDX identifier in `package.json` may use **AGPL-3.0-or-later**; either way, the grant is at least AGPL v3.
6
+
7
+ ## Commercial licensing
8
+
9
+ Commercial licenses are offered by **Vex Heavy Industries LLC**. See [`LICENSE-COMMERCIAL`](./LICENSE-COMMERCIAL) for contact details ([vex.wtf](https://vex.wtf), **licensing@vex.wtf**).
10
+
11
+ If you need terms **other than the AGPL** (for example, a proprietary product or a deployment where AGPL obligations are not acceptable), inquire there.
12
+
13
+ ---
14
+
15
+ _This file summarizes how we intend licensing to work. It is not legal advice. For binding terms, rely on the written agreement or license grant you receive from the copyright holders._
package/README.md CHANGED
@@ -58,20 +58,21 @@ Spire reads configuration from environment variables. **Docker Compose:** put th
58
58
 
59
59
  ### Required
60
60
 
61
- | Variable | Description |
62
- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
- | `SPK` | Server private key, hex-encoded. Generate with `npm run gen-spk` (prints `SPK` and `JWT_SECRET` lines). Used for server identity signing (NaCl). |
64
- | `JWT_SECRET` | Hex or string used as the **HMAC secret for JWTs** — **required** and must be **separate from `SPK`**. `npm run gen-spk` emits a dedicated value; do not reuse `SPK` here. |
65
- | `DB_TYPE` | `sqlite`, `sqlite3`, or `sqlite3mem`. All values use **SQLite** via `better-sqlite3` (file or `:memory:`). `sqlite3mem` is for tests. The string `mysql` is still accepted for compatibility but maps to the same SQLite setup as the default (there is no MySQL driver). |
61
+ | Variable | Description |
62
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `SPK` | Server private key, hex-encoded. Generate with `npm run gen-spk` (prints `SPK` and `JWT_SECRET` lines). Used for server identity signing (NaCl). |
64
+ | `JWT_SECRET` | Hex or string used as the **HMAC secret for JWTs** — **required** and must be **separate from `SPK`**. `npm run gen-spk` emits a dedicated value; do not reuse `SPK` here. |
65
+ | `DB_TYPE` | `sqlite3` or `sqlite3mem`. All values use **SQLite** via `better-sqlite3` (file or `:memory:`). |
66
66
 
67
67
  ### Optional
68
68
 
69
- | Variable | Default | Description |
70
- | -------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
71
- | `API_PORT` | `16777` | Port for the REST API and WebSocket server (see `Spire` default in code if unset). |
72
- | `NODE_ENV` | _(unset)_ | Set to `production` to disable interactive `/docs` / `/async-docs`. If unset or any other value, doc viewers are mounted. `helmet()` runs in all modes. |
73
- | `CORS_ORIGINS` | _(empty)_ | Comma-separated allowed `Origin` values for `cors`. If unset or empty, **no cross-origin browser access** (`origin: false`). |
74
- | `CANARY` | _(unset)_ | |
69
+ | Variable | Default | Description |
70
+ | -------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
71
+ | `API_PORT` | `16777` | Port for the REST API and WebSocket server (see `Spire` default in code if unset). |
72
+ | `NODE_ENV` | _(unset)_ | Set to `production` to disable interactive `/docs` / `/async-docs`. If unset or any other value, doc viewers are mounted. `helmet()` runs in all modes. |
73
+ | `CORS_ORIGINS` | _(empty)_ | Comma-separated allowed `Origin` values. If set, only those origins may use credentialed browser requests. If unset, Spire **reflects the request `Origin`** so self-hosted Spire and arbitrary app origins (Tauri, localhost, etc.) work without configuration — appropriate for bearer-token APIs; set an allowlist if you need to restrict which sites may call your instance from users' browsers. |
74
+ | `DEV_API_KEY` | _(empty)_ | When set, requests that send header `x-dev-api-key` with the same value (constant-time compare) **skip in-process rate limiters**. The same gate enables **`GET /status/process`** (404 without a valid key): a small JSON snapshot of the Spire Node process (PID, uptime, `memoryUsage`, cumulative `resourceUsage`, WebSocket client count). Dev / load-testing only — never set in production. |
75
+ | `CANARY` | _(unset)_ | |
75
76
 
76
77
  ### Sample `.env`
77
78
 
@@ -95,11 +96,12 @@ npm run format # prettier --write
95
96
  npm run format:check
96
97
  npm test # vitest run
97
98
  npx type-coverage # type-coverage (≥95%)
98
- npm run license:check # license allowlist gate
99
99
  ```
100
100
 
101
101
  See [AGENTS.md](./AGENTS.md) for the release flow (changesets → publish → deploy-hook) and the rules for writing changesets.
102
102
 
103
+ Contributions from outside contributors should follow [CONTRIBUTING.md](./CONTRIBUTING.md) (including the [CLA](./CLA.md)).
104
+
103
105
  ## License
104
106
 
105
- [AGPL-3.0-or-later](./LICENSE)
107
+ Open source default: **[AGPL-3.0](./LICENSE)** (full text; see `package.json` for SPDX). Commercial licenses from **Vex Heavy Industries LLC**: [**LICENSE-COMMERCIAL**](./LICENSE-COMMERCIAL), [**LICENSING.md**](./LICENSING.md), [vex.wtf](https://vex.wtf).
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
+ * Licensed under AGPL-3.0. See LICENSE for details.
4
+ * Commercial licenses available at vex.wtf
5
+ */
1
6
  import type { Database } from "./Database.ts";
2
7
  import type { BaseMsg, Device, User, UserRecord } from "@vex-chat/types";
3
8
  import type WebSocket from "ws";
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
+ * Licensed under AGPL-3.0. See LICENSE for details.
4
+ * Commercial licenses available at vex.wtf
5
+ */
1
6
  import { EventEmitter } from "events";
2
7
  import { setTimeout as sleep } from "node:timers/promises";
3
8
  import { xConcat, XUtils } from "@vex-chat/crypto";
@@ -191,8 +196,7 @@ export class ClientManager extends EventEmitter {
191
196
  this.send(p);
192
197
  }
193
198
  async pingLoop() {
194
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop
195
- while (true) {
199
+ while (!this.failed) {
196
200
  this.ping();
197
201
  await sleep(5000);
198
202
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ClientManager.js","sourceRoot":"","sources":["../src/ClientManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ClientManager.js","sourceRoot":"","sources":["../src/ClientManager.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
+ * Licensed under AGPL-3.0. See LICENSE for details.
4
+ * Commercial licenses available at vex.wtf
5
+ */
1
6
  import type { SpireOptions } from "./Spire.ts";
2
7
  import type { Channel, Device, DevicePayload, Emoji, FileSQL, Invite, KeyBundle, MailWS, Permission, PreKeysWS, RegistrationPayload, Server, UserRecord } from "@vex-chat/types";
3
8
  import { EventEmitter } from "events";
@@ -7,7 +12,30 @@ export interface InternalUserRecord extends UserRecord {
7
12
  }
8
13
  export declare class Database extends EventEmitter {
9
14
  private db;
15
+ /** Underlying better-sqlite3 handle (file or :memory:). */
16
+ private readonly rawSqlite;
17
+ /**
18
+ * In-process cache for {@link retrieveUserDeviceList} when queried for a
19
+ * single owner. Libvex calls GET /user/:id/devices on every outgoing
20
+ * `forward()`; stress runs otherwise hammer SQLite with identical reads.
21
+ * Cleared on device create/delete and when the DB is closed.
22
+ */
23
+ private readonly userDeviceListCache;
24
+ /**
25
+ * Short TTL cache for {@link retrieveEmojiList} (key = server id, stored as
26
+ * `emojis.owner`). The noise harness fires `emoji.retrieveList` as a
27
+ * follow-up on ~1/25 successful ops, so this path can contend on SQLite
28
+ * before heavier surfaces. Invalidated on emoji create/delete.
29
+ */
30
+ private readonly emojiListByServerCache;
31
+ private static readonly USER_DEVICE_LIST_CACHE_TTL_MS;
32
+ private static readonly EMOJI_LIST_CACHE_TTL_MS;
10
33
  constructor(options?: SpireOptions);
34
+ /**
35
+ * Dev-only snapshot of SQLite files + pragmas (same process as Spire).
36
+ * Not for production callers.
37
+ */
38
+ getDevSqliteMonitor(): Record<string, unknown>;
11
39
  close(): Promise<void>;
12
40
  createChannel(name: string, serverID: string): Promise<Channel>;
13
41
  createDevice(owner: string, payload: DevicePayload): Promise<Device>;
@@ -46,7 +74,7 @@ export declare class Database extends EventEmitter {
46
74
  retrieveChannels(serverID: string): Promise<Channel[]>;
47
75
  retrieveDevice(deviceID: string): Promise<Device | null>;
48
76
  retrieveEmoji(emojiID: string): Promise<Emoji | null>;
49
- retrieveEmojiList(userID: string): Promise<Emoji[]>;
77
+ retrieveEmojiList(serverID: string): Promise<Emoji[]>;
50
78
  retrieveFile(fileID: string): Promise<FileSQL | null>;
51
79
  retrieveGroupMembers(channelID: string): Promise<UserRecord[]>;
52
80
  retrieveInvite(inviteID: string): Promise<Invite | null>;
package/dist/Database.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Copyright (c) 2020-2026 Vex Heavy Industries LLC
3
+ * Licensed under AGPL-3.0. See LICENSE for details.
4
+ * Commercial licenses available at vex.wtf
5
+ */
1
6
  var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
7
  if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
8
  return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
@@ -8,6 +13,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
8
13
  };
9
14
  import { EventEmitter } from "events";
10
15
  import { pbkdf2Sync } from "node:crypto";
16
+ import { statSync } from "node:fs";
11
17
  import * as fs from "node:fs/promises";
12
18
  import path from "node:path";
13
19
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -84,6 +90,24 @@ const pubkeyRegex = /[0-9a-f]{64}/;
84
90
  const PBKDF2_ITERATIONS = 1000;
85
91
  export class Database extends EventEmitter {
86
92
  db;
93
+ /** Underlying better-sqlite3 handle (file or :memory:). */
94
+ rawSqlite;
95
+ /**
96
+ * In-process cache for {@link retrieveUserDeviceList} when queried for a
97
+ * single owner. Libvex calls GET /user/:id/devices on every outgoing
98
+ * `forward()`; stress runs otherwise hammer SQLite with identical reads.
99
+ * Cleared on device create/delete and when the DB is closed.
100
+ */
101
+ userDeviceListCache = new Map();
102
+ /**
103
+ * Short TTL cache for {@link retrieveEmojiList} (key = server id, stored as
104
+ * `emojis.owner`). The noise harness fires `emoji.retrieveList` as a
105
+ * follow-up on ~1/25 successful ops, so this path can contend on SQLite
106
+ * before heavier surfaces. Invalidated on emoji create/delete.
107
+ */
108
+ emojiListByServerCache = new Map();
109
+ static USER_DEVICE_LIST_CACHE_TTL_MS = 10_000;
110
+ static EMOJI_LIST_CACHE_TTL_MS = 10_000;
87
111
  constructor(options) {
88
112
  super();
89
113
  const dbType = options?.dbType || "sqlite3";
@@ -100,19 +124,78 @@ export class Database extends EventEmitter {
100
124
  filename = "spire.sqlite";
101
125
  break;
102
126
  }
103
- const sqliteDb = new BetterSqlite3(filename);
104
- sqliteDb.pragma("journal_mode = WAL");
105
- sqliteDb.pragma("synchronous = NORMAL");
106
- sqliteDb.pragma("busy_timeout = 5000");
107
- sqliteDb.pragma("cache_size = -64000");
108
- sqliteDb.pragma("temp_store = memory");
109
- sqliteDb.pragma("foreign_keys = ON");
127
+ this.rawSqlite = new BetterSqlite3(filename);
128
+ this.rawSqlite.pragma("journal_mode = WAL");
129
+ this.rawSqlite.pragma("synchronous = NORMAL");
130
+ this.rawSqlite.pragma("busy_timeout = 5000");
131
+ this.rawSqlite.pragma("cache_size = -64000");
132
+ this.rawSqlite.pragma("temp_store = memory");
133
+ this.rawSqlite.pragma("foreign_keys = ON");
110
134
  this.db = new Kysely({
111
- dialect: new SqliteDialect({ database: sqliteDb }),
135
+ dialect: new SqliteDialect({ database: this.rawSqlite }),
112
136
  });
113
137
  void this.init();
114
138
  }
139
+ /**
140
+ * Dev-only snapshot of SQLite files + pragmas (same process as Spire).
141
+ * Not for production callers.
142
+ */
143
+ getDevSqliteMonitor() {
144
+ const openedAs = this.rawSqlite.name;
145
+ const absPath = openedAs === ":memory:"
146
+ ? ":memory:"
147
+ : path.isAbsolute(openedAs)
148
+ ? openedAs
149
+ : path.resolve(process.cwd(), openedAs);
150
+ const journalMode = this.rawSqlite.pragma("journal_mode", {
151
+ simple: true,
152
+ });
153
+ const synchronous = this.rawSqlite.pragma("synchronous", {
154
+ simple: true,
155
+ });
156
+ const busyTimeout = this.rawSqlite.pragma("busy_timeout", {
157
+ simple: true,
158
+ });
159
+ const cacheSize = this.rawSqlite.pragma("cache_size", { simple: true });
160
+ const pageCount = this.rawSqlite.pragma("page_count", { simple: true });
161
+ const pageSize = this.rawSqlite.pragma("page_size", { simple: true });
162
+ const freelistCount = this.rawSqlite.pragma("freelist_count", {
163
+ simple: true,
164
+ });
165
+ const out = {
166
+ absPath,
167
+ busyTimeout,
168
+ cacheSize,
169
+ freelistCount,
170
+ journalMode,
171
+ openedAs,
172
+ pageCount,
173
+ pageSize,
174
+ synchronous,
175
+ };
176
+ if (openedAs !== ":memory:") {
177
+ const filePaths = {
178
+ main: absPath,
179
+ shm: `${absPath}-shm`,
180
+ wal: `${absPath}-wal`,
181
+ };
182
+ out["filePaths"] = filePaths;
183
+ const sizes = {};
184
+ for (const [label, fp] of Object.entries(filePaths)) {
185
+ try {
186
+ sizes[label] = statSync(fp).size;
187
+ }
188
+ catch {
189
+ sizes[label] = 0;
190
+ }
191
+ }
192
+ out["fileBytes"] = sizes;
193
+ }
194
+ return out;
195
+ }
115
196
  async close() {
197
+ this.userDeviceListCache.clear();
198
+ this.emojiListByServerCache.clear();
116
199
  await this.db.destroy();
117
200
  }
118
201
  async createChannel(name, serverID) {
@@ -134,6 +217,7 @@ export class Database extends EventEmitter {
134
217
  signKey: payload.signKey,
135
218
  };
136
219
  await this.db.insertInto("devices").values(device).execute();
220
+ this.userDeviceListCache.delete(owner);
137
221
  const medPreKeys = {
138
222
  deviceID: device.deviceID,
139
223
  index: payload.preKeyIndex,
@@ -147,6 +231,7 @@ export class Database extends EventEmitter {
147
231
  }
148
232
  async createEmoji(emoji) {
149
233
  await this.db.insertInto("emojis").values(emoji).execute();
234
+ this.emojiListByServerCache.delete(emoji.owner);
150
235
  }
151
236
  async createFile(file) {
152
237
  await this.db.insertInto("files").values(file).execute();
@@ -162,27 +247,29 @@ export class Database extends EventEmitter {
162
247
  return invite;
163
248
  }
164
249
  async createPermission(userID, resourceType, resourceID, powerLevel) {
165
- const permissionID = crypto.randomUUID();
166
- // check if it already exists
167
- const checkPermission = await this.db
168
- .selectFrom("permissions")
169
- .selectAll()
170
- .where("userID", "=", userID)
171
- .where("resourceID", "=", resourceID)
172
- .execute();
173
- const existing = checkPermission[0];
174
- if (existing) {
175
- return existing;
176
- }
177
- const permission = {
178
- permissionID,
179
- powerLevel,
180
- resourceID,
181
- resourceType,
182
- userID,
183
- };
184
- await this.db.insertInto("permissions").values(permission).execute();
185
- return permission;
250
+ // Atomic check-then-insert inside a transaction so two concurrent
251
+ // callers cannot both find "no existing" and insert duplicates.
252
+ return this.db.transaction().execute(async (trx) => {
253
+ const checkPermission = await trx
254
+ .selectFrom("permissions")
255
+ .selectAll()
256
+ .where("userID", "=", userID)
257
+ .where("resourceID", "=", resourceID)
258
+ .execute();
259
+ const existing = checkPermission[0];
260
+ if (existing) {
261
+ return existing;
262
+ }
263
+ const permission = {
264
+ permissionID: crypto.randomUUID(),
265
+ powerLevel,
266
+ resourceID,
267
+ resourceType,
268
+ userID,
269
+ };
270
+ await trx.insertInto("permissions").values(permission).execute();
271
+ return permission;
272
+ });
186
273
  }
187
274
  async createServer(name, ownerID) {
188
275
  // create the server
@@ -241,6 +328,11 @@ export class Database extends EventEmitter {
241
328
  .execute();
242
329
  }
243
330
  async deleteDevice(deviceID) {
331
+ const ownerRow = await this.db
332
+ .selectFrom("devices")
333
+ .select("owner")
334
+ .where("deviceID", "=", deviceID)
335
+ .executeTakeFirst();
244
336
  await this.db
245
337
  .deleteFrom("preKeys")
246
338
  .where("deviceID", "=", deviceID)
@@ -254,12 +346,19 @@ export class Database extends EventEmitter {
254
346
  .set({ deleted: 1 })
255
347
  .where("deviceID", "=", deviceID)
256
348
  .execute();
349
+ if (ownerRow?.owner) {
350
+ this.userDeviceListCache.delete(ownerRow.owner);
351
+ }
257
352
  }
258
353
  async deleteEmoji(emojiID) {
354
+ const existing = await this.retrieveEmoji(emojiID);
259
355
  await this.db
260
356
  .deleteFrom("emojis")
261
357
  .where("emojiID", "=", emojiID)
262
358
  .execute();
359
+ if (existing) {
360
+ this.emojiListByServerCache.delete(existing.owner);
361
+ }
263
362
  }
264
363
  async deleteInvite(inviteID) {
265
364
  await this.db
@@ -315,30 +414,32 @@ export class Database extends EventEmitter {
315
414
  return keyBundle;
316
415
  }
317
416
  async getOTK(deviceID) {
318
- const rows = await this.db
319
- .selectFrom("oneTimeKeys")
320
- .selectAll()
321
- .where("deviceID", "=", deviceID)
322
- .orderBy("index")
323
- .limit(1)
324
- .execute();
325
- const otkInfo = rows[0];
326
- if (!otkInfo) {
327
- return null;
328
- }
329
- const otk = {
330
- deviceID: otkInfo.deviceID,
331
- index: otkInfo.index,
332
- publicKey: XUtils.decodeHex(otkInfo.publicKey),
333
- signature: XUtils.decodeHex(otkInfo.signature),
334
- };
335
- // delete the otk
336
- await this.db
337
- .deleteFrom("oneTimeKeys")
338
- .where("deviceID", "=", deviceID)
339
- .where("index", "=", otk.index)
340
- .execute();
341
- return otk;
417
+ // Atomic select-then-delete inside a transaction so two concurrent
418
+ // callers cannot dispense the same one-time key.
419
+ return this.db.transaction().execute(async (trx) => {
420
+ const rows = await trx
421
+ .selectFrom("oneTimeKeys")
422
+ .selectAll()
423
+ .where("deviceID", "=", deviceID)
424
+ .orderBy("index")
425
+ .limit(1)
426
+ .execute();
427
+ const otkInfo = rows[0];
428
+ if (!otkInfo) {
429
+ return null;
430
+ }
431
+ await trx
432
+ .deleteFrom("oneTimeKeys")
433
+ .where("deviceID", "=", deviceID)
434
+ .where("index", "=", otkInfo.index)
435
+ .execute();
436
+ return {
437
+ deviceID: otkInfo.deviceID,
438
+ index: otkInfo.index,
439
+ publicKey: XUtils.decodeHex(otkInfo.publicKey),
440
+ signature: XUtils.decodeHex(otkInfo.signature),
441
+ };
442
+ });
342
443
  }
343
444
  async getOTKCount(deviceID) {
344
445
  const result = await this.db
@@ -491,12 +592,22 @@ export class Database extends EventEmitter {
491
592
  .execute();
492
593
  return rows[0] ?? null;
493
594
  }
494
- async retrieveEmojiList(userID) {
495
- return this.db
595
+ async retrieveEmojiList(serverID) {
596
+ const now = Date.now();
597
+ const hit = this.emojiListByServerCache.get(serverID);
598
+ if (hit && hit.until > now) {
599
+ return hit.rows.map((r) => ({ ...r }));
600
+ }
601
+ const rows = await this.db
496
602
  .selectFrom("emojis")
497
603
  .selectAll()
498
- .where("owner", "=", userID)
604
+ .where("owner", "=", serverID)
499
605
  .execute();
606
+ this.emojiListByServerCache.set(serverID, {
607
+ rows: rows.map((r) => ({ ...r })),
608
+ until: now + Database.EMOJI_LIST_CACHE_TTL_MS,
609
+ });
610
+ return rows.map((r) => ({ ...r }));
500
611
  }
501
612
  async retrieveFile(fileID) {
502
613
  const file = await this.db
@@ -608,14 +719,25 @@ export class Database extends EventEmitter {
608
719
  .selectAll()
609
720
  .where("serverID", "=", serverID)
610
721
  .execute();
611
- return rows.filter((invite) => {
612
- const valid = new Date(Date.now()).getTime() <
613
- new Date(invite.expiration).getTime();
614
- if (!valid) {
615
- void this.deleteInvite(invite.inviteID);
722
+ const nowMs = Date.now();
723
+ const expiredIds = [];
724
+ const valid = [];
725
+ for (const row of rows) {
726
+ const expMs = new Date(row.expiration).getTime();
727
+ if (!Number.isFinite(expMs) || expMs <= nowMs) {
728
+ expiredIds.push(row.inviteID);
729
+ continue;
616
730
  }
617
- return valid;
618
- });
731
+ valid.push(row);
732
+ }
733
+ if (expiredIds.length > 0) {
734
+ await this.db
735
+ .deleteFrom("invites")
736
+ .where("serverID", "=", serverID)
737
+ .where("inviteID", "in", expiredIds)
738
+ .execute();
739
+ }
740
+ return valid;
619
741
  }
620
742
  async retrieveServers(userID) {
621
743
  const serverPerms = await this.retrievePermissions(userID, "server");
@@ -651,13 +773,33 @@ export class Database extends EventEmitter {
651
773
  return row ? toUserRecord(row) : null;
652
774
  }
653
775
  async retrieveUserDeviceList(userIDs) {
776
+ const now = Date.now();
777
+ if (userIDs.length === 1) {
778
+ const owner = userIDs[0];
779
+ if (typeof owner === "string") {
780
+ const hit = this.userDeviceListCache.get(owner);
781
+ if (hit && hit.until > now) {
782
+ return hit.devices.map((d) => ({ ...d }));
783
+ }
784
+ }
785
+ }
654
786
  const rows = await this.db
655
787
  .selectFrom("devices")
656
788
  .selectAll()
657
789
  .where("owner", "in", userIDs)
658
790
  .where("deleted", "=", 0)
659
791
  .execute();
660
- return rows.map(toDevice);
792
+ const devices = rows.map(toDevice);
793
+ if (userIDs.length === 1) {
794
+ const owner = userIDs[0];
795
+ if (typeof owner === "string") {
796
+ this.userDeviceListCache.set(owner, {
797
+ devices: devices.map((d) => ({ ...d })),
798
+ until: now + Database.USER_DEVICE_LIST_CACHE_TTL_MS,
799
+ });
800
+ }
801
+ }
802
+ return devices;
661
803
  }
662
804
  async retrieveUsers() {
663
805
  const rows = await this.db.selectFrom("users").selectAll().execute();