mindoodb 0.0.85 → 0.0.86

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 (235) hide show
  1. package/README-server.md +1 -0
  2. package/README.md +6 -4
  3. package/dist/cloudflare/DurableObjectSql.d.ts +56 -0
  4. package/dist/cloudflare/DurableObjectSql.d.ts.map +1 -0
  5. package/dist/cloudflare/DurableObjectSql.js +180 -0
  6. package/dist/cloudflare/DurableObjectSql.js.map +1 -0
  7. package/dist/cloudflare/MindooDatabaseObject.d.ts +86 -0
  8. package/dist/cloudflare/MindooDatabaseObject.d.ts.map +1 -0
  9. package/dist/cloudflare/MindooDatabaseObject.js +614 -0
  10. package/dist/cloudflare/MindooDatabaseObject.js.map +1 -0
  11. package/dist/cloudflare/MindooServerObject.d.ts +63 -0
  12. package/dist/cloudflare/MindooServerObject.d.ts.map +1 -0
  13. package/dist/cloudflare/MindooServerObject.js +521 -0
  14. package/dist/cloudflare/MindooServerObject.js.map +1 -0
  15. package/dist/cloudflare/WorkersCryptoAdapter.d.ts +7 -0
  16. package/dist/cloudflare/WorkersCryptoAdapter.d.ts.map +1 -0
  17. package/dist/cloudflare/WorkersCryptoAdapter.js +14 -0
  18. package/dist/cloudflare/WorkersCryptoAdapter.js.map +1 -0
  19. package/dist/cloudflare/host.d.ts +49 -0
  20. package/dist/cloudflare/host.d.ts.map +1 -0
  21. package/dist/cloudflare/host.js +104 -0
  22. package/dist/cloudflare/host.js.map +1 -0
  23. package/dist/cloudflare/http.d.ts +91 -0
  24. package/dist/cloudflare/http.d.ts.map +1 -0
  25. package/dist/cloudflare/http.js +266 -0
  26. package/dist/cloudflare/http.js.map +1 -0
  27. package/dist/cloudflare/index.d.ts +12 -0
  28. package/dist/cloudflare/index.d.ts.map +1 -0
  29. package/dist/cloudflare/index.js +20 -0
  30. package/dist/cloudflare/index.js.map +1 -0
  31. package/dist/cloudflare/storeRpc.d.ts +22 -0
  32. package/dist/cloudflare/storeRpc.d.ts.map +1 -0
  33. package/dist/cloudflare/storeRpc.js +85 -0
  34. package/dist/cloudflare/storeRpc.js.map +1 -0
  35. package/dist/cloudflare/types.d.ts +101 -0
  36. package/dist/cloudflare/types.d.ts.map +1 -0
  37. package/dist/cloudflare/types.js +10 -0
  38. package/dist/cloudflare/types.js.map +1 -0
  39. package/dist/cloudflare/worker.d.ts +25 -0
  40. package/dist/cloudflare/worker.d.ts.map +1 -0
  41. package/dist/cloudflare/worker.js +170 -0
  42. package/dist/cloudflare/worker.js.map +1 -0
  43. package/dist/core/BaseMindooDB.d.ts +28 -1
  44. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  45. package/dist/core/BaseMindooDB.js +180 -86
  46. package/dist/core/BaseMindooDB.js.map +1 -1
  47. package/dist/core/BaseMindooTenant.d.ts +14 -0
  48. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  49. package/dist/core/BaseMindooTenant.js +46 -0
  50. package/dist/core/BaseMindooTenant.js.map +1 -1
  51. package/dist/core/BaseMindooTenantDirectory.d.ts +10 -0
  52. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  53. package/dist/core/BaseMindooTenantDirectory.js +79 -8
  54. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  55. package/dist/core/ChangeDocDraft.d.ts +133 -0
  56. package/dist/core/ChangeDocDraft.d.ts.map +1 -0
  57. package/dist/core/ChangeDocDraft.js +492 -0
  58. package/dist/core/ChangeDocDraft.js.map +1 -0
  59. package/dist/core/appendonlystores/network/AuthenticationService.d.ts +8 -0
  60. package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
  61. package/dist/core/appendonlystores/network/AuthenticationService.js +19 -0
  62. package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
  63. package/dist/core/appendonlystores/network/IrohStreamIO.d.ts.map +1 -1
  64. package/dist/core/appendonlystores/network/IrohStreamIO.js +3 -23
  65. package/dist/core/appendonlystores/network/IrohStreamIO.js.map +1 -1
  66. package/dist/core/appendonlystores/rpc/storeRpcCodec.d.ts.map +1 -1
  67. package/dist/core/appendonlystores/rpc/storeRpcCodec.js +3 -23
  68. package/dist/core/appendonlystores/rpc/storeRpcCodec.js.map +1 -1
  69. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts.map +1 -1
  70. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js +21 -30
  71. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js.map +1 -1
  72. package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts +37 -1
  73. package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts.map +1 -1
  74. package/dist/core/appendonlystores/sqlite/SqliteDriver.js +99 -40
  75. package/dist/core/appendonlystores/sqlite/SqliteDriver.js.map +1 -1
  76. package/dist/core/expressions/evaluateExpression.d.ts +8 -0
  77. package/dist/core/expressions/evaluateExpression.d.ts.map +1 -1
  78. package/dist/core/expressions/evaluateExpression.js +29 -4
  79. package/dist/core/expressions/evaluateExpression.js.map +1 -1
  80. package/dist/core/expressions/metadata.js +4 -4
  81. package/dist/core/expressions/metadata.js.map +1 -1
  82. package/dist/core/types.d.ts +61 -2
  83. package/dist/core/types.d.ts.map +1 -1
  84. package/dist/core/types.js.map +1 -1
  85. package/dist/core/uri/MindooURI.d.ts.map +1 -1
  86. package/dist/core/uri/MindooURI.js +3 -24
  87. package/dist/core/uri/MindooURI.js.map +1 -1
  88. package/dist/core/utils/base64.d.ts +17 -0
  89. package/dist/core/utils/base64.d.ts.map +1 -0
  90. package/dist/core/utils/base64.js +54 -0
  91. package/dist/core/utils/base64.js.map +1 -0
  92. package/dist/core/utils/idGeneration.d.ts.map +1 -1
  93. package/dist/core/utils/idGeneration.js +14 -3
  94. package/dist/core/utils/idGeneration.js.map +1 -1
  95. package/dist/node/server/FsServerDataStore.d.ts +23 -0
  96. package/dist/node/server/FsServerDataStore.d.ts.map +1 -0
  97. package/dist/node/server/FsServerDataStore.js +59 -0
  98. package/dist/node/server/FsServerDataStore.js.map +1 -0
  99. package/dist/node/server/IrohServerEndpoint.d.ts +1 -1
  100. package/dist/node/server/IrohServerEndpoint.d.ts.map +1 -1
  101. package/dist/node/server/IrohServerRpc.d.ts +5 -5
  102. package/dist/node/server/IrohServerRpc.d.ts.map +1 -1
  103. package/dist/node/server/IrohServerRpc.js +1 -1
  104. package/dist/node/server/IrohServerRpc.js.map +1 -1
  105. package/dist/node/server/MindooDBServer.d.ts +29 -150
  106. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  107. package/dist/node/server/MindooDBServer.js +160 -1573
  108. package/dist/node/server/MindooDBServer.js.map +1 -1
  109. package/dist/node/server/PurgedDocRegistry.d.ts +4 -27
  110. package/dist/node/server/PurgedDocRegistry.d.ts.map +1 -1
  111. package/dist/node/server/PurgedDocRegistry.js +6 -83
  112. package/dist/node/server/PurgedDocRegistry.js.map +1 -1
  113. package/dist/node/server/StoreFactory.d.ts +1 -1
  114. package/dist/node/server/StoreFactory.d.ts.map +1 -1
  115. package/dist/node/server/TenantManager.d.ts +16 -284
  116. package/dist/node/server/TenantManager.d.ts.map +1 -1
  117. package/dist/node/server/TenantManager.js +35 -1168
  118. package/dist/node/server/TenantManager.js.map +1 -1
  119. package/dist/node/server/add-to-network.js +1 -1
  120. package/dist/node/server/add-to-network.js.map +1 -1
  121. package/dist/node/server/config.d.ts +11 -15
  122. package/dist/node/server/config.d.ts.map +1 -1
  123. package/dist/node/server/config.js +30 -245
  124. package/dist/node/server/config.js.map +1 -1
  125. package/dist/node/server/expressAdapter.d.ts +23 -0
  126. package/dist/node/server/expressAdapter.d.ts.map +1 -0
  127. package/dist/node/server/expressAdapter.js +108 -0
  128. package/dist/node/server/expressAdapter.js.map +1 -0
  129. package/dist/node/server/nativeIrohStreamIO.d.ts +1 -1
  130. package/dist/node/server/nativeIrohStreamIO.d.ts.map +1 -1
  131. package/dist/node/server/peer/FileClusterAuditStorage.d.ts +16 -0
  132. package/dist/node/server/peer/FileClusterAuditStorage.d.ts.map +1 -0
  133. package/dist/node/server/peer/FileClusterAuditStorage.js +39 -0
  134. package/dist/node/server/peer/FileClusterAuditStorage.js.map +1 -0
  135. package/dist/node/server/peer/IrohPeerLink.d.ts +1 -1
  136. package/dist/node/server/peer/IrohPeerLink.d.ts.map +1 -1
  137. package/dist/node/server/peer/routes.d.ts +10 -44
  138. package/dist/node/server/peer/routes.d.ts.map +1 -1
  139. package/dist/node/server/peer/routes.js +33 -275
  140. package/dist/node/server/peer/routes.js.map +1 -1
  141. package/dist/node/server/resolveServerPassword.js +1 -1
  142. package/dist/node/server/resolveServerPassword.js.map +1 -1
  143. package/dist/node/server/server.js +1 -1
  144. package/dist/node/server/server.js.map +1 -1
  145. package/dist/node/server/serverinit.js +1 -1
  146. package/dist/node/server/serverinit.js.map +1 -1
  147. package/dist/server/CapabilityMatcher.d.ts +60 -0
  148. package/dist/server/CapabilityMatcher.d.ts.map +1 -0
  149. package/dist/server/CapabilityMatcher.js +138 -0
  150. package/dist/server/CapabilityMatcher.js.map +1 -0
  151. package/dist/server/DeviceDiscoveryService.d.ts +36 -0
  152. package/dist/server/DeviceDiscoveryService.d.ts.map +1 -0
  153. package/dist/server/DeviceDiscoveryService.js +165 -0
  154. package/dist/server/DeviceDiscoveryService.js.map +1 -0
  155. package/dist/server/PurgedDocRegistry.d.ts +35 -0
  156. package/dist/server/PurgedDocRegistry.d.ts.map +1 -0
  157. package/dist/server/PurgedDocRegistry.js +90 -0
  158. package/dist/server/PurgedDocRegistry.js.map +1 -0
  159. package/dist/server/ServerDataStore.d.ts +67 -0
  160. package/dist/server/ServerDataStore.d.ts.map +1 -0
  161. package/dist/server/ServerDataStore.js +88 -0
  162. package/dist/server/ServerDataStore.js.map +1 -0
  163. package/dist/server/SyncEventBus.d.ts +39 -0
  164. package/dist/server/SyncEventBus.d.ts.map +1 -0
  165. package/dist/server/SyncEventBus.js +41 -0
  166. package/dist/server/SyncEventBus.js.map +1 -0
  167. package/dist/server/SystemAdminAuth.d.ts +82 -0
  168. package/dist/server/SystemAdminAuth.d.ts.map +1 -0
  169. package/dist/server/SystemAdminAuth.js +228 -0
  170. package/dist/server/SystemAdminAuth.js.map +1 -0
  171. package/dist/server/TenantManager.d.ts +330 -0
  172. package/dist/server/TenantManager.d.ts.map +1 -0
  173. package/dist/server/TenantManager.js +1186 -0
  174. package/dist/server/TenantManager.js.map +1 -0
  175. package/dist/server/http/MindooDBServerCore.d.ts +313 -0
  176. package/dist/server/http/MindooDBServerCore.d.ts.map +1 -0
  177. package/dist/server/http/MindooDBServerCore.js +1403 -0
  178. package/dist/server/http/MindooDBServerCore.js.map +1 -0
  179. package/dist/server/http/types.d.ts +89 -0
  180. package/dist/server/http/types.d.ts.map +1 -0
  181. package/dist/server/http/types.js +29 -0
  182. package/dist/server/http/types.js.map +1 -0
  183. package/dist/server/index.d.ts +32 -0
  184. package/dist/server/index.d.ts.map +1 -0
  185. package/dist/server/index.js +76 -0
  186. package/dist/server/index.js.map +1 -0
  187. package/dist/server/peer/ClusterAuditLog.d.ts +71 -0
  188. package/dist/server/peer/ClusterAuditLog.d.ts.map +1 -0
  189. package/dist/server/peer/ClusterAuditLog.js +81 -0
  190. package/dist/server/peer/ClusterAuditLog.js.map +1 -0
  191. package/dist/server/peer/ClusterJobRunner.d.ts +43 -0
  192. package/dist/server/peer/ClusterJobRunner.d.ts.map +1 -0
  193. package/dist/server/peer/ClusterJobRunner.js +119 -0
  194. package/dist/server/peer/ClusterJobRunner.js.map +1 -0
  195. package/dist/server/peer/ClusterManager.d.ts +149 -0
  196. package/dist/server/peer/ClusterManager.d.ts.map +1 -0
  197. package/dist/server/peer/ClusterManager.js +292 -0
  198. package/dist/server/peer/ClusterManager.js.map +1 -0
  199. package/dist/server/peer/PeerAuthService.d.ts +69 -0
  200. package/dist/server/peer/PeerAuthService.d.ts.map +1 -0
  201. package/dist/server/peer/PeerAuthService.js +208 -0
  202. package/dist/server/peer/PeerAuthService.js.map +1 -0
  203. package/dist/server/peer/PeerReplicator.d.ts +303 -0
  204. package/dist/server/peer/PeerReplicator.d.ts.map +1 -0
  205. package/dist/server/peer/PeerReplicator.js +1071 -0
  206. package/dist/server/peer/PeerReplicator.js.map +1 -0
  207. package/dist/server/peer/clusterHttp.d.ts +113 -0
  208. package/dist/server/peer/clusterHttp.d.ts.map +1 -0
  209. package/dist/server/peer/clusterHttp.js +290 -0
  210. package/dist/server/peer/clusterHttp.js.map +1 -0
  211. package/dist/server/peer/types.d.ts +244 -0
  212. package/dist/server/peer/types.d.ts.map +1 -0
  213. package/dist/server/peer/types.js +189 -0
  214. package/dist/server/peer/types.js.map +1 -0
  215. package/dist/server/rateLimits.d.ts +119 -0
  216. package/dist/server/rateLimits.d.ts.map +1 -0
  217. package/dist/server/rateLimits.js +159 -0
  218. package/dist/server/rateLimits.js.map +1 -0
  219. package/dist/server/serverConfig.d.ts +37 -0
  220. package/dist/server/serverConfig.d.ts.map +1 -0
  221. package/dist/server/serverConfig.js +251 -0
  222. package/dist/server/serverConfig.js.map +1 -0
  223. package/dist/server/timestampProxy.d.ts +129 -0
  224. package/dist/server/timestampProxy.d.ts.map +1 -0
  225. package/dist/server/timestampProxy.js +361 -0
  226. package/dist/server/timestampProxy.js.map +1 -0
  227. package/dist/server/types.d.ts +267 -0
  228. package/dist/server/types.d.ts.map +1 -0
  229. package/dist/server/types.js +56 -0
  230. package/dist/server/types.js.map +1 -0
  231. package/dist/server/validation.d.ts +43 -0
  232. package/dist/server/validation.d.ts.map +1 -0
  233. package/dist/server/validation.js +104 -0
  234. package/dist/server/validation.js.map +1 -0
  235. package/package.json +9 -1
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Types for server-to-server (peer) replication and the cluster admin surface.
3
+ *
4
+ * Two auth classes, two route prefixes — the split is load-bearing:
5
+ *
6
+ * - `/system/peer/*` is the **server-to-server protocol**. Callers authenticate
7
+ * with a server identity listed in `trusted-servers.json`. Only servers speak
8
+ * it.
9
+ * - `/system/cluster/*` is the **admin surface**. Callers authenticate with a
10
+ * system-admin JWT and pass the {@link CapabilityMatcher}. Only humans and
11
+ * UIs speak it.
12
+ *
13
+ * No endpoint accepts both, so a peer can never administer and an admin can
14
+ * never write as a peer.
15
+ */
16
+ /**
17
+ * Which way entries flow to a peer.
18
+ *
19
+ * Mirrors Haven's client-side `SyncDirectionPreference` so operators meet one
20
+ * vocabulary. `pull` is the backup/archive node (never writes to the peer),
21
+ * `push` the DMZ head (never reads back), `disabled` keeps the trust entry for
22
+ * inbound authentication but opens no outbound session.
23
+ */
24
+ export type PeerSyncDirection = "bidirectional" | "push" | "pull" | "disabled";
25
+ /**
26
+ * Mesh shape.
27
+ *
28
+ * A `spoke` opens sessions only to `hub` peers, never to another spoke, which
29
+ * turns the full mesh into a spanning tree. Because the entry set is a CRDT
30
+ * both converge; the tree just avoids the O(N^2) sessions of a large mesh.
31
+ * `peer` (the default) is fully meshed and is the right answer for two or three
32
+ * nodes — duplicate pushes are acknowledged idempotently by the receiver's
33
+ * duplicate path, so the redundancy is cheap.
34
+ */
35
+ export type PeerRole = "peer" | "hub" | "spoke";
36
+ /**
37
+ * How eagerly attachment blobs follow their documents.
38
+ *
39
+ * Attachments are usually the bulk of the bytes and live in a separate store,
40
+ * which makes this the biggest bandwidth lever. `lazy` still transfers them but
41
+ * only after every document store is done; `never` skips the attachment store
42
+ * entirely (an edge node that only needs text).
43
+ */
44
+ export type PeerAttachmentMode = "eager" | "lazy" | "never";
45
+ /** Peer settings resolved against their defaults. */
46
+ export interface PeerSettings {
47
+ direction: PeerSyncDirection;
48
+ role: PeerRole;
49
+ attachments: PeerAttachmentMode;
50
+ }
51
+ export declare const DEFAULT_PEER_SETTINGS: PeerSettings;
52
+ export declare function isPeerSyncDirection(value: unknown): value is PeerSyncDirection;
53
+ export declare function isPeerRole(value: unknown): value is PeerRole;
54
+ export declare function isPeerAttachmentMode(value: unknown): value is PeerAttachmentMode;
55
+ /** Fill in defaults for any peer field the operator left unset. */
56
+ export declare function normalizePeerSettings(raw: {
57
+ direction?: unknown;
58
+ role?: unknown;
59
+ attachments?: unknown;
60
+ }): PeerSettings;
61
+ /**
62
+ * Should this node open an outbound session to that peer?
63
+ *
64
+ * Spoke-to-spoke is the only pairing we suppress: both sides would otherwise
65
+ * dial each other even though the hub already carries their traffic.
66
+ */
67
+ export declare function shouldOpenSessionTo(local: PeerRole, remote: PeerRole): boolean;
68
+ /** May we send local entries to this peer? */
69
+ export declare function directionAllowsPush(direction: PeerSyncDirection): boolean;
70
+ /** May we take the peer's entries? */
71
+ export declare function directionAllowsPull(direction: PeerSyncDirection): boolean;
72
+ /**
73
+ * Order the databases of one tenant for replication.
74
+ *
75
+ * `directory` first, then `userdirectory`, then everything else alphabetically.
76
+ * This is not cosmetic. A peer validates every pushed entry against its own
77
+ * directory (`validatePublicSigningKey`), and an unknown author key is a
78
+ * **per-entry rejection**, not a batch failure. Push an application database
79
+ * before the peer has the author's grant and those entries are refused; with
80
+ * the client's cursor semantics they would then be skipped forever, leaving the
81
+ * two servers silently divergent.
82
+ *
83
+ * The same ordering also shrinks the transient window in which a purged
84
+ * document or a revoked `decryptionKeyId` makes the peer answer `ACCESS_DENIED`
85
+ * for a whole batch: both denylists are derived from the directory (purge
86
+ * requests live there and are executed by `executePendingPurges` after every
87
+ * directory push), so they converge as soon as the directory has arrived.
88
+ */
89
+ export declare function orderDatabasesForReplication(dbIds: Iterable<string>): string[];
90
+ /** True for the two system databases that must replicate before app data. */
91
+ export declare function isTrustBearingDatabase(dbId: string): boolean;
92
+ /** Live state of the outbound session to one peer. */
93
+ export type ClusterSessionState = "connected" | "connecting" | "backoff" | "disabled" | "paused" | "unreachable";
94
+ /**
95
+ * Health rollup, computed on the server so every console agrees on the
96
+ * thresholds instead of inventing its own.
97
+ */
98
+ export type ClusterHealth = "ok" | "degraded" | "stalled" | "down";
99
+ /**
100
+ * Why a peer is not `ok`. Typed so a UI can colour-code and explain without
101
+ * parsing prose.
102
+ *
103
+ * `entries-awaiting-directory-trust` is the one that matters most: it means the
104
+ * peer rejected entries because it does not yet trust their author, the cursor
105
+ * was held, and a retry is pending. Left unattended it is the visible precursor
106
+ * of two servers drifting apart.
107
+ */
108
+ export type ClusterHealthReason = "entries-awaiting-directory-trust" | "access-denied-backoff" | "events-disconnected" | "intersection-stale" | "lag-above-threshold" | "peer-auth-failed" | "peer-unreachable" | "no-url-configured" | "session-disabled" | "session-paused";
109
+ /** Typed error class beside the free-text `lastError`. */
110
+ export type ClusterErrorClass = "auth" | "network" | "access-denied" | "protocol" | "internal";
111
+ /**
112
+ * Bucket a replication failure so the console can react without parsing the
113
+ * message.
114
+ *
115
+ * The distinction that matters operationally is `access-denied` versus the
116
+ * rest: it is the peer *deliberately* refusing a batch (a purged document, a
117
+ * revoked key) and it resolves itself once the directory has replicated, so it
118
+ * warrants a backoff and not an alert. Everything else is either a
119
+ * misconfiguration (`auth`) or a genuine outage (`network`).
120
+ */
121
+ /**
122
+ * Strip document and entry identifiers out of a message before it reaches the
123
+ * cluster status read-model.
124
+ *
125
+ * Rejection messages name the offending entry, and an entry id starts with the
126
+ * document id. The cluster console is a system-admin surface, and a system
127
+ * admin is not supposed to learn which documents a tenant holds — that is the
128
+ * same admin-blindness the encryption enforces, and it would be odd to hand it
129
+ * back through an error string. The classified reason plus the counts are what
130
+ * an operator can act on anyway.
131
+ */
132
+ export declare function redactEntryIds(message: string): string;
133
+ export declare function classifyPeerError(error: unknown): ClusterErrorClass;
134
+ /** Per-tenant slice of a peer's replication state. */
135
+ export interface ClusterTenantStatus {
136
+ tenantId: string;
137
+ /** Databases mirrored with this peer, in replication order. */
138
+ databases: string[];
139
+ lastSyncAt: string | null;
140
+ /** Entries the peer still owes us or we owe the peer, by head comparison. */
141
+ lag: number;
142
+ /** Entries rejected and queued for retry (see `entries-awaiting-directory-trust`). */
143
+ retryQueueDepth: number;
144
+ rejectedEntries: number;
145
+ health: ClusterHealth;
146
+ reasons: ClusterHealthReason[];
147
+ }
148
+ /** One peer as this node sees it. */
149
+ export interface ClusterPeerStatus {
150
+ name: string;
151
+ url: string | null;
152
+ direction: PeerSyncDirection;
153
+ role: PeerRole;
154
+ attachments: PeerAttachmentMode;
155
+ state: ClusterSessionState;
156
+ health: ClusterHealth;
157
+ reasons: ClusterHealthReason[];
158
+ /** Tenants both sides hold, from the last Bloom intersection. */
159
+ sharedTenants: number;
160
+ lastIntersectionAt: string | null;
161
+ lastSyncAt: string | null;
162
+ eventsConnected: boolean;
163
+ lag: number;
164
+ retryQueueDepth: number;
165
+ rejectedEntries: number;
166
+ transferredEntries: number;
167
+ transferredBytes: number;
168
+ lastError: string | null;
169
+ lastErrorClass: ClusterErrorClass | null;
170
+ lastErrorAt: string | null;
171
+ }
172
+ /** Summary answer of `GET /system/cluster/status`. */
173
+ export interface ClusterStatusResponse {
174
+ server: string | null;
175
+ health: ClusterHealth;
176
+ reasons: ClusterHealthReason[];
177
+ autoSyncEnabled: boolean;
178
+ generatedAt: string;
179
+ peers: ClusterPeerStatus[];
180
+ }
181
+ /** Drill-down answer of `GET /system/cluster/status/tenants/:tenantId`. */
182
+ export interface ClusterTenantStatusResponse {
183
+ tenantId: string;
184
+ generatedAt: string;
185
+ peers: Array<{
186
+ name: string;
187
+ tenant: ClusterTenantStatus | null;
188
+ }>;
189
+ }
190
+ /** One entry of `GET /system/cluster/topology`. */
191
+ export interface ClusterTopologyPeer {
192
+ name: string;
193
+ url: string | null;
194
+ direction: PeerSyncDirection;
195
+ role: PeerRole;
196
+ attachments: PeerAttachmentMode;
197
+ sessionActive: boolean;
198
+ }
199
+ export interface ClusterTopologyResponse {
200
+ /**
201
+ * This node's own identity and role. The console starts here and then polls
202
+ * every peer directly — no node answers on behalf of another (fan-out).
203
+ */
204
+ server: {
205
+ name: string | null;
206
+ role: PeerRole;
207
+ };
208
+ peers: ClusterTopologyPeer[];
209
+ }
210
+ export type ClusterJobState = "queued" | "running" | "succeeded" | "failed";
211
+ export type ClusterJobKind = "sync" | "retry-rejected" | "refresh-intersection" | "pause" | "resume";
212
+ export interface ClusterJobScope {
213
+ peer?: string;
214
+ tenantId?: string;
215
+ dbId?: string;
216
+ }
217
+ export interface ClusterJob {
218
+ id: string;
219
+ kind: ClusterJobKind;
220
+ scope: ClusterJobScope;
221
+ state: ClusterJobState;
222
+ createdAt: string;
223
+ startedAt: string | null;
224
+ finishedAt: string | null;
225
+ /** Coarse progress counters; never carries document identity. */
226
+ progress: {
227
+ total: number;
228
+ completed: number;
229
+ };
230
+ result: ClusterJobResult | null;
231
+ error: string | null;
232
+ errorClass: ClusterErrorClass | null;
233
+ /** System-admin principal that requested the job. */
234
+ requestedBy: string;
235
+ }
236
+ export interface ClusterJobResult {
237
+ transferredEntries: number;
238
+ transferredBytes: number;
239
+ /** Count only — the entry ids would leak document identity to the console. */
240
+ rejectedEntries: number;
241
+ tenantsProcessed: number;
242
+ databasesProcessed: number;
243
+ }
244
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/server/peer/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ /**
3
+ * Types for server-to-server (peer) replication and the cluster admin surface.
4
+ *
5
+ * Two auth classes, two route prefixes — the split is load-bearing:
6
+ *
7
+ * - `/system/peer/*` is the **server-to-server protocol**. Callers authenticate
8
+ * with a server identity listed in `trusted-servers.json`. Only servers speak
9
+ * it.
10
+ * - `/system/cluster/*` is the **admin surface**. Callers authenticate with a
11
+ * system-admin JWT and pass the {@link CapabilityMatcher}. Only humans and
12
+ * UIs speak it.
13
+ *
14
+ * No endpoint accepts both, so a peer can never administer and an admin can
15
+ * never write as a peer.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.DEFAULT_PEER_SETTINGS = void 0;
19
+ exports.isPeerSyncDirection = isPeerSyncDirection;
20
+ exports.isPeerRole = isPeerRole;
21
+ exports.isPeerAttachmentMode = isPeerAttachmentMode;
22
+ exports.normalizePeerSettings = normalizePeerSettings;
23
+ exports.shouldOpenSessionTo = shouldOpenSessionTo;
24
+ exports.directionAllowsPush = directionAllowsPush;
25
+ exports.directionAllowsPull = directionAllowsPull;
26
+ exports.orderDatabasesForReplication = orderDatabasesForReplication;
27
+ exports.isTrustBearingDatabase = isTrustBearingDatabase;
28
+ exports.redactEntryIds = redactEntryIds;
29
+ exports.classifyPeerError = classifyPeerError;
30
+ const types_1 = require("../../core/types");
31
+ exports.DEFAULT_PEER_SETTINGS = {
32
+ direction: "bidirectional",
33
+ role: "peer",
34
+ attachments: "eager",
35
+ };
36
+ function isPeerSyncDirection(value) {
37
+ return (value === "bidirectional" || value === "push" || value === "pull" || value === "disabled");
38
+ }
39
+ function isPeerRole(value) {
40
+ return value === "peer" || value === "hub" || value === "spoke";
41
+ }
42
+ function isPeerAttachmentMode(value) {
43
+ return value === "eager" || value === "lazy" || value === "never";
44
+ }
45
+ /** Fill in defaults for any peer field the operator left unset. */
46
+ function normalizePeerSettings(raw) {
47
+ return {
48
+ direction: isPeerSyncDirection(raw.direction)
49
+ ? raw.direction
50
+ : exports.DEFAULT_PEER_SETTINGS.direction,
51
+ role: isPeerRole(raw.role) ? raw.role : exports.DEFAULT_PEER_SETTINGS.role,
52
+ attachments: isPeerAttachmentMode(raw.attachments)
53
+ ? raw.attachments
54
+ : exports.DEFAULT_PEER_SETTINGS.attachments,
55
+ };
56
+ }
57
+ /**
58
+ * Should this node open an outbound session to that peer?
59
+ *
60
+ * Spoke-to-spoke is the only pairing we suppress: both sides would otherwise
61
+ * dial each other even though the hub already carries their traffic.
62
+ */
63
+ function shouldOpenSessionTo(local, remote) {
64
+ return !(local === "spoke" && remote === "spoke");
65
+ }
66
+ /** May we send local entries to this peer? */
67
+ function directionAllowsPush(direction) {
68
+ return direction === "bidirectional" || direction === "push";
69
+ }
70
+ /** May we take the peer's entries? */
71
+ function directionAllowsPull(direction) {
72
+ return direction === "bidirectional" || direction === "pull";
73
+ }
74
+ // =========================================================================
75
+ // Replication ordering (convergence invariant 1)
76
+ // =========================================================================
77
+ /**
78
+ * Order the databases of one tenant for replication.
79
+ *
80
+ * `directory` first, then `userdirectory`, then everything else alphabetically.
81
+ * This is not cosmetic. A peer validates every pushed entry against its own
82
+ * directory (`validatePublicSigningKey`), and an unknown author key is a
83
+ * **per-entry rejection**, not a batch failure. Push an application database
84
+ * before the peer has the author's grant and those entries are refused; with
85
+ * the client's cursor semantics they would then be skipped forever, leaving the
86
+ * two servers silently divergent.
87
+ *
88
+ * The same ordering also shrinks the transient window in which a purged
89
+ * document or a revoked `decryptionKeyId` makes the peer answer `ACCESS_DENIED`
90
+ * for a whole batch: both denylists are derived from the directory (purge
91
+ * requests live there and are executed by `executePendingPurges` after every
92
+ * directory push), so they converge as soon as the directory has arrived.
93
+ */
94
+ function orderDatabasesForReplication(dbIds) {
95
+ const rest = [];
96
+ let hasDirectory = false;
97
+ let hasUserDirectory = false;
98
+ for (const dbId of dbIds) {
99
+ if (dbId === types_1.DIRECTORY_DB_ID) {
100
+ hasDirectory = true;
101
+ }
102
+ else if (dbId === types_1.USER_DIRECTORY_DB_ID) {
103
+ hasUserDirectory = true;
104
+ }
105
+ else {
106
+ rest.push(dbId);
107
+ }
108
+ }
109
+ rest.sort((left, right) => left.localeCompare(right));
110
+ const ordered = [];
111
+ if (hasDirectory)
112
+ ordered.push(types_1.DIRECTORY_DB_ID);
113
+ if (hasUserDirectory)
114
+ ordered.push(types_1.USER_DIRECTORY_DB_ID);
115
+ return ordered.concat(rest);
116
+ }
117
+ /** True for the two system databases that must replicate before app data. */
118
+ function isTrustBearingDatabase(dbId) {
119
+ return dbId === types_1.DIRECTORY_DB_ID || dbId === types_1.USER_DIRECTORY_DB_ID;
120
+ }
121
+ /**
122
+ * Bucket a replication failure so the console can react without parsing the
123
+ * message.
124
+ *
125
+ * The distinction that matters operationally is `access-denied` versus the
126
+ * rest: it is the peer *deliberately* refusing a batch (a purged document, a
127
+ * revoked key) and it resolves itself once the directory has replicated, so it
128
+ * warrants a backoff and not an alert. Everything else is either a
129
+ * misconfiguration (`auth`) or a genuine outage (`network`).
130
+ */
131
+ /**
132
+ * Strip document and entry identifiers out of a message before it reaches the
133
+ * cluster status read-model.
134
+ *
135
+ * Rejection messages name the offending entry, and an entry id starts with the
136
+ * document id. The cluster console is a system-admin surface, and a system
137
+ * admin is not supposed to learn which documents a tenant holds — that is the
138
+ * same admin-blindness the encryption enforces, and it would be odd to hand it
139
+ * back through an error string. The classified reason plus the counts are what
140
+ * an operator can act on anyway.
141
+ */
142
+ function redactEntryIds(message) {
143
+ return message.replace(/[0-9a-f]{16,}/gi, "<redacted>");
144
+ }
145
+ function classifyPeerError(error) {
146
+ const type = error?.type;
147
+ switch (type) {
148
+ case "ACCESS_DENIED":
149
+ // Every 403 reaches the client as USER_REVOKED, whatever the server meant
150
+ // by it. For a peer that is unambiguous: it holds no user identity, so a
151
+ // 403 can only mean the target refuses these particular entries — a purged
152
+ // document, a revoked decryption key. That is a backoff, not a
153
+ // misconfigured trust relationship.
154
+ case "USER_REVOKED":
155
+ return "access-denied";
156
+ case "INVALID_TOKEN":
157
+ case "INVALID_SIGNATURE":
158
+ case "CHALLENGE_EXPIRED":
159
+ return "auth";
160
+ case "NETWORK_ERROR":
161
+ case "RATE_LIMITED":
162
+ return "network";
163
+ case "PAYLOAD_TOO_LARGE":
164
+ case "SERVER_ERROR":
165
+ return "protocol";
166
+ default:
167
+ break;
168
+ }
169
+ const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
170
+ if (message.includes("access_denied") || message.includes("access denied")) {
171
+ return "access-denied";
172
+ }
173
+ if (message.includes("401") ||
174
+ message.includes("403") ||
175
+ message.includes("authentication") ||
176
+ message.includes("token")) {
177
+ return "auth";
178
+ }
179
+ if (message.includes("fetch failed") ||
180
+ message.includes("econnrefused") ||
181
+ message.includes("enotfound") ||
182
+ message.includes("etimedout") ||
183
+ message.includes("socket") ||
184
+ message.includes("network")) {
185
+ return "network";
186
+ }
187
+ return "internal";
188
+ }
189
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/server/peer/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Rate-limit resolution shared by the server's limiter tiers.
3
+ *
4
+ * Kept out of {@link MindooDBServer} so the interaction between the global net
5
+ * and the per-route tiers is testable on its own: it is the part that is easy
6
+ * to get wrong, and getting it wrong is silent — a global cap below a tier cap
7
+ * simply replaces that tier, and the tier's configuration stops meaning
8
+ * anything.
9
+ */
10
+ import type { ServerRateLimitsConfig } from "./types";
11
+ export interface ResolvedRateLimit {
12
+ windowMs: number;
13
+ max: number;
14
+ }
15
+ export interface ResolvedGlobalRateLimit extends ResolvedRateLimit {
16
+ /** Requests the tiers may spend inside the global window, plus headroom. */
17
+ tierFloor: number;
18
+ /** True when the resolved max rejects traffic a tier would still allow. */
19
+ shadowsTiers: boolean;
20
+ }
21
+ /**
22
+ * Resolve the global limiter against the tiers it is supposed to back up.
23
+ *
24
+ * Without an explicit `rateLimits.global`, the ceiling is derived from the
25
+ * tiers so the net catches only traffic no tier claims. An explicit value is
26
+ * honoured — an operator may want a hard cap — but reported as shadowing so
27
+ * the caller can say so at startup instead of leaving it to be discovered as
28
+ * mysterious 429s.
29
+ */
30
+ export declare function resolveGlobalRateLimit(configured: ServerRateLimitsConfig["global"], tiers: {
31
+ sync: ResolvedRateLimit;
32
+ auth: ResolvedRateLimit;
33
+ defaultWindowMs: number;
34
+ headroom: number;
35
+ }): ResolvedGlobalRateLimit;
36
+ /**
37
+ * Bucket key for the sync tier.
38
+ *
39
+ * The principal is what makes this usable behind a NAT or a CDN: keyed on the
40
+ * address alone, one client opening several databases spends everyone else's
41
+ * budget. The address stays in the key so that callers we could not
42
+ * authenticate still get separated per source rather than sharing one
43
+ * "anonymous" bucket.
44
+ */
45
+ export declare function buildSyncRateLimitKey(tenantId: string | undefined, principal: string | undefined, ip: string | undefined): string;
46
+ /**
47
+ * Rate-limit defaults. Auth and sync have separate tiers because a single
48
+ * challenge/authenticate handshake is two requests; with multiple tenants,
49
+ * store kinds, and token renewals the auth budget exhausts quickly if set
50
+ * too low. These can be overridden in config.json under `rateLimits.auth`
51
+ * and `rateLimits.sync`.
52
+ */
53
+ export declare const DEFAULT_AUTH_RATE_LIMIT_WINDOW_MS = 60000;
54
+ export declare const DEFAULT_AUTH_RATE_LIMIT_MAX = 120;
55
+ export declare const DEFAULT_SYNC_RATE_LIMIT_WINDOW_MS = 60000;
56
+ /**
57
+ * Sync is keyed per authenticated principal, and a client legitimately opens
58
+ * several databases at once: a host like Teacher's Desk reads six, and a cold
59
+ * pass over one of them costs a few hundred batched requests. A budget sized
60
+ * for a single database turns that normal open into a throttled one.
61
+ */
62
+ export declare const DEFAULT_SYNC_RATE_LIMIT_MAX = 3000;
63
+ /**
64
+ * The global limiter is a coarse net for unauthenticated floods and unknown
65
+ * routes; the per-tier limiters are the real budgets. It runs first, so a
66
+ * global max below a tier max silently replaces that tier — the sync budget
67
+ * was unreachable while this was a hardcoded 500. The default is therefore
68
+ * derived from the tiers, and an override that would shadow one is honoured
69
+ * but flagged at startup rather than quietly applied.
70
+ */
71
+ export declare const DEFAULT_GLOBAL_RATE_LIMIT_WINDOW_MS = 60000;
72
+ export declare const GLOBAL_RATE_LIMIT_TIER_HEADROOM = 500;
73
+ /**
74
+ * Timestamping gets its own budget rather than sharing the sync one. Sync is
75
+ * chatty and high-volume; sealing a document is a deliberate, rare user action.
76
+ * Sharing a bucket would let ordinary sync traffic starve the seal endpoint, and
77
+ * a runaway seal loop degrade sync.
78
+ *
79
+ * The daily cap matters more than the burst cap: proxying collapses every user
80
+ * onto the server's IP, so one tenant can exhaust an upstream's per-IP allowance
81
+ * (open-tsa.eu publishes 60/minute) and lock out everyone else on this server.
82
+ */
83
+ export declare const DEFAULT_TIMESTAMP_RATE_LIMIT_WINDOW_MS = 60000;
84
+ export declare const DEFAULT_TIMESTAMP_RATE_LIMIT_MAX = 10;
85
+ export declare const DEFAULT_TIMESTAMP_DAILY_LIMIT_MAX = 500;
86
+ export declare const TIMESTAMP_DAILY_WINDOW_MS: number;
87
+ /** `/system/*` admin routes: aggressive per-IP throttle. */
88
+ export declare const DEFAULT_SYSTEM_RATE_LIMIT_WINDOW_MS = 60000;
89
+ export declare const DEFAULT_SYSTEM_RATE_LIMIT_MAX = 30;
90
+ /** Device discovery scans every local tenant per request. */
91
+ export declare const DEVICE_DISCOVERY_RATE_LIMIT: ResolvedRateLimit;
92
+ /** Server-to-server peer routes; replication must not crawl. */
93
+ export declare const PEER_RATE_LIMIT: ResolvedRateLimit;
94
+ /** Every limiter tier of a server, resolved against `config.json`. */
95
+ export interface ServerRateLimitPolicy {
96
+ global: ResolvedGlobalRateLimit;
97
+ auth: ResolvedRateLimit;
98
+ sync: ResolvedRateLimit;
99
+ timestamp: ResolvedRateLimit & {
100
+ dailyMax: number;
101
+ };
102
+ system: ResolvedRateLimit;
103
+ device: ResolvedRateLimit;
104
+ peer: ResolvedRateLimit;
105
+ }
106
+ export declare function resolveServerRateLimits(rateLimits: ServerRateLimitsConfig | undefined): ServerRateLimitPolicy;
107
+ /**
108
+ * Composite key for the auth tier. Challenge requests include the username so
109
+ * that brute-forcing one account doesn't block legitimate users on the same
110
+ * IP. Authenticate requests omit username because the challenge token is
111
+ * already scoped to a single principal.
112
+ */
113
+ export declare function buildAuthRateLimitKey(scope: "system" | "tenant", phase: "challenge" | "authenticate", tenantId: string | undefined, clientIp: string, body: {
114
+ username?: unknown;
115
+ signingPublicKey?: unknown;
116
+ } | undefined): string;
117
+ /** Bucket key of the timestamp tiers: tenant + principal + IP. */
118
+ export declare function buildTimestampRateLimitKey(tier: "burst" | "daily", tenantId: string | undefined, principal: string | undefined, clientIp: string): string;
119
+ //# sourceMappingURL=rateLimits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimits.d.ts","sourceRoot":"","sources":["../../src/server/rateLimits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,uBAAwB,SAAQ,iBAAiB;IAChE,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EAC5C,KAAK,EAAE;IACL,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,iBAAiB,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACA,uBAAuB,CAiBzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,EAAE,EAAE,MAAM,GAAG,SAAS,GACrB,MAAM,CAER;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,QAAS,CAAC;AACxD,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,iCAAiC,QAAS,CAAC;AACxD;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AAEjD;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,QAAS,CAAC;AAC1D,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,sCAAsC,QAAS,CAAC;AAC7D,eAAO,MAAM,gCAAgC,KAAK,CAAC;AACnD,eAAO,MAAM,iCAAiC,MAAM,CAAC;AACrD,eAAO,MAAM,yBAAyB,QAAsB,CAAC;AAE7D,4DAA4D;AAC5D,eAAO,MAAM,mCAAmC,QAAS,CAAC;AAC1D,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,2BAA2B,EAAE,iBAAiD,CAAC;AAE5F,gEAAgE;AAChE,eAAO,MAAM,eAAe,EAAE,iBAAkD,CAAC;AAEjF,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,uBAAuB,CAAC;IAChC,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,EAAE,iBAAiB,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,sBAAsB,GAAG,SAAS,GAC7C,qBAAqB,CA+BvB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,QAAQ,GAAG,QAAQ,EAC1B,KAAK,EAAE,WAAW,GAAG,cAAc,EACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,GACnE,MAAM,CAgBR;AAED,kEAAkE;AAClE,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,OAAO,GAAG,OAAO,EACvB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,QAAQ,EAAE,MAAM,GACf,MAAM,CAER"}