mindoodb 0.0.3 → 0.0.5

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 (214) hide show
  1. package/README-server.md +1030 -0
  2. package/README.md +14 -26
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +27 -0
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  5. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +270 -121
  6. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  7. package/dist/appendonlystores/network/HttpTransport.d.ts +15 -0
  8. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  9. package/dist/appendonlystores/network/HttpTransport.js +235 -10
  10. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  11. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +5 -0
  12. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  13. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +40 -4
  14. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  15. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts +10 -3
  16. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts.map +1 -1
  17. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js +138 -13
  18. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js.map +1 -1
  19. package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts +21 -0
  20. package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts.map +1 -0
  21. package/dist/browser/cache/IndexedDBLocalCacheStore.js +106 -0
  22. package/dist/browser/cache/IndexedDBLocalCacheStore.js.map +1 -0
  23. package/dist/browser/index.d.ts +3 -0
  24. package/dist/browser/index.d.ts.map +1 -1
  25. package/dist/browser/index.js +9 -1
  26. package/dist/browser/index.js.map +1 -1
  27. package/dist/core/BaseMindooDB.d.ts +108 -13
  28. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  29. package/dist/core/BaseMindooDB.js +1268 -292
  30. package/dist/core/BaseMindooDB.js.map +1 -1
  31. package/dist/core/BaseMindooTenant.d.ts +31 -1
  32. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  33. package/dist/core/BaseMindooTenant.js +212 -129
  34. package/dist/core/BaseMindooTenant.js.map +1 -1
  35. package/dist/core/BaseMindooTenantDirectory.d.ts +13 -10
  36. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  37. package/dist/core/BaseMindooTenantDirectory.js +134 -27
  38. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  39. package/dist/core/BaseMindooTenantFactory.d.ts +5 -1
  40. package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
  41. package/dist/core/BaseMindooTenantFactory.js +46 -69
  42. package/dist/core/BaseMindooTenantFactory.js.map +1 -1
  43. package/dist/core/MindooDBServerAdmin.d.ts +241 -0
  44. package/dist/core/MindooDBServerAdmin.d.ts.map +1 -0
  45. package/dist/core/MindooDBServerAdmin.js +401 -0
  46. package/dist/core/MindooDBServerAdmin.js.map +1 -0
  47. package/dist/core/appendonlystores/AttachmentReadPlanner.d.ts +22 -0
  48. package/dist/core/appendonlystores/AttachmentReadPlanner.d.ts.map +1 -0
  49. package/dist/core/appendonlystores/AttachmentReadPlanner.js +124 -0
  50. package/dist/core/appendonlystores/AttachmentReadPlanner.js.map +1 -0
  51. package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts +10 -4
  52. package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts.map +1 -1
  53. package/dist/core/appendonlystores/InMemoryContentAddressedStore.js +44 -9
  54. package/dist/core/appendonlystores/InMemoryContentAddressedStore.js.map +1 -1
  55. package/dist/core/appendonlystores/MaterializationPlanner.d.ts +84 -0
  56. package/dist/core/appendonlystores/MaterializationPlanner.d.ts.map +1 -0
  57. package/dist/core/appendonlystores/MaterializationPlanner.js +326 -0
  58. package/dist/core/appendonlystores/MaterializationPlanner.js.map +1 -0
  59. package/dist/core/appendonlystores/network/NetworkTransport.d.ts +33 -0
  60. package/dist/core/appendonlystores/network/NetworkTransport.d.ts.map +1 -1
  61. package/dist/core/appendonlystores/network/types.d.ts +35 -0
  62. package/dist/core/appendonlystores/network/types.d.ts.map +1 -1
  63. package/dist/core/appendonlystores/network/types.js +2 -0
  64. package/dist/core/appendonlystores/network/types.js.map +1 -1
  65. package/dist/core/appendonlystores/types.d.ts +174 -3
  66. package/dist/core/appendonlystores/types.d.ts.map +1 -1
  67. package/dist/core/cache/CacheManager.d.ts +75 -0
  68. package/dist/core/cache/CacheManager.d.ts.map +1 -0
  69. package/dist/core/cache/CacheManager.js +101 -0
  70. package/dist/core/cache/CacheManager.js.map +1 -0
  71. package/dist/core/cache/EncryptedLocalCacheStore.d.ts +41 -0
  72. package/dist/core/cache/EncryptedLocalCacheStore.d.ts.map +1 -0
  73. package/dist/core/cache/EncryptedLocalCacheStore.js +120 -0
  74. package/dist/core/cache/EncryptedLocalCacheStore.js.map +1 -0
  75. package/dist/core/cache/LocalCacheStore.d.ts +50 -0
  76. package/dist/core/cache/LocalCacheStore.d.ts.map +1 -0
  77. package/dist/core/cache/LocalCacheStore.js +39 -0
  78. package/dist/core/cache/LocalCacheStore.js.map +1 -0
  79. package/dist/core/crypto/privateKeyEncryption.d.ts +5 -0
  80. package/dist/core/crypto/privateKeyEncryption.d.ts.map +1 -0
  81. package/dist/core/crypto/privateKeyEncryption.js +87 -0
  82. package/dist/core/crypto/privateKeyEncryption.js.map +1 -0
  83. package/dist/core/index.d.ts +13 -6
  84. package/dist/core/index.d.ts.map +1 -1
  85. package/dist/core/index.js +13 -1
  86. package/dist/core/index.js.map +1 -1
  87. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +4 -1
  88. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
  89. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +39 -11
  90. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
  91. package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts +1 -0
  92. package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts.map +1 -1
  93. package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js +3 -0
  94. package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js.map +1 -1
  95. package/dist/core/indexing/virtualviews/VirtualView.d.ts +43 -0
  96. package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -1
  97. package/dist/core/indexing/virtualviews/VirtualView.js +173 -0
  98. package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -1
  99. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +7 -0
  100. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -1
  101. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +28 -0
  102. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -1
  103. package/dist/core/keys/KeyBag.d.ts +38 -14
  104. package/dist/core/keys/KeyBag.d.ts.map +1 -1
  105. package/dist/core/keys/KeyBag.js +102 -76
  106. package/dist/core/keys/KeyBag.js.map +1 -1
  107. package/dist/core/keys/KeyContext.d.ts +3 -3
  108. package/dist/core/keys/KeyContext.d.ts.map +1 -1
  109. package/dist/core/keys/KeyContext.js +10 -4
  110. package/dist/core/keys/KeyContext.js.map +1 -1
  111. package/dist/core/types.d.ts +341 -22
  112. package/dist/core/types.d.ts.map +1 -1
  113. package/dist/core/types.js +7 -1
  114. package/dist/core/types.js.map +1 -1
  115. package/dist/core/utils/idGeneration.d.ts.map +1 -1
  116. package/dist/core/utils/idGeneration.js +37 -3
  117. package/dist/core/utils/idGeneration.js.map +1 -1
  118. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts +31 -7
  119. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -1
  120. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js +139 -17
  121. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -1
  122. package/dist/node/attachments/writeAttachmentToFile.d.ts +15 -0
  123. package/dist/node/attachments/writeAttachmentToFile.d.ts.map +1 -0
  124. package/dist/node/attachments/writeAttachmentToFile.js +73 -0
  125. package/dist/node/attachments/writeAttachmentToFile.js.map +1 -0
  126. package/dist/node/cache/FileSystemLocalCacheStore.d.ts +17 -0
  127. package/dist/node/cache/FileSystemLocalCacheStore.d.ts.map +1 -0
  128. package/dist/node/cache/FileSystemLocalCacheStore.js +128 -0
  129. package/dist/node/cache/FileSystemLocalCacheStore.js.map +1 -0
  130. package/dist/node/cli/change-identity-password.d.ts +7 -0
  131. package/dist/node/cli/change-identity-password.d.ts.map +1 -0
  132. package/dist/node/cli/change-identity-password.js +67 -0
  133. package/dist/node/cli/change-identity-password.js.map +1 -0
  134. package/dist/node/cli/cli-utils.d.ts +16 -0
  135. package/dist/node/cli/cli-utils.d.ts.map +1 -0
  136. package/dist/node/cli/cli-utils.js +113 -0
  137. package/dist/node/cli/cli-utils.js.map +1 -0
  138. package/dist/node/cli/identity-export-public.d.ts +4 -0
  139. package/dist/node/cli/identity-export-public.d.ts.map +1 -0
  140. package/dist/node/cli/identity-export-public.js +55 -0
  141. package/dist/node/cli/identity-export-public.js.map +1 -0
  142. package/dist/node/cli/identity-info.d.ts +4 -0
  143. package/dist/node/cli/identity-info.d.ts.map +1 -0
  144. package/dist/node/cli/identity-info.js +57 -0
  145. package/dist/node/cli/identity-info.js.map +1 -0
  146. package/dist/node/index.d.ts +1 -0
  147. package/dist/node/index.d.ts.map +1 -1
  148. package/dist/node/index.js +4 -1
  149. package/dist/node/index.js.map +1 -1
  150. package/dist/node/server/CapabilityMatcher.d.ts +45 -0
  151. package/dist/node/server/CapabilityMatcher.d.ts.map +1 -0
  152. package/dist/node/server/CapabilityMatcher.js +117 -0
  153. package/dist/node/server/CapabilityMatcher.js.map +1 -0
  154. package/dist/node/server/MindooDBServer.d.ts +94 -0
  155. package/dist/node/server/MindooDBServer.d.ts.map +1 -0
  156. package/dist/node/server/MindooDBServer.js +1256 -0
  157. package/dist/node/server/MindooDBServer.js.map +1 -0
  158. package/dist/node/server/ServerSync.d.ts +41 -0
  159. package/dist/node/server/ServerSync.d.ts.map +1 -0
  160. package/dist/node/server/ServerSync.js +187 -0
  161. package/dist/node/server/ServerSync.js.map +1 -0
  162. package/dist/node/server/StoreFactory.d.ts +45 -0
  163. package/dist/node/server/StoreFactory.d.ts.map +1 -0
  164. package/dist/node/server/StoreFactory.js +89 -0
  165. package/dist/node/server/StoreFactory.js.map +1 -0
  166. package/dist/node/server/SystemAdminAuth.d.ts +65 -0
  167. package/dist/node/server/SystemAdminAuth.d.ts.map +1 -0
  168. package/dist/node/server/SystemAdminAuth.js +200 -0
  169. package/dist/node/server/SystemAdminAuth.js.map +1 -0
  170. package/dist/node/server/SystemIpAllowlist.d.ts +21 -0
  171. package/dist/node/server/SystemIpAllowlist.d.ts.map +1 -0
  172. package/dist/node/server/SystemIpAllowlist.js +210 -0
  173. package/dist/node/server/SystemIpAllowlist.js.map +1 -0
  174. package/dist/node/server/TenantManager.d.ts +100 -0
  175. package/dist/node/server/TenantManager.d.ts.map +1 -0
  176. package/dist/node/server/TenantManager.js +615 -0
  177. package/dist/node/server/TenantManager.js.map +1 -0
  178. package/dist/node/server/add-to-network.d.ts +20 -0
  179. package/dist/node/server/add-to-network.d.ts.map +1 -0
  180. package/dist/node/server/add-to-network.js +275 -0
  181. package/dist/node/server/add-to-network.js.map +1 -0
  182. package/dist/node/server/config.d.ts +51 -0
  183. package/dist/node/server/config.d.ts.map +1 -0
  184. package/dist/node/server/config.js +218 -0
  185. package/dist/node/server/config.js.map +1 -0
  186. package/dist/node/server/resolveServerPassword.d.ts +7 -0
  187. package/dist/node/server/resolveServerPassword.d.ts.map +1 -0
  188. package/dist/node/server/resolveServerPassword.js +25 -0
  189. package/dist/node/server/resolveServerPassword.js.map +1 -0
  190. package/dist/node/server/server.d.ts +22 -0
  191. package/dist/node/server/server.d.ts.map +1 -0
  192. package/dist/node/server/server.js +225 -0
  193. package/dist/node/server/server.js.map +1 -0
  194. package/dist/node/server/serverinit.d.ts +21 -0
  195. package/dist/node/server/serverinit.d.ts.map +1 -0
  196. package/dist/node/server/serverinit.js +317 -0
  197. package/dist/node/server/serverinit.js.map +1 -0
  198. package/dist/node/server/types.d.ts +167 -0
  199. package/dist/node/server/types.d.ts.map +1 -0
  200. package/dist/node/server/types.js +24 -0
  201. package/dist/node/server/types.js.map +1 -0
  202. package/dist/node/server/validation.d.ts +47 -0
  203. package/dist/node/server/validation.d.ts.map +1 -0
  204. package/dist/node/server/validation.js +114 -0
  205. package/dist/node/server/validation.js.map +1 -0
  206. package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts +48 -0
  207. package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts.map +1 -0
  208. package/dist/reactnative/cache/MMKVLocalCacheStore.js +113 -0
  209. package/dist/reactnative/cache/MMKVLocalCacheStore.js.map +1 -0
  210. package/dist/reactnative/index.d.ts +1 -0
  211. package/dist/reactnative/index.d.ts.map +1 -1
  212. package/dist/reactnative/index.js +4 -1
  213. package/dist/reactnative/index.js.map +1 -1
  214. package/package.json +15 -3
@@ -0,0 +1,1256 @@
1
+ "use strict";
2
+ /**
3
+ * MindooDB Server - Express HTTP server implementing the sync API.
4
+ *
5
+ * Security features:
6
+ * - Capabilities-based system admin authorization via config.json
7
+ * - Challenge/response (Ed25519) authentication for /system/* endpoints
8
+ * - JWT-based session management for system admins
9
+ * - Input validation on all identifiers (path traversal prevention)
10
+ * - Rate limiting per endpoint tier
11
+ * - Security headers (helmet) and CORS
12
+ * - Error sanitization (no internal details leaked)
13
+ * - Request size limits and timeouts
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __importDefault = (this && this.__importDefault) || function (mod) {
49
+ return (mod && mod.__esModule) ? mod : { "default": mod };
50
+ };
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.MindooDBServer = void 0;
53
+ const express_1 = __importStar(require("express"));
54
+ const https_1 = __importDefault(require("https"));
55
+ const path_1 = __importDefault(require("path"));
56
+ const fs_1 = require("fs");
57
+ const helmet_1 = __importDefault(require("helmet"));
58
+ const cors_1 = __importDefault(require("cors"));
59
+ const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
60
+ const types_1 = require("../../core/appendonlystores/network/types");
61
+ const TenantManager_1 = require("./TenantManager");
62
+ const CapabilityMatcher_1 = require("./CapabilityMatcher");
63
+ const SystemAdminAuth_1 = require("./SystemAdminAuth");
64
+ const config_1 = require("./config");
65
+ const SystemIpAllowlist_1 = require("./SystemIpAllowlist");
66
+ const validation_1 = require("./validation");
67
+ function parseBodySizeLimitToBytes(limit) {
68
+ const normalized = limit.trim().toLowerCase();
69
+ if (!normalized) {
70
+ return null;
71
+ }
72
+ if (/^\d+$/.test(normalized)) {
73
+ return Number(normalized);
74
+ }
75
+ const match = normalized.match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)$/);
76
+ if (!match) {
77
+ return null;
78
+ }
79
+ const value = Number(match[1]);
80
+ const unit = match[2];
81
+ const multiplier = unit === "b"
82
+ ? 1
83
+ : unit === "kb"
84
+ ? 1024
85
+ : unit === "mb"
86
+ ? 1024 * 1024
87
+ : 1024 * 1024 * 1024;
88
+ return Math.round(value * multiplier);
89
+ }
90
+ class MindooDBServer {
91
+ constructor(dataDir, serverPassword, staticDir, config, configPath) {
92
+ this.app = (0, express_1.default)();
93
+ this.tenantManager = new TenantManager_1.TenantManager(dataDir, serverPassword);
94
+ this.staticDir = staticDir;
95
+ this.configPath = configPath ?? path_1.default.join(dataDir, "config.json");
96
+ this.jsonBodyLimit = process.env.MINDOODB_JSON_BODY_LIMIT?.trim() || "5mb";
97
+ this.jsonBodyLimitBytes = parseBodySizeLimitToBytes(this.jsonBodyLimit);
98
+ this.serverConfig = config ?? { capabilities: {} };
99
+ this.capabilityMatcher = new CapabilityMatcher_1.CapabilityMatcher(this.serverConfig);
100
+ const cryptoAdapter = this.tenantManager.getCryptoAdapter();
101
+ this.systemAdminAuth = new SystemAdminAuth_1.SystemAdminAuthService(cryptoAdapter, this.serverConfig);
102
+ const principalCount = Object.values(this.serverConfig.capabilities).reduce((sum, arr) => sum + arr.length, 0);
103
+ if (principalCount === 0) {
104
+ console.log(`[MindooDBServer] WARNING: No system admin principals configured. All /system/* endpoints are locked down.`);
105
+ }
106
+ else {
107
+ console.log(`[MindooDBServer] System admin auth configured with ${principalCount} principal(s)`);
108
+ }
109
+ this.setupMiddleware();
110
+ this.setupRoutes();
111
+ }
112
+ getApp() {
113
+ return this.app;
114
+ }
115
+ getTenantManager() {
116
+ return this.tenantManager;
117
+ }
118
+ getSystemAdminAuth() {
119
+ return this.systemAdminAuth;
120
+ }
121
+ getServerConfig() {
122
+ return this.serverConfig;
123
+ }
124
+ getConfigPath() {
125
+ return this.configPath;
126
+ }
127
+ reloadConfig(newConfig) {
128
+ this.serverConfig = newConfig;
129
+ this.capabilityMatcher.reload(newConfig);
130
+ this.systemAdminAuth.reloadPrincipals(newConfig);
131
+ }
132
+ listen(port) {
133
+ const server = this.app.listen(port, () => {
134
+ console.log(`[MindooDBServer] Listening on port ${port}`);
135
+ });
136
+ server.setTimeout(30000);
137
+ }
138
+ listenTls(port, certPath, keyPath) {
139
+ const tlsOptions = {
140
+ cert: (0, fs_1.readFileSync)(certPath),
141
+ key: (0, fs_1.readFileSync)(keyPath),
142
+ };
143
+ const server = https_1.default.createServer(tlsOptions, this.app);
144
+ server.listen(port, () => {
145
+ console.log(`[MindooDBServer] Listening on HTTPS port ${port}`);
146
+ });
147
+ server.setTimeout(30000);
148
+ }
149
+ setupMiddleware() {
150
+ this.app.use((0, helmet_1.default)());
151
+ const corsOrigin = this.readCorsOriginsFromEnv();
152
+ this.app.use((0, cors_1.default)({
153
+ origin: corsOrigin || false,
154
+ methods: ["GET", "POST", "PUT", "DELETE"],
155
+ allowedHeaders: ["Content-Type", "Authorization"],
156
+ }));
157
+ this.app.use((0, express_rate_limit_1.default)({
158
+ windowMs: 60000,
159
+ max: 500,
160
+ standardHeaders: true,
161
+ legacyHeaders: false,
162
+ message: { error: "Too many requests, please try again later" },
163
+ }));
164
+ this.app.use(express_1.default.json({
165
+ limit: this.jsonBodyLimit,
166
+ verify: (req, _res, buf) => {
167
+ req.jsonBodyBytes = buf.length;
168
+ },
169
+ }));
170
+ this.app.use((req, res, next) => {
171
+ const contentType = req.headers["content-type"] ?? "-";
172
+ const contentLength = req.headers["content-length"] ?? "-";
173
+ console.log(`[${new Date().toISOString()}] ${req.method} ${req.path} content-type=${contentType} content-length=${contentLength}`);
174
+ next();
175
+ });
176
+ }
177
+ readCorsOriginsFromEnv() {
178
+ const raw = process.env.MINDOODB_CORS_ORIGIN?.trim();
179
+ if (!raw) {
180
+ return undefined;
181
+ }
182
+ const origins = raw
183
+ .split(",")
184
+ .map((entry) => entry.trim())
185
+ .filter((entry) => entry.length > 0);
186
+ if (origins.length <= 1) {
187
+ return origins[0];
188
+ }
189
+ return origins;
190
+ }
191
+ setupRoutes() {
192
+ // Root redirect
193
+ this.app.get("/", (req, res) => {
194
+ if (this.staticDir && (0, fs_1.existsSync)(path_1.default.join(this.staticDir, "index.html"))) {
195
+ res.redirect(302, "/statics/index.html");
196
+ }
197
+ else {
198
+ res.status(404).json({ error: "Not found" });
199
+ }
200
+ });
201
+ this.app.get("/health", (req, res) => {
202
+ res.json({ status: "ok", timestamp: Date.now() });
203
+ });
204
+ this.app.get("/.well-known/mindoodb-server-info", (req, res) => {
205
+ const info = this.tenantManager.getServerPublicInfo();
206
+ if (!info) {
207
+ res.status(503).json({ error: "Server identity not initialized" });
208
+ return;
209
+ }
210
+ res.json({
211
+ ...info,
212
+ maxJsonRequestBodyLimit: this.jsonBodyLimit,
213
+ maxJsonRequestBodyBytes: this.jsonBodyLimitBytes ?? undefined,
214
+ });
215
+ });
216
+ this.app.get("/.well-known/mindoodb-tenants/:tenantId/publicinfos-fingerprints", async (req, res) => {
217
+ const tenantId = req.params.tenantId.toLowerCase();
218
+ try {
219
+ (0, validation_1.validateTenantId)(tenantId);
220
+ const fingerprints = await this.tenantManager.listTenantPublicInfosFingerprints(tenantId);
221
+ if (fingerprints.length === 0) {
222
+ res.status(404).json({ error: "Tenant not found on server" });
223
+ return;
224
+ }
225
+ const response = {
226
+ tenantId,
227
+ fingerprints,
228
+ };
229
+ res.json(response);
230
+ }
231
+ catch (error) {
232
+ if (error instanceof validation_1.ValidationError) {
233
+ res.status(400).json({ error: error.message });
234
+ return;
235
+ }
236
+ if (error instanceof Error && error.message.includes("not found")) {
237
+ res.status(404).json({ error: "Tenant not found on server" });
238
+ return;
239
+ }
240
+ console.error("[MindooDBServer] Error reading tenant publicInfos fingerprints:", error);
241
+ res.status(500).json({ error: "Failed to read tenant publicInfos fingerprints" });
242
+ }
243
+ });
244
+ // Static file serving
245
+ if (this.staticDir) {
246
+ const resolvedStaticDir = path_1.default.resolve(this.staticDir);
247
+ this.app.use("/statics", (req, res, next) => {
248
+ if (req.path.includes("..")) {
249
+ res.status(400).json({ error: "Invalid path" });
250
+ return;
251
+ }
252
+ next();
253
+ }, express_1.default.static(resolvedStaticDir, { dotfiles: "deny", etag: true }));
254
+ console.log(`[MindooDBServer] Serving static files from ${resolvedStaticDir} at /statics/`);
255
+ }
256
+ // System admin routes (replaces former /admin/*)
257
+ const systemIpRaw = (0, SystemIpAllowlist_1.readSystemIpAllowListFromEnv)();
258
+ if (!(0, SystemIpAllowlist_1.isSystemIpAllowListDisabled)(systemIpRaw)) {
259
+ console.log(`[MindooDBServer] MINDOODB_ADMIN_ALLOWED_IPS is set — /system/* limited to: ${systemIpRaw}`);
260
+ }
261
+ const systemRateLimit = (0, express_rate_limit_1.default)({
262
+ windowMs: 60000,
263
+ max: 30,
264
+ standardHeaders: true,
265
+ legacyHeaders: false,
266
+ message: { error: "Too many system requests, please try again later" },
267
+ });
268
+ const systemRouter = (0, express_1.Router)();
269
+ this.setupSystemRoutes(systemRouter);
270
+ this.app.use("/system", SystemIpAllowlist_1.systemIpAllowlistMiddleware, systemRateLimit, systemRouter);
271
+ // Tenant-scoped routes
272
+ this.app.use("/:tenantId", this.tenantMiddleware.bind(this), this.createTenantRouter());
273
+ this.app.use(this.errorHandler.bind(this));
274
+ }
275
+ // ==================== System Admin Auth Middleware ====================
276
+ /**
277
+ * JWT-based middleware for /system/* routes (excluding /system/auth/*).
278
+ * Extracts and validates the bearer token, then checks capabilities
279
+ * for the current (method, path, username, publicsignkey).
280
+ */
281
+ async systemAdminMiddleware(req, res, next) {
282
+ const auth = req.headers.authorization;
283
+ if (!auth || !auth.startsWith("Bearer ")) {
284
+ res.status(401).json({ error: "Missing or invalid Authorization header" });
285
+ return;
286
+ }
287
+ const token = auth.substring(7);
288
+ const payload = await this.systemAdminAuth.validateToken(token);
289
+ if (!payload) {
290
+ res.status(401).json({ error: "Invalid or expired token" });
291
+ return;
292
+ }
293
+ // The original request path relative to the /system mount is in req.path,
294
+ // but we need the full path for capability matching.
295
+ const fullPath = `/system${req.path}`;
296
+ const method = req.method.toUpperCase();
297
+ if (!this.capabilityMatcher.isAuthorized(method, fullPath, payload.sub, payload.publicsignkey)) {
298
+ res.status(403).json({ error: "Forbidden: insufficient capabilities" });
299
+ return;
300
+ }
301
+ req.systemAdmin = {
302
+ username: payload.sub,
303
+ publicsignkey: payload.publicsignkey,
304
+ };
305
+ next();
306
+ }
307
+ // ==================== Tenant Middleware ====================
308
+ tenantMiddleware(req, res, next) {
309
+ const rawTenantId = req.params.tenantId?.toLowerCase();
310
+ if (!rawTenantId) {
311
+ res.status(400).json({ error: "Tenant ID required" });
312
+ return;
313
+ }
314
+ try {
315
+ (0, validation_1.validateTenantIdFormat)(rawTenantId);
316
+ }
317
+ catch {
318
+ res.status(400).json({ error: "Invalid tenant ID format" });
319
+ return;
320
+ }
321
+ if (!this.tenantManager.tenantExists(rawTenantId)) {
322
+ res.status(404).json({ error: this.formatTenantNotFoundOnServerError(rawTenantId) });
323
+ return;
324
+ }
325
+ req.tenantId = rawTenantId;
326
+ next();
327
+ }
328
+ // ==================== System Routes ====================
329
+ setupSystemRoutes(router) {
330
+ // Auth endpoints — no JWT required (these *produce* JWTs)
331
+ const authRateLimit = (0, express_rate_limit_1.default)({
332
+ windowMs: 60000,
333
+ max: 20,
334
+ standardHeaders: true,
335
+ legacyHeaders: false,
336
+ message: { error: "Too many authentication attempts, please try again later" },
337
+ });
338
+ router.post("/auth/challenge", authRateLimit, this.handleSystemChallenge.bind(this));
339
+ router.post("/auth/authenticate", authRateLimit, this.handleSystemAuthenticate.bind(this));
340
+ // All other /system/* routes require JWT + capability check
341
+ const authMiddleware = this.systemAdminMiddleware.bind(this);
342
+ // Tenant CRUD
343
+ router.get("/tenants", authMiddleware, (req, res) => {
344
+ try {
345
+ const tenants = this.tenantManager.listTenants();
346
+ const response = { tenants };
347
+ res.json(response);
348
+ }
349
+ catch (error) {
350
+ console.error("[MindooDBServer] Error listing tenants:", error);
351
+ res.status(500).json({ error: "Failed to list tenants" });
352
+ }
353
+ });
354
+ router.post("/tenants/:tenantId", authMiddleware, async (req, res) => {
355
+ try {
356
+ const tenantId = req.params.tenantId.toLowerCase();
357
+ try {
358
+ (0, validation_1.validateTenantId)(tenantId);
359
+ }
360
+ catch (validationError) {
361
+ res.status(400).json({
362
+ error: validationError instanceof validation_1.ValidationError
363
+ ? validationError.message
364
+ : "Invalid tenantId format",
365
+ });
366
+ return;
367
+ }
368
+ const request = {
369
+ ...req.body,
370
+ tenantId,
371
+ };
372
+ if (!request.adminSigningPublicKey) {
373
+ res.status(400).json({ error: "adminSigningPublicKey is required" });
374
+ return;
375
+ }
376
+ if (!request.adminEncryptionPublicKey) {
377
+ res.status(400).json({ error: "adminEncryptionPublicKey is required" });
378
+ return;
379
+ }
380
+ if (request.adminUsername !== undefined) {
381
+ (0, validation_1.validateUsername)(request.adminUsername);
382
+ }
383
+ (0, validation_1.validateStringLength)(request.adminSigningPublicKey, validation_1.MAX_PEM_KEY_LENGTH, "adminSigningPublicKey");
384
+ (0, validation_1.validateStringLength)(request.adminEncryptionPublicKey, validation_1.MAX_PEM_KEY_LENGTH, "adminEncryptionPublicKey");
385
+ if (!request.encryptedPublicInfosKey && !request.publicInfosKey) {
386
+ res.status(400).json({ error: "encryptedPublicInfosKey or publicInfosKey is required" });
387
+ return;
388
+ }
389
+ if (request.publicInfosKey !== undefined) {
390
+ (0, validation_1.validateStringLength)(request.publicInfosKey, validation_1.MAX_PEM_KEY_LENGTH, "publicInfosKey");
391
+ }
392
+ if (request.encryptedPublicInfosKey !== undefined) {
393
+ (0, validation_1.validateStringLength)(request.encryptedPublicInfosKey, validation_1.MAX_PEM_KEY_LENGTH, "encryptedPublicInfosKey");
394
+ }
395
+ const result = await this.tenantManager.registerTenant(request);
396
+ const response = {
397
+ success: true,
398
+ tenantId: result.context.tenantId,
399
+ created: result.created,
400
+ message: result.created
401
+ ? `Tenant ${result.context.tenantId} registered successfully`
402
+ : `Tenant ${result.context.tenantId} already exists with matching $publicinfos key`,
403
+ };
404
+ res.status(result.created ? 201 : 200).json(response);
405
+ }
406
+ catch (error) {
407
+ if (error instanceof validation_1.ValidationError) {
408
+ res.status(400).json({ error: error.message });
409
+ return;
410
+ }
411
+ if (error instanceof Error && error.message.includes("different $publicinfos key")) {
412
+ res.status(409).json({ error: error.message });
413
+ return;
414
+ }
415
+ console.error("[MindooDBServer] Error registering tenant:", error);
416
+ res.status(500).json({ error: "Failed to register tenant" });
417
+ }
418
+ });
419
+ router.put("/tenants/:tenantId", authMiddleware, (req, res) => {
420
+ try {
421
+ const tenantId = req.params.tenantId.toLowerCase();
422
+ try {
423
+ (0, validation_1.validateTenantId)(tenantId);
424
+ }
425
+ catch {
426
+ res.status(400).json({ error: "Invalid tenantId format" });
427
+ return;
428
+ }
429
+ if (!this.tenantManager.tenantExists(tenantId)) {
430
+ res.status(404).json({ error: "Tenant not found" });
431
+ return;
432
+ }
433
+ this.tenantManager.updateTenantConfig(tenantId, req.body);
434
+ res.json({ success: true, message: `Tenant ${tenantId} updated` });
435
+ }
436
+ catch (error) {
437
+ if (error instanceof validation_1.ValidationError) {
438
+ res.status(400).json({ error: error.message });
439
+ return;
440
+ }
441
+ console.error("[MindooDBServer] Error updating tenant:", error);
442
+ res.status(500).json({ error: "Failed to update tenant" });
443
+ }
444
+ });
445
+ router.delete("/tenants/:tenantId", authMiddleware, async (req, res) => {
446
+ try {
447
+ const tenantId = req.params.tenantId.toLowerCase();
448
+ try {
449
+ (0, validation_1.validateTenantId)(tenantId);
450
+ }
451
+ catch {
452
+ res.status(400).json({ error: "Invalid tenantId format" });
453
+ return;
454
+ }
455
+ if (!this.tenantManager.tenantExists(tenantId)) {
456
+ res.status(404).json({ error: "Tenant not found" });
457
+ return;
458
+ }
459
+ await this.tenantManager.removeTenant(tenantId);
460
+ res.json({ success: true, message: `Tenant ${tenantId} removed` });
461
+ }
462
+ catch (error) {
463
+ console.error("[MindooDBServer] Error removing tenant:", error);
464
+ res.status(500).json({ error: "Failed to remove tenant" });
465
+ }
466
+ });
467
+ // Trusted server management
468
+ router.get("/trusted-servers", authMiddleware, (req, res) => {
469
+ try {
470
+ res.json({ servers: this.tenantManager.listTrustedServers() });
471
+ }
472
+ catch (error) {
473
+ console.error("[MindooDBServer] Error listing trusted servers:", error);
474
+ res.status(500).json({ error: "Failed to list trusted servers" });
475
+ }
476
+ });
477
+ router.post("/trusted-servers", authMiddleware, (req, res) => {
478
+ try {
479
+ const { name, signingPublicKey, encryptionPublicKey } = req.body;
480
+ if (!name || !signingPublicKey || !encryptionPublicKey) {
481
+ res.status(400).json({ error: "name, signingPublicKey, and encryptionPublicKey are required" });
482
+ return;
483
+ }
484
+ (0, validation_1.validateStringLength)(name, 256, "name");
485
+ (0, validation_1.validateStringLength)(signingPublicKey, validation_1.MAX_PEM_KEY_LENGTH, "signingPublicKey");
486
+ (0, validation_1.validateStringLength)(encryptionPublicKey, validation_1.MAX_PEM_KEY_LENGTH, "encryptionPublicKey");
487
+ const server = { name, signingPublicKey, encryptionPublicKey };
488
+ this.tenantManager.addTrustedServer(server);
489
+ res.status(201).json({ success: true, message: `Trusted server "${name}" added` });
490
+ }
491
+ catch (error) {
492
+ if (error instanceof validation_1.ValidationError) {
493
+ res.status(400).json({ error: error.message });
494
+ return;
495
+ }
496
+ if (error instanceof Error && error.message.includes("already exists")) {
497
+ res.status(409).json({ error: error.message });
498
+ return;
499
+ }
500
+ console.error("[MindooDBServer] Error adding trusted server:", error);
501
+ res.status(500).json({ error: "Failed to add trusted server" });
502
+ }
503
+ });
504
+ router.delete("/trusted-servers/:serverName", authMiddleware, (req, res) => {
505
+ try {
506
+ const serverName = req.params.serverName;
507
+ (0, validation_1.validateStringLength)(serverName, 256, "serverName");
508
+ const removed = this.tenantManager.removeTrustedServer(serverName);
509
+ if (!removed) {
510
+ res.status(404).json({ error: "Trusted server not found" });
511
+ return;
512
+ }
513
+ res.json({ success: true, message: `Trusted server "${serverName}" removed` });
514
+ }
515
+ catch (error) {
516
+ if (error instanceof validation_1.ValidationError) {
517
+ res.status(400).json({ error: error.message });
518
+ return;
519
+ }
520
+ console.error("[MindooDBServer] Error removing trusted server:", error);
521
+ res.status(500).json({ error: "Failed to remove trusted server" });
522
+ }
523
+ });
524
+ // Per-tenant sync server management
525
+ router.get("/tenants/:tenantId/sync-servers", authMiddleware, (req, res) => {
526
+ try {
527
+ const tenantId = req.params.tenantId.toLowerCase();
528
+ try {
529
+ (0, validation_1.validateTenantId)(tenantId);
530
+ }
531
+ catch {
532
+ res.status(400).json({ error: "Invalid tenantId format" });
533
+ return;
534
+ }
535
+ if (!this.tenantManager.tenantExists(tenantId)) {
536
+ res.status(404).json({ error: "Tenant not found" });
537
+ return;
538
+ }
539
+ const servers = this.tenantManager.getTenantSyncServers(tenantId);
540
+ res.json({ servers });
541
+ }
542
+ catch (error) {
543
+ console.error("[MindooDBServer] Error listing sync servers:", error);
544
+ res.status(500).json({ error: "Failed to list sync servers" });
545
+ }
546
+ });
547
+ router.post("/tenants/:tenantId/sync-servers", authMiddleware, (req, res) => {
548
+ try {
549
+ const tenantId = req.params.tenantId.toLowerCase();
550
+ try {
551
+ (0, validation_1.validateTenantId)(tenantId);
552
+ }
553
+ catch {
554
+ res.status(400).json({ error: "Invalid tenantId format" });
555
+ return;
556
+ }
557
+ if (!this.tenantManager.tenantExists(tenantId)) {
558
+ res.status(404).json({ error: "Tenant not found" });
559
+ return;
560
+ }
561
+ const { name, url, syncIntervalMs, databases } = req.body;
562
+ if (!name || !url) {
563
+ res.status(400).json({ error: "name and url are required" });
564
+ return;
565
+ }
566
+ if (!databases || !Array.isArray(databases) || databases.length === 0) {
567
+ res.status(400).json({ error: "databases array is required and must not be empty" });
568
+ return;
569
+ }
570
+ (0, validation_1.validateStringLength)(name, 256, "name");
571
+ (0, validation_1.validateStringLength)(url, 2048, "url");
572
+ const config = { name, url, databases };
573
+ if (syncIntervalMs !== undefined) {
574
+ config.syncIntervalMs = syncIntervalMs;
575
+ }
576
+ this.tenantManager.addTenantSyncServer(tenantId, config);
577
+ res.status(201).json({ success: true, message: `Sync server "${name}" configured for tenant ${tenantId}` });
578
+ }
579
+ catch (error) {
580
+ if (error instanceof validation_1.ValidationError) {
581
+ res.status(400).json({ error: error.message });
582
+ return;
583
+ }
584
+ console.error("[MindooDBServer] Error adding sync server:", error);
585
+ res.status(500).json({ error: "Failed to add sync server" });
586
+ }
587
+ });
588
+ router.delete("/tenants/:tenantId/sync-servers/:serverName", authMiddleware, (req, res) => {
589
+ try {
590
+ const tenantId = req.params.tenantId.toLowerCase();
591
+ try {
592
+ (0, validation_1.validateTenantId)(tenantId);
593
+ }
594
+ catch {
595
+ res.status(400).json({ error: "Invalid tenantId format" });
596
+ return;
597
+ }
598
+ if (!this.tenantManager.tenantExists(tenantId)) {
599
+ res.status(404).json({ error: "Tenant not found" });
600
+ return;
601
+ }
602
+ const serverName = decodeURIComponent(req.params.serverName);
603
+ (0, validation_1.validateStringLength)(serverName, 256, "serverName");
604
+ const removed = this.tenantManager.removeTenantSyncServer(tenantId, serverName);
605
+ if (!removed) {
606
+ res.status(404).json({ error: "Sync server not found" });
607
+ return;
608
+ }
609
+ res.json({ success: true, message: `Sync server "${serverName}" removed from tenant ${tenantId}` });
610
+ }
611
+ catch (error) {
612
+ if (error instanceof validation_1.ValidationError) {
613
+ res.status(400).json({ error: error.message });
614
+ return;
615
+ }
616
+ console.error("[MindooDBServer] Error removing sync server:", error);
617
+ res.status(500).json({ error: "Failed to remove sync server" });
618
+ }
619
+ });
620
+ // Per-tenant trigger-sync (moved from /:tenantId/admin/trigger-sync)
621
+ router.post("/tenants/:tenantId/trigger-sync", authMiddleware, this.handleTriggerSync.bind(this));
622
+ // Runtime config management
623
+ router.get("/config/backups", authMiddleware, (req, res) => {
624
+ try {
625
+ res.json({ backups: (0, config_1.listConfigBackups)(this.configPath) });
626
+ }
627
+ catch (error) {
628
+ console.error("[MindooDBServer] Error listing config backups:", error);
629
+ res.status(500).json({ error: "Failed to list config backups" });
630
+ }
631
+ });
632
+ router.get("/config/backups/:backupFile", authMiddleware, (req, res) => {
633
+ try {
634
+ const backupFile = decodeURIComponent(req.params.backupFile);
635
+ if (!(0, config_1.isConfigBackupFilename)(backupFile)) {
636
+ res.status(400).json({ error: "Invalid config backup filename" });
637
+ return;
638
+ }
639
+ const config = (0, config_1.loadConfigBackup)(this.configPath, backupFile);
640
+ res.json({ file: backupFile, config });
641
+ }
642
+ catch (error) {
643
+ if (error instanceof Error && error.message.startsWith("Config backup not found:")) {
644
+ res.status(404).json({ error: "Config backup not found" });
645
+ return;
646
+ }
647
+ if (error instanceof Error && error.message.startsWith("Invalid config backup filename:")) {
648
+ res.status(400).json({ error: "Invalid config backup filename" });
649
+ return;
650
+ }
651
+ console.error("[MindooDBServer] Error reading config backup:", error);
652
+ res.status(500).json({ error: "Failed to read config backup" });
653
+ }
654
+ });
655
+ router.get("/config", authMiddleware, (req, res) => {
656
+ res.json(this.serverConfig);
657
+ });
658
+ router.put("/config", authMiddleware, this.handleUpdateConfig.bind(this));
659
+ }
660
+ // ==================== System Auth Handlers ====================
661
+ async handleSystemChallenge(req, res) {
662
+ try {
663
+ const { username, publicsignkey } = req.body;
664
+ if (!username || typeof username !== "string") {
665
+ res.status(400).json({ error: "username is required" });
666
+ return;
667
+ }
668
+ if (!publicsignkey || typeof publicsignkey !== "string") {
669
+ res.status(400).json({ error: "publicsignkey is required" });
670
+ return;
671
+ }
672
+ (0, validation_1.validateUsername)(username);
673
+ (0, validation_1.validateStringLength)(publicsignkey, validation_1.MAX_PEM_KEY_LENGTH, "publicsignkey");
674
+ const challenge = await this.systemAdminAuth.generateChallenge(username, publicsignkey);
675
+ res.json({ challenge });
676
+ }
677
+ catch (error) {
678
+ if (error instanceof Error && error.message === "Unknown system admin principal") {
679
+ res.status(404).json({ error: "System admin principal not found" });
680
+ return;
681
+ }
682
+ if (error instanceof validation_1.ValidationError) {
683
+ res.status(400).json({ error: error.message });
684
+ return;
685
+ }
686
+ console.error("[MindooDBServer] System challenge error:", error);
687
+ res.status(500).json({ error: "Internal server error" });
688
+ }
689
+ }
690
+ async handleSystemAuthenticate(req, res) {
691
+ try {
692
+ const { challenge, signature } = req.body;
693
+ if (!challenge || !signature) {
694
+ res.status(400).json({ error: "challenge and signature are required" });
695
+ return;
696
+ }
697
+ (0, validation_1.validateStringLength)(challenge, validation_1.MAX_CHALLENGE_LENGTH, "challenge");
698
+ (0, validation_1.validateStringLength)(signature, validation_1.MAX_SIGNATURE_LENGTH, "signature");
699
+ const signatureBytes = this.base64ToUint8Array(signature);
700
+ const result = await this.systemAdminAuth.authenticate(challenge, signatureBytes);
701
+ res.json(result);
702
+ }
703
+ catch (error) {
704
+ if (error instanceof validation_1.ValidationError) {
705
+ res.status(400).json({ error: error.message });
706
+ return;
707
+ }
708
+ console.error("[MindooDBServer] System authenticate error:", error);
709
+ res.status(500).json({ error: "Internal server error" });
710
+ }
711
+ }
712
+ // ==================== Config Update Handler ====================
713
+ async handleUpdateConfig(req, res) {
714
+ try {
715
+ let newConfig;
716
+ try {
717
+ newConfig = (0, config_1.validateServerConfig)(req.body, "request body");
718
+ }
719
+ catch (err) {
720
+ res.status(400).json({
721
+ error: err instanceof Error ? err.message : "Invalid config format",
722
+ });
723
+ return;
724
+ }
725
+ // Self-lockout protection: verify the calling admin retains PUT /system/config access
726
+ const callingAdmin = req.systemAdmin;
727
+ const proposedMatcher = new CapabilityMatcher_1.CapabilityMatcher(newConfig);
728
+ if (!proposedMatcher.isAuthorized("PUT", "/system/config", callingAdmin.username, callingAdmin.publicsignkey)) {
729
+ res.status(400).json({
730
+ error: "This change would remove your own access to PUT /system/config",
731
+ });
732
+ return;
733
+ }
734
+ // Backup the current config before overwriting
735
+ const { existsSync: fsExists } = await import("fs");
736
+ let backupFile = null;
737
+ if (fsExists(this.configPath)) {
738
+ backupFile = (0, config_1.backupConfig)(this.configPath);
739
+ }
740
+ // Persist new config to disk
741
+ (0, config_1.writeConfig)(this.configPath, newConfig);
742
+ // Hot-swap in-memory state
743
+ this.reloadConfig(newConfig);
744
+ console.log(`[MindooDBServer] Config updated by ${callingAdmin.username}` +
745
+ (backupFile ? ` (backup: ${backupFile})` : ""));
746
+ res.json({
747
+ success: true,
748
+ ...(backupFile ? { backupFile } : {}),
749
+ });
750
+ }
751
+ catch (error) {
752
+ console.error("[MindooDBServer] Error updating config:", error);
753
+ res.status(500).json({ error: "Failed to update config" });
754
+ }
755
+ }
756
+ // ==================== Tenant Routes ====================
757
+ createTenantRouter() {
758
+ const router = (0, express_1.Router)({ mergeParams: true });
759
+ // Auth endpoints get stricter rate limiting
760
+ const authRateLimit = (0, express_rate_limit_1.default)({
761
+ windowMs: 60000,
762
+ max: 20,
763
+ standardHeaders: true,
764
+ legacyHeaders: false,
765
+ message: { error: "Too many authentication attempts, please try again later" },
766
+ });
767
+ // Sync endpoints get a higher limit
768
+ const syncRateLimit = (0, express_rate_limit_1.default)({
769
+ windowMs: 60000,
770
+ max: 200,
771
+ standardHeaders: true,
772
+ legacyHeaders: false,
773
+ message: { error: "Too many sync requests, please try again later" },
774
+ });
775
+ router.post("/auth/challenge", authRateLimit, this.handleChallenge.bind(this));
776
+ router.post("/auth/authenticate", authRateLimit, this.handleAuthenticate.bind(this));
777
+ router.post("/sync/findNewEntries", syncRateLimit, this.handleFindNewEntries.bind(this));
778
+ router.post("/sync/findNewEntriesForDoc", syncRateLimit, this.handleFindNewEntriesForDoc.bind(this));
779
+ router.post("/sync/findEntries", syncRateLimit, this.handleFindEntries.bind(this));
780
+ router.post("/sync/scanEntriesSince", syncRateLimit, this.handleScanEntriesSince.bind(this));
781
+ router.post("/sync/getIdBloomSummary", syncRateLimit, this.handleGetIdBloomSummary.bind(this));
782
+ router.post("/sync/getCompactionStatus", syncRateLimit, this.handleGetCompactionStatus.bind(this));
783
+ router.get("/sync/capabilities", syncRateLimit, this.handleGetCapabilities.bind(this));
784
+ router.post("/sync/getEntries", syncRateLimit, this.handleGetEntries.bind(this));
785
+ router.post("/sync/getEntryMetadata", syncRateLimit, this.handleGetEntryMetadata.bind(this));
786
+ router.post("/sync/putEntries", syncRateLimit, this.handlePutEntries.bind(this));
787
+ router.post("/sync/hasEntries", syncRateLimit, this.handleHasEntries.bind(this));
788
+ router.get("/sync/getAllIds", syncRateLimit, this.handleGetAllIds.bind(this));
789
+ router.post("/sync/resolveDependencies", syncRateLimit, this.handleResolveDependencies.bind(this));
790
+ router.post("/sync/planDocumentMaterialization", syncRateLimit, this.handlePlanDocumentMaterialization.bind(this));
791
+ router.post("/sync/planDocumentMaterializationBatch", syncRateLimit, this.handlePlanDocumentMaterializationBatch.bind(this));
792
+ router.post("/sync/planAttachmentReadByWalkingMetadata", syncRateLimit, this.handlePlanAttachmentReadByWalkingMetadata.bind(this));
793
+ return router;
794
+ }
795
+ // ==================== Validation Helpers ====================
796
+ validateDbId(dbId) {
797
+ return (0, validation_1.validateIdentifier)(dbId, "dbId");
798
+ }
799
+ formatTenantNotFoundOnServerError(tenantId) {
800
+ const serverName = this.tenantManager.getServerPublicInfo()?.name ?? "unknown";
801
+ return `Tenant ${tenantId} not found on server ${serverName}`;
802
+ }
803
+ // ==================== Tenant Auth Handlers ====================
804
+ async handleChallenge(req, res) {
805
+ try {
806
+ const { username } = req.body;
807
+ (0, validation_1.validateUsername)(username);
808
+ const authService = await this.tenantManager.getAuthService(req.tenantId);
809
+ const challenge = await authService.generateChallenge(username);
810
+ res.json({ challenge });
811
+ }
812
+ catch (error) {
813
+ this.handleRequestError(error, res);
814
+ }
815
+ }
816
+ async handleAuthenticate(req, res) {
817
+ try {
818
+ const { challenge, signature } = req.body;
819
+ if (!challenge || !signature) {
820
+ res.status(400).json({ error: "challenge and signature are required" });
821
+ return;
822
+ }
823
+ (0, validation_1.validateStringLength)(challenge, validation_1.MAX_CHALLENGE_LENGTH, "challenge");
824
+ (0, validation_1.validateStringLength)(signature, validation_1.MAX_SIGNATURE_LENGTH, "signature");
825
+ const authService = await this.tenantManager.getAuthService(req.tenantId);
826
+ const signatureBytes = this.base64ToUint8Array(signature);
827
+ const result = await authService.authenticate(challenge, signatureBytes);
828
+ res.json(result);
829
+ }
830
+ catch (error) {
831
+ this.handleRequestError(error, res);
832
+ }
833
+ }
834
+ // ==================== Sync Handlers ====================
835
+ async handleFindNewEntries(req, res) {
836
+ try {
837
+ const token = this.extractToken(req);
838
+ const { dbId, haveIds } = req.body;
839
+ const validDbId = this.validateDbId(dbId);
840
+ (0, validation_1.validateArraySize)(haveIds, validation_1.MAX_HAVE_IDS, "haveIds");
841
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
842
+ const entries = await serverStore.handleFindNewEntries(token, haveIds || []);
843
+ res.json({
844
+ entries: entries.map((e) => this.serializeEntryMetadata(e)),
845
+ });
846
+ }
847
+ catch (error) {
848
+ this.handleRequestError(error, res);
849
+ }
850
+ }
851
+ async handleFindNewEntriesForDoc(req, res) {
852
+ try {
853
+ const token = this.extractToken(req);
854
+ const { dbId, haveIds, docId } = req.body;
855
+ const validDbId = this.validateDbId(dbId);
856
+ if (!docId) {
857
+ res.status(400).json({ error: "docId is required" });
858
+ return;
859
+ }
860
+ (0, validation_1.validateArraySize)(haveIds, validation_1.MAX_HAVE_IDS, "haveIds");
861
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
862
+ const entries = await serverStore.handleFindNewEntriesForDoc(token, haveIds || [], docId);
863
+ res.json({
864
+ entries: entries.map((e) => this.serializeEntryMetadata(e)),
865
+ });
866
+ }
867
+ catch (error) {
868
+ this.handleRequestError(error, res);
869
+ }
870
+ }
871
+ async handleFindEntries(req, res) {
872
+ try {
873
+ const token = this.extractToken(req);
874
+ const { dbId, type, creationDateFrom, creationDateUntil } = req.body;
875
+ const validDbId = this.validateDbId(dbId);
876
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
877
+ const entries = await serverStore.handleFindEntries(token, type, creationDateFrom ?? null, creationDateUntil ?? null);
878
+ res.json({
879
+ entries: entries.map((e) => this.serializeEntryMetadata(e)),
880
+ });
881
+ }
882
+ catch (error) {
883
+ this.handleRequestError(error, res);
884
+ }
885
+ }
886
+ async handleScanEntriesSince(req, res) {
887
+ try {
888
+ const token = this.extractToken(req);
889
+ const { dbId, cursor, limit, filters } = req.body;
890
+ const validDbId = this.validateDbId(dbId);
891
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
892
+ const result = await serverStore.handleScanEntriesSince(token, cursor ?? null, limit, filters);
893
+ res.json({
894
+ entries: result.entries.map((e) => this.serializeEntryMetadata(e)),
895
+ nextCursor: result.nextCursor,
896
+ hasMore: result.hasMore,
897
+ });
898
+ }
899
+ catch (error) {
900
+ this.handleRequestError(error, res);
901
+ }
902
+ }
903
+ async handleGetIdBloomSummary(req, res) {
904
+ try {
905
+ const token = this.extractToken(req);
906
+ const { dbId } = req.body;
907
+ const validDbId = this.validateDbId(dbId);
908
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
909
+ const summary = await serverStore.handleGetIdBloomSummary(token);
910
+ res.json({ summary: summary });
911
+ }
912
+ catch (error) {
913
+ this.handleRequestError(error, res);
914
+ }
915
+ }
916
+ async handleGetCapabilities(req, res) {
917
+ try {
918
+ const token = this.extractToken(req);
919
+ const rawDbId = req.query.dbId || "directory";
920
+ const validDbId = this.validateDbId(rawDbId);
921
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
922
+ const capabilities = await serverStore.handleGetCapabilities(token);
923
+ res.json({ capabilities: capabilities });
924
+ }
925
+ catch (error) {
926
+ this.handleRequestError(error, res);
927
+ }
928
+ }
929
+ async handleGetCompactionStatus(req, res) {
930
+ try {
931
+ const token = this.extractToken(req);
932
+ const { dbId } = req.body;
933
+ const validDbId = this.validateDbId(dbId);
934
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
935
+ const status = await serverStore.handleGetCompactionStatus(token);
936
+ res.json({ status: status });
937
+ }
938
+ catch (error) {
939
+ this.handleRequestError(error, res);
940
+ }
941
+ }
942
+ async handleGetEntries(req, res) {
943
+ try {
944
+ const token = this.extractToken(req);
945
+ const { dbId, ids } = req.body;
946
+ const validDbId = this.validateDbId(dbId);
947
+ (0, validation_1.validateArraySize)(ids, validation_1.MAX_ENTRY_IDS, "ids");
948
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
949
+ const entries = await serverStore.handleGetEntries(token, ids || []);
950
+ res.json({
951
+ entries: entries.map((e) => ({
952
+ ...this.serializeEntryMetadata(e),
953
+ rsaEncryptedPayload: this.uint8ArrayToBase64(e.rsaEncryptedPayload),
954
+ })),
955
+ });
956
+ }
957
+ catch (error) {
958
+ this.handleRequestError(error, res);
959
+ }
960
+ }
961
+ async handleGetEntryMetadata(req, res) {
962
+ try {
963
+ const token = this.extractToken(req);
964
+ const { dbId, id } = req.body;
965
+ const validDbId = this.validateDbId(dbId);
966
+ if (!id) {
967
+ res.status(400).json({ error: "id is required" });
968
+ return;
969
+ }
970
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
971
+ const entry = await serverStore.handleGetEntryMetadata(token, id);
972
+ res.json({
973
+ entry: entry ? this.serializeEntryMetadata(entry) : null,
974
+ });
975
+ }
976
+ catch (error) {
977
+ this.handleRequestError(error, res);
978
+ }
979
+ }
980
+ async handlePutEntries(req, res) {
981
+ let stage = "extract-token";
982
+ try {
983
+ const token = this.extractToken(req);
984
+ stage = "read-body";
985
+ const { dbId, entries } = req.body;
986
+ const validDbId = this.validateDbId(dbId);
987
+ (0, validation_1.validateArraySize)(entries, validation_1.MAX_PUT_ENTRIES, "entries");
988
+ const serializedEntries = Array.isArray(entries) ? entries : [];
989
+ const serializedEntryTypeCounts = serializedEntries.reduce((acc, entry) => {
990
+ const key = entry?.entryType ?? "unknown";
991
+ acc[key] = (acc[key] ?? 0) + 1;
992
+ return acc;
993
+ }, {});
994
+ console.log(`[MindooDBServer] putEntries request tenant=${req.tenantId ?? "-"} db=${validDbId} entries=${serializedEntries.length} parsed-bytes=${req.jsonBodyBytes ?? "-"} content-length=${req.headers["content-length"] ?? "-"} types=${JSON.stringify(serializedEntryTypeCounts)}`);
995
+ stage = "load-server-store";
996
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
997
+ stage = "deserialize-entries";
998
+ const deserializedEntries = serializedEntries.map((entry, index) => {
999
+ try {
1000
+ return this.deserializeEntry(entry);
1001
+ }
1002
+ catch (error) {
1003
+ console.error("[MindooDBServer] putEntries failed while deserializing entry", {
1004
+ index,
1005
+ id: entry?.id ?? "-",
1006
+ type: entry?.entryType ?? "-",
1007
+ docId: entry?.docId ?? "-",
1008
+ encryptedSize: entry?.encryptedSize ?? "-",
1009
+ originalSize: entry?.originalSize ?? "-",
1010
+ signatureLength: entry?.signature?.length ?? "-",
1011
+ encryptedDataLength: entry?.encryptedData?.length ?? "-",
1012
+ error,
1013
+ });
1014
+ throw error;
1015
+ }
1016
+ });
1017
+ const totalEncryptedBytes = deserializedEntries.reduce((sum, entry) => sum + entry.encryptedData.byteLength, 0);
1018
+ const totalOriginalBytes = deserializedEntries.reduce((sum, entry) => sum + entry.originalSize, 0);
1019
+ console.log(`[MindooDBServer] putEntries deserialized tenant=${req.tenantId ?? "-"} db=${validDbId} entries=${deserializedEntries.length} totalEncryptedBytes=${totalEncryptedBytes} totalOriginalBytes=${totalOriginalBytes}`);
1020
+ stage = "store-entries";
1021
+ await serverStore.handlePutEntries(token, deserializedEntries);
1022
+ stage = "respond-success";
1023
+ res.json({ success: true });
1024
+ }
1025
+ catch (error) {
1026
+ console.error("[MindooDBServer] putEntries failed", {
1027
+ stage,
1028
+ tenantId: req.tenantId ?? "-",
1029
+ dbId: req.body?.dbId ?? "-",
1030
+ parsedBytes: req.jsonBodyBytes ?? "-",
1031
+ contentLength: req.headers["content-length"] ?? "-",
1032
+ entryCount: Array.isArray(req.body?.entries) ? req.body.entries.length : "-",
1033
+ error,
1034
+ });
1035
+ this.handleRequestError(error, res);
1036
+ }
1037
+ }
1038
+ async handleHasEntries(req, res) {
1039
+ try {
1040
+ const token = this.extractToken(req);
1041
+ const { dbId, ids } = req.body;
1042
+ const validDbId = this.validateDbId(dbId);
1043
+ (0, validation_1.validateArraySize)(ids, validation_1.MAX_ENTRY_IDS, "ids");
1044
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
1045
+ const existingIds = await serverStore.handleHasEntries(token, ids || []);
1046
+ res.json({ ids: existingIds });
1047
+ }
1048
+ catch (error) {
1049
+ this.handleRequestError(error, res);
1050
+ }
1051
+ }
1052
+ async handleGetAllIds(req, res) {
1053
+ try {
1054
+ const token = this.extractToken(req);
1055
+ const rawDbId = req.query.dbId || "directory";
1056
+ const validDbId = this.validateDbId(rawDbId);
1057
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
1058
+ const ids = await serverStore.handleGetAllIds(token);
1059
+ res.json({ ids });
1060
+ }
1061
+ catch (error) {
1062
+ this.handleRequestError(error, res);
1063
+ }
1064
+ }
1065
+ async handleResolveDependencies(req, res) {
1066
+ try {
1067
+ const token = this.extractToken(req);
1068
+ const { dbId, startId, options } = req.body;
1069
+ const validDbId = this.validateDbId(dbId);
1070
+ if (!startId) {
1071
+ res.status(400).json({ error: "startId is required" });
1072
+ return;
1073
+ }
1074
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
1075
+ const ids = await serverStore.handleResolveDependencies(token, startId, options);
1076
+ res.json({ ids });
1077
+ }
1078
+ catch (error) {
1079
+ this.handleRequestError(error, res);
1080
+ }
1081
+ }
1082
+ async handlePlanDocumentMaterialization(req, res) {
1083
+ try {
1084
+ const token = this.extractToken(req);
1085
+ const { dbId, docId, options } = req.body;
1086
+ const validDbId = this.validateDbId(dbId);
1087
+ if (!docId) {
1088
+ res.status(400).json({ error: "docId is required" });
1089
+ return;
1090
+ }
1091
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
1092
+ const plan = await serverStore.handlePlanDocumentMaterialization(token, docId, options);
1093
+ res.json({ plan: plan });
1094
+ }
1095
+ catch (error) {
1096
+ this.handleRequestError(error, res);
1097
+ }
1098
+ }
1099
+ async handlePlanDocumentMaterializationBatch(req, res) {
1100
+ try {
1101
+ const token = this.extractToken(req);
1102
+ const { dbId, docIds, options } = req.body;
1103
+ const validDbId = this.validateDbId(dbId);
1104
+ (0, validation_1.validateArraySize)(docIds, validation_1.MAX_HAVE_IDS, "docIds");
1105
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
1106
+ const batchPlan = await serverStore.handlePlanDocumentMaterializationBatch(token, docIds || [], options);
1107
+ res.json({ batchPlan: batchPlan });
1108
+ }
1109
+ catch (error) {
1110
+ this.handleRequestError(error, res);
1111
+ }
1112
+ }
1113
+ async handlePlanAttachmentReadByWalkingMetadata(req, res) {
1114
+ try {
1115
+ const token = this.extractToken(req);
1116
+ const { dbId, lastChunkId, attachmentSize, options } = req.body;
1117
+ const validDbId = this.validateDbId(dbId);
1118
+ if (!lastChunkId) {
1119
+ res.status(400).json({ error: "lastChunkId is required" });
1120
+ return;
1121
+ }
1122
+ if (typeof attachmentSize !== "number" || !Number.isFinite(attachmentSize)) {
1123
+ res.status(400).json({ error: "attachmentSize is required" });
1124
+ return;
1125
+ }
1126
+ if (!options || typeof options.startByte !== "number") {
1127
+ res.status(400).json({ error: "options.startByte is required" });
1128
+ return;
1129
+ }
1130
+ const serverStore = await this.tenantManager.getServerStore(req.tenantId, validDbId);
1131
+ const plan = await serverStore.handlePlanAttachmentReadByWalkingMetadata(token, lastChunkId, attachmentSize, options);
1132
+ res.json({ plan: plan });
1133
+ }
1134
+ catch (error) {
1135
+ this.handleRequestError(error, res);
1136
+ }
1137
+ }
1138
+ // ==================== Management Handlers ====================
1139
+ async handleTriggerSync(req, res) {
1140
+ res.json({ success: true, message: "Sync triggered (not yet implemented)" });
1141
+ }
1142
+ // ==================== Helper Methods ====================
1143
+ extractToken(req) {
1144
+ const auth = req.headers.authorization;
1145
+ if (!auth || !auth.startsWith("Bearer ")) {
1146
+ throw new types_1.NetworkError("INVALID_TOKEN", "Missing or invalid Authorization header");
1147
+ }
1148
+ return auth.substring(7);
1149
+ }
1150
+ handleRequestError(error, res) {
1151
+ if (error instanceof validation_1.ValidationError) {
1152
+ res.status(400).json({ error: error.message });
1153
+ return;
1154
+ }
1155
+ if (error instanceof Error && error.name === "NetworkError") {
1156
+ const networkError = error;
1157
+ const status = this.getStatusForErrorType(networkError.type);
1158
+ res.status(status).json({ error: networkError.message });
1159
+ return;
1160
+ }
1161
+ console.error("[MindooDBServer] Request error:", error);
1162
+ res.status(500).json({ error: "Internal server error" });
1163
+ }
1164
+ getStatusForErrorType(type) {
1165
+ switch (type) {
1166
+ case "INVALID_TOKEN":
1167
+ return 401;
1168
+ case "USER_REVOKED":
1169
+ return 403;
1170
+ case "USER_NOT_FOUND":
1171
+ return 404;
1172
+ case "INVALID_SIGNATURE":
1173
+ return 403;
1174
+ case "CHALLENGE_EXPIRED":
1175
+ return 401;
1176
+ default:
1177
+ return 500;
1178
+ }
1179
+ }
1180
+ errorHandler(err, req, res, next) {
1181
+ const parserError = err;
1182
+ const status = parserError.statusCode ?? parserError.status;
1183
+ if (status === 413 || parserError.type === "entity.too.large") {
1184
+ console.error("[MindooDBServer] Request body too large", {
1185
+ method: req.method,
1186
+ path: req.path,
1187
+ contentLength: req.headers["content-length"] ?? "-",
1188
+ parsedBytes: req.jsonBodyBytes ?? "-",
1189
+ limit: parserError.limit ?? this.jsonBodyLimit,
1190
+ });
1191
+ res.status(413).json({ error: "Request body too large" });
1192
+ return;
1193
+ }
1194
+ if (status === 400 && parserError.type === "entity.parse.failed") {
1195
+ console.error("[MindooDBServer] Invalid JSON body", {
1196
+ method: req.method,
1197
+ path: req.path,
1198
+ contentLength: req.headers["content-length"] ?? "-",
1199
+ parsedBytes: req.jsonBodyBytes ?? "-",
1200
+ error: err,
1201
+ });
1202
+ res.status(400).json({ error: "Invalid JSON body" });
1203
+ return;
1204
+ }
1205
+ console.error("[MindooDBServer] Unhandled error", {
1206
+ method: req.method,
1207
+ path: req.path,
1208
+ contentLength: req.headers["content-length"] ?? "-",
1209
+ parsedBytes: req.jsonBodyBytes ?? "-",
1210
+ error: err,
1211
+ });
1212
+ res.status(500).json({ error: "Internal server error" });
1213
+ }
1214
+ // ==================== Serialization ====================
1215
+ serializeEntryMetadata(metadata) {
1216
+ return {
1217
+ entryType: metadata.entryType,
1218
+ id: metadata.id,
1219
+ contentHash: metadata.contentHash,
1220
+ docId: metadata.docId,
1221
+ dependencyIds: metadata.dependencyIds,
1222
+ createdAt: metadata.createdAt,
1223
+ receiptOrder: metadata.receiptOrder,
1224
+ createdByPublicKey: metadata.createdByPublicKey,
1225
+ decryptionKeyId: metadata.decryptionKeyId,
1226
+ signature: this.uint8ArrayToBase64(metadata.signature),
1227
+ originalSize: metadata.originalSize,
1228
+ encryptedSize: metadata.encryptedSize,
1229
+ };
1230
+ }
1231
+ deserializeEntry(serialized) {
1232
+ return {
1233
+ entryType: serialized.entryType,
1234
+ id: serialized.id,
1235
+ contentHash: serialized.contentHash,
1236
+ docId: serialized.docId,
1237
+ dependencyIds: serialized.dependencyIds,
1238
+ createdAt: serialized.createdAt,
1239
+ receiptOrder: serialized.receiptOrder,
1240
+ createdByPublicKey: serialized.createdByPublicKey,
1241
+ decryptionKeyId: serialized.decryptionKeyId,
1242
+ signature: this.base64ToUint8Array(serialized.signature),
1243
+ originalSize: serialized.originalSize,
1244
+ encryptedSize: serialized.encryptedSize,
1245
+ encryptedData: this.base64ToUint8Array(serialized.encryptedData),
1246
+ };
1247
+ }
1248
+ uint8ArrayToBase64(bytes) {
1249
+ return Buffer.from(bytes).toString("base64");
1250
+ }
1251
+ base64ToUint8Array(base64) {
1252
+ return new Uint8Array(Buffer.from(base64, "base64"));
1253
+ }
1254
+ }
1255
+ exports.MindooDBServer = MindooDBServer;
1256
+ //# sourceMappingURL=MindooDBServer.js.map