@enbox/dwn-server 0.0.3 → 0.0.4

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 (295) hide show
  1. package/LICENSE +3 -2
  2. package/README.md +112 -212
  3. package/dist/esm/src/admin/activity-log.d.ts +44 -0
  4. package/dist/esm/src/admin/activity-log.d.ts.map +1 -0
  5. package/dist/esm/src/admin/activity-log.js +85 -0
  6. package/dist/esm/src/admin/activity-log.js.map +1 -0
  7. package/dist/esm/src/admin/admin-api.d.ts +61 -0
  8. package/dist/esm/src/admin/admin-api.d.ts.map +1 -0
  9. package/dist/esm/src/admin/admin-api.js +1047 -0
  10. package/dist/esm/src/admin/admin-api.js.map +1 -0
  11. package/dist/esm/src/admin/admin-auth.d.ts +9 -0
  12. package/dist/esm/src/admin/admin-auth.d.ts.map +1 -0
  13. package/dist/esm/src/admin/admin-auth.js +45 -0
  14. package/dist/esm/src/admin/admin-auth.js.map +1 -0
  15. package/dist/esm/src/admin/admin-store.d.ts +111 -0
  16. package/dist/esm/src/admin/admin-store.d.ts.map +1 -0
  17. package/dist/esm/src/admin/admin-store.js +376 -0
  18. package/dist/esm/src/admin/admin-store.js.map +1 -0
  19. package/dist/esm/src/admin/audit-log.d.ts +94 -0
  20. package/dist/esm/src/admin/audit-log.d.ts.map +1 -0
  21. package/dist/esm/src/admin/audit-log.js +220 -0
  22. package/dist/esm/src/admin/audit-log.js.map +1 -0
  23. package/dist/esm/src/admin/index.d.ts +10 -0
  24. package/dist/esm/src/admin/index.d.ts.map +1 -0
  25. package/dist/esm/src/admin/index.js +7 -0
  26. package/dist/esm/src/admin/index.js.map +1 -0
  27. package/dist/esm/src/admin/types.d.ts +306 -0
  28. package/dist/esm/src/admin/types.d.ts.map +1 -0
  29. package/dist/esm/src/admin/types.js +2 -0
  30. package/dist/esm/src/admin/types.js.map +1 -0
  31. package/dist/esm/src/admin/webhook-manager.d.ts +55 -0
  32. package/dist/esm/src/admin/webhook-manager.d.ts.map +1 -0
  33. package/dist/esm/src/admin/webhook-manager.js +184 -0
  34. package/dist/esm/src/admin/webhook-manager.js.map +1 -0
  35. package/dist/esm/src/config.d.ts +122 -3
  36. package/dist/esm/src/config.d.ts.map +1 -1
  37. package/dist/esm/src/config.js +151 -5
  38. package/dist/esm/src/config.js.map +1 -1
  39. package/dist/esm/src/connection/connection-manager.d.ts +24 -1
  40. package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
  41. package/dist/esm/src/connection/connection-manager.js +33 -2
  42. package/dist/esm/src/connection/connection-manager.js.map +1 -1
  43. package/dist/esm/src/connection/flow-controller.d.ts +53 -0
  44. package/dist/esm/src/connection/flow-controller.d.ts.map +1 -0
  45. package/dist/esm/src/connection/flow-controller.js +101 -0
  46. package/dist/esm/src/connection/flow-controller.js.map +1 -0
  47. package/dist/esm/src/connection/socket-connection.d.ts +39 -4
  48. package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
  49. package/dist/esm/src/connection/socket-connection.js +80 -9
  50. package/dist/esm/src/connection/socket-connection.js.map +1 -1
  51. package/dist/esm/src/delivery-service.d.ts +43 -0
  52. package/dist/esm/src/delivery-service.d.ts.map +1 -0
  53. package/dist/esm/src/delivery-service.js +574 -0
  54. package/dist/esm/src/delivery-service.js.map +1 -0
  55. package/dist/esm/src/dwn-error.d.ts +10 -1
  56. package/dist/esm/src/dwn-error.d.ts.map +1 -1
  57. package/dist/esm/src/dwn-error.js +9 -0
  58. package/dist/esm/src/dwn-error.js.map +1 -1
  59. package/dist/esm/src/dwn-server.d.ts +8 -0
  60. package/dist/esm/src/dwn-server.d.ts.map +1 -1
  61. package/dist/esm/src/dwn-server.js +198 -12
  62. package/dist/esm/src/dwn-server.js.map +1 -1
  63. package/dist/esm/src/http-api.d.ts +19 -2
  64. package/dist/esm/src/http-api.d.ts.map +1 -1
  65. package/dist/esm/src/http-api.js +219 -19
  66. package/dist/esm/src/http-api.js.map +1 -1
  67. package/dist/esm/src/index.d.ts +6 -2
  68. package/dist/esm/src/index.d.ts.map +1 -1
  69. package/dist/esm/src/index.js +4 -1
  70. package/dist/esm/src/index.js.map +1 -1
  71. package/dist/esm/src/json-rpc-api.js +2 -1
  72. package/dist/esm/src/json-rpc-api.js.map +1 -1
  73. package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -1
  74. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +106 -4
  75. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -1
  76. package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts +20 -0
  77. package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts.map +1 -0
  78. package/dist/esm/src/json-rpc-handlers/subscription/ack.js +41 -0
  79. package/dist/esm/src/json-rpc-handlers/subscription/ack.js.map +1 -0
  80. package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -1
  81. package/dist/esm/src/json-rpc-handlers/subscription/close.js +1 -1
  82. package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -1
  83. package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +1 -0
  84. package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -1
  85. package/dist/esm/src/json-rpc-handlers/subscription/index.js +1 -0
  86. package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -1
  87. package/dist/esm/src/lib/json-rpc-router.d.ts +22 -4
  88. package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -1
  89. package/dist/esm/src/lib/json-rpc-router.js.map +1 -1
  90. package/dist/esm/src/lib/sql-utils.d.ts +6 -0
  91. package/dist/esm/src/lib/sql-utils.d.ts.map +1 -0
  92. package/dist/esm/src/lib/sql-utils.js +8 -0
  93. package/dist/esm/src/lib/sql-utils.js.map +1 -0
  94. package/dist/esm/src/main.js +0 -6
  95. package/dist/esm/src/main.js.map +1 -1
  96. package/dist/esm/src/message-processed-hook.d.ts +35 -0
  97. package/dist/esm/src/message-processed-hook.d.ts.map +1 -0
  98. package/dist/esm/src/message-processed-hook.js +2 -0
  99. package/dist/esm/src/message-processed-hook.js.map +1 -0
  100. package/dist/esm/src/metrics.d.ts +13 -1
  101. package/dist/esm/src/metrics.d.ts.map +1 -1
  102. package/dist/esm/src/metrics.js +41 -1
  103. package/dist/esm/src/metrics.js.map +1 -1
  104. package/dist/esm/src/plugins/event-log-nats.d.ts +25 -0
  105. package/dist/esm/src/plugins/event-log-nats.d.ts.map +1 -0
  106. package/dist/esm/src/plugins/event-log-nats.js +379 -0
  107. package/dist/esm/src/plugins/event-log-nats.js.map +1 -0
  108. package/dist/esm/src/rate-limiter.d.ts +60 -0
  109. package/dist/esm/src/rate-limiter.d.ts.map +1 -0
  110. package/dist/esm/src/rate-limiter.js +116 -0
  111. package/dist/esm/src/rate-limiter.js.map +1 -0
  112. package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts +53 -0
  113. package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts.map +1 -0
  114. package/dist/esm/src/registration/jwt-provider-auth-plugin.js +90 -0
  115. package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -0
  116. package/dist/esm/src/registration/open-auth-handler.d.ts +37 -0
  117. package/dist/esm/src/registration/open-auth-handler.d.ts.map +1 -0
  118. package/dist/esm/src/registration/open-auth-handler.js +214 -0
  119. package/dist/esm/src/registration/open-auth-handler.js.map +1 -0
  120. package/dist/esm/src/registration/proof-of-work-manager.d.ts +1 -1
  121. package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
  122. package/dist/esm/src/registration/provider-auth-plugin.d.ts +46 -0
  123. package/dist/esm/src/registration/provider-auth-plugin.d.ts.map +1 -0
  124. package/dist/esm/src/registration/provider-auth-plugin.js +29 -0
  125. package/dist/esm/src/registration/provider-auth-plugin.js.map +1 -0
  126. package/dist/esm/src/registration/registration-manager.d.ts +27 -4
  127. package/dist/esm/src/registration/registration-manager.d.ts.map +1 -1
  128. package/dist/esm/src/registration/registration-manager.js +77 -6
  129. package/dist/esm/src/registration/registration-manager.js.map +1 -1
  130. package/dist/esm/src/registration/registration-store.d.ts +83 -3
  131. package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
  132. package/dist/esm/src/registration/registration-store.js +248 -11
  133. package/dist/esm/src/registration/registration-store.js.map +1 -1
  134. package/dist/esm/src/storage.d.ts +4 -4
  135. package/dist/esm/src/storage.d.ts.map +1 -1
  136. package/dist/esm/src/storage.js +100 -20
  137. package/dist/esm/src/storage.js.map +1 -1
  138. package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -1
  139. package/dist/esm/src/web5-connect/sql-ttl-cache.js +8 -1
  140. package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -1
  141. package/dist/esm/src/ws-api.d.ts +17 -1
  142. package/dist/esm/src/ws-api.d.ts.map +1 -1
  143. package/dist/esm/src/ws-api.js +9 -2
  144. package/dist/esm/src/ws-api.js.map +1 -1
  145. package/package.json +16 -16
  146. package/src/admin/activity-log.ts +100 -0
  147. package/src/admin/admin-api.ts +1308 -0
  148. package/src/admin/admin-auth.ts +56 -0
  149. package/src/admin/admin-store.ts +515 -0
  150. package/src/admin/audit-log.ts +327 -0
  151. package/src/admin/index.ts +34 -0
  152. package/src/admin/types.ts +352 -0
  153. package/src/admin/webhook-manager.ts +245 -0
  154. package/src/config.ts +177 -5
  155. package/src/connection/connection-manager.ts +50 -6
  156. package/src/connection/flow-controller.ts +117 -0
  157. package/src/connection/socket-connection.ts +103 -21
  158. package/src/delivery-service.ts +740 -0
  159. package/src/dwn-error.ts +9 -0
  160. package/src/dwn-server.ts +242 -14
  161. package/src/http-api.ts +271 -30
  162. package/src/index.ts +13 -2
  163. package/src/json-rpc-api.ts +2 -1
  164. package/src/json-rpc-handlers/dwn/process-message.ts +140 -5
  165. package/src/json-rpc-handlers/subscription/ack.ts +63 -0
  166. package/src/json-rpc-handlers/subscription/close.ts +2 -6
  167. package/src/json-rpc-handlers/subscription/index.ts +1 -0
  168. package/src/lib/json-rpc-router.ts +22 -6
  169. package/src/lib/sql-utils.ts +7 -0
  170. package/src/main.ts +0 -8
  171. package/src/message-processed-hook.ts +33 -0
  172. package/src/metrics.ts +50 -1
  173. package/src/plugins/event-log-nats.ts +466 -0
  174. package/src/rate-limiter.ts +143 -0
  175. package/src/registration/jwt-provider-auth-plugin.ts +119 -0
  176. package/src/registration/open-auth-handler.ts +263 -0
  177. package/src/registration/proof-of-work-manager.ts +1 -1
  178. package/src/registration/provider-auth-plugin.ts +84 -0
  179. package/src/registration/registration-manager.ts +108 -12
  180. package/src/registration/registration-store.ts +326 -17
  181. package/src/storage.ts +121 -27
  182. package/src/web5-connect/sql-ttl-cache.ts +7 -1
  183. package/src/ws-api.ts +30 -2
  184. package/dist/esm/src/json-rpc-socket.d.ts +0 -39
  185. package/dist/esm/src/json-rpc-socket.d.ts.map +0 -1
  186. package/dist/esm/src/json-rpc-socket.js +0 -125
  187. package/dist/esm/src/json-rpc-socket.js.map +0 -1
  188. package/dist/esm/src/lib/json-rpc.d.ts +0 -54
  189. package/dist/esm/src/lib/json-rpc.d.ts.map +0 -1
  190. package/dist/esm/src/lib/json-rpc.js +0 -60
  191. package/dist/esm/src/lib/json-rpc.js.map +0 -1
  192. package/dist/esm/src/registration/proof-of-work-types.d.ts +0 -8
  193. package/dist/esm/src/registration/proof-of-work-types.d.ts.map +0 -1
  194. package/dist/esm/src/registration/proof-of-work-types.js +0 -2
  195. package/dist/esm/src/registration/proof-of-work-types.js.map +0 -1
  196. package/dist/esm/src/registration/registration-types.d.ts +0 -18
  197. package/dist/esm/src/registration/registration-types.d.ts.map +0 -1
  198. package/dist/esm/src/registration/registration-types.js +0 -2
  199. package/dist/esm/src/registration/registration-types.js.map +0 -1
  200. package/dist/esm/tests/common-scenario-validator.d.ts +0 -11
  201. package/dist/esm/tests/common-scenario-validator.d.ts.map +0 -1
  202. package/dist/esm/tests/common-scenario-validator.js +0 -113
  203. package/dist/esm/tests/common-scenario-validator.js.map +0 -1
  204. package/dist/esm/tests/connection/connection-manager.spec.d.ts +0 -2
  205. package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +0 -1
  206. package/dist/esm/tests/connection/connection-manager.spec.js +0 -49
  207. package/dist/esm/tests/connection/connection-manager.spec.js.map +0 -1
  208. package/dist/esm/tests/connection/socket-connection.spec.d.ts +0 -2
  209. package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +0 -1
  210. package/dist/esm/tests/connection/socket-connection.spec.js +0 -147
  211. package/dist/esm/tests/connection/socket-connection.spec.js.map +0 -1
  212. package/dist/esm/tests/cors/http-api.browser.d.ts +0 -2
  213. package/dist/esm/tests/cors/http-api.browser.d.ts.map +0 -1
  214. package/dist/esm/tests/cors/http-api.browser.js +0 -60
  215. package/dist/esm/tests/cors/http-api.browser.js.map +0 -1
  216. package/dist/esm/tests/cors/ping.browser.d.ts +0 -2
  217. package/dist/esm/tests/cors/ping.browser.d.ts.map +0 -1
  218. package/dist/esm/tests/cors/ping.browser.js +0 -7
  219. package/dist/esm/tests/cors/ping.browser.js.map +0 -1
  220. package/dist/esm/tests/dwn-process-message.spec.d.ts +0 -2
  221. package/dist/esm/tests/dwn-process-message.spec.d.ts.map +0 -1
  222. package/dist/esm/tests/dwn-process-message.spec.js +0 -172
  223. package/dist/esm/tests/dwn-process-message.spec.js.map +0 -1
  224. package/dist/esm/tests/dwn-server.spec.d.ts +0 -2
  225. package/dist/esm/tests/dwn-server.spec.d.ts.map +0 -1
  226. package/dist/esm/tests/dwn-server.spec.js +0 -48
  227. package/dist/esm/tests/dwn-server.spec.js.map +0 -1
  228. package/dist/esm/tests/http-api.spec.d.ts +0 -2
  229. package/dist/esm/tests/http-api.spec.d.ts.map +0 -1
  230. package/dist/esm/tests/http-api.spec.js +0 -782
  231. package/dist/esm/tests/http-api.spec.js.map +0 -1
  232. package/dist/esm/tests/json-rpc-socket.spec.d.ts +0 -2
  233. package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +0 -1
  234. package/dist/esm/tests/json-rpc-socket.spec.js +0 -227
  235. package/dist/esm/tests/json-rpc-socket.spec.js.map +0 -1
  236. package/dist/esm/tests/plugins/data-store-sqlite.d.ts +0 -17
  237. package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +0 -1
  238. package/dist/esm/tests/plugins/data-store-sqlite.js +0 -23
  239. package/dist/esm/tests/plugins/data-store-sqlite.js.map +0 -1
  240. package/dist/esm/tests/plugins/event-log-sqlite.d.ts +0 -17
  241. package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +0 -1
  242. package/dist/esm/tests/plugins/event-log-sqlite.js +0 -23
  243. package/dist/esm/tests/plugins/event-log-sqlite.js.map +0 -1
  244. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +0 -17
  245. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +0 -1
  246. package/dist/esm/tests/plugins/event-stream-in-memory.js +0 -21
  247. package/dist/esm/tests/plugins/event-stream-in-memory.js.map +0 -1
  248. package/dist/esm/tests/plugins/message-store-sqlite.d.ts +0 -17
  249. package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +0 -1
  250. package/dist/esm/tests/plugins/message-store-sqlite.js +0 -23
  251. package/dist/esm/tests/plugins/message-store-sqlite.js.map +0 -1
  252. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +0 -17
  253. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +0 -1
  254. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +0 -23
  255. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +0 -1
  256. package/dist/esm/tests/process-handler.spec.d.ts +0 -2
  257. package/dist/esm/tests/process-handler.spec.d.ts.map +0 -1
  258. package/dist/esm/tests/process-handler.spec.js +0 -60
  259. package/dist/esm/tests/process-handler.spec.js.map +0 -1
  260. package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +0 -2
  261. package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +0 -1
  262. package/dist/esm/tests/registration/proof-of-work-manager.spec.js +0 -156
  263. package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +0 -1
  264. package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +0 -2
  265. package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +0 -1
  266. package/dist/esm/tests/rpc-subscribe-close.spec.js +0 -81
  267. package/dist/esm/tests/rpc-subscribe-close.spec.js.map +0 -1
  268. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +0 -2
  269. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +0 -1
  270. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +0 -74
  271. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +0 -1
  272. package/dist/esm/tests/scenarios/registration.spec.d.ts +0 -2
  273. package/dist/esm/tests/scenarios/registration.spec.d.ts.map +0 -1
  274. package/dist/esm/tests/scenarios/registration.spec.js +0 -511
  275. package/dist/esm/tests/scenarios/registration.spec.js.map +0 -1
  276. package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +0 -2
  277. package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +0 -1
  278. package/dist/esm/tests/scenarios/web5-connect.spec.js +0 -141
  279. package/dist/esm/tests/scenarios/web5-connect.spec.js.map +0 -1
  280. package/dist/esm/tests/test-dwn.d.ts +0 -7
  281. package/dist/esm/tests/test-dwn.d.ts.map +0 -1
  282. package/dist/esm/tests/test-dwn.js +0 -28
  283. package/dist/esm/tests/test-dwn.js.map +0 -1
  284. package/dist/esm/tests/utils.d.ts +0 -43
  285. package/dist/esm/tests/utils.d.ts.map +0 -1
  286. package/dist/esm/tests/utils.js +0 -107
  287. package/dist/esm/tests/utils.js.map +0 -1
  288. package/dist/esm/tests/ws-api.spec.d.ts +0 -2
  289. package/dist/esm/tests/ws-api.spec.d.ts.map +0 -1
  290. package/dist/esm/tests/ws-api.spec.js +0 -332
  291. package/dist/esm/tests/ws-api.spec.js.map +0 -1
  292. package/src/json-rpc-socket.ts +0 -156
  293. package/src/lib/json-rpc.ts +0 -126
  294. package/src/registration/proof-of-work-types.ts +0 -7
  295. package/src/registration/registration-types.ts +0 -18
@@ -1,13 +1,16 @@
1
1
  import type { Dialect } from '@enbox/dwn-sql-store';
2
- import type { RegistrationData } from './registration-types.js';
2
+ import type { TenantQuota } from '../admin/types.js';
3
3
 
4
- import { Kysely } from 'kysely';
4
+ import { Kysely, sql } from 'kysely';
5
+
6
+ import { escapeLikeWildcards } from '../lib/sql-utils.js';
5
7
 
6
8
  /**
7
9
  * The RegistrationStore is responsible for storing and retrieving tenant registration information.
8
10
  */
9
11
  export class RegistrationStore {
10
12
  private static readonly registeredTenantTableName = 'registeredTenants';
13
+ private static readonly tenantQuotasTableName = 'tenantQuotas';
11
14
 
12
15
  private db: Kysely<RegistrationDatabase>;
13
16
 
@@ -19,11 +22,11 @@ export class RegistrationStore {
19
22
  * Creates a new RegistrationStore instance.
20
23
  */
21
24
  public static async create(sqlDialect: Dialect): Promise<RegistrationStore> {
22
- const proofOfWorkManager = new RegistrationStore(sqlDialect);
25
+ const store = new RegistrationStore(sqlDialect);
23
26
 
24
- await proofOfWorkManager.initialize();
27
+ await store.initialize();
25
28
 
26
- return proofOfWorkManager;
29
+ return store;
27
30
  }
28
31
 
29
32
  private async initialize(): Promise<void> {
@@ -32,33 +35,101 @@ export class RegistrationStore {
32
35
  .ifNotExists()
33
36
  .addColumn('did', 'text', (column) => column.primaryKey())
34
37
  .addColumn('termsOfServiceHash', 'text')
38
+ .addColumn('suspended', 'integer', (column) => column.defaultTo(0))
39
+ .execute();
40
+
41
+ // Add the `suspended` column to existing tables that don't have it yet.
42
+ // Kysely doesn't support `ADD COLUMN IF NOT EXISTS` across all dialects, so we
43
+ // catch and ignore the "column already exists" error.
44
+ try {
45
+ await this.db.schema
46
+ .alterTable(RegistrationStore.registeredTenantTableName)
47
+ .addColumn('suspended', 'integer', (column) => column.defaultTo(0))
48
+ .execute();
49
+ } catch {
50
+ // Column already exists — expected for new installations.
51
+ }
52
+
53
+ // Add provider-auth columns (idempotent migration). https://github.com/enboxorg/enbox/issues/404
54
+ for (const col of ['accountId', 'registrationType', 'registeredAt', 'metadata']) {
55
+ try {
56
+ await this.db.schema
57
+ .alterTable(RegistrationStore.registeredTenantTableName)
58
+ .addColumn(col, 'text')
59
+ .execute();
60
+ } catch {
61
+ // Column already exists — expected.
62
+ }
63
+ }
64
+
65
+ // Per-tenant storage quotas table.
66
+ await this.db.schema
67
+ .createTable(RegistrationStore.tenantQuotasTableName)
68
+ .ifNotExists()
69
+ .addColumn('did', 'text', (column) => column.primaryKey())
70
+ .addColumn('maxMessages', 'integer', (column) => column.defaultTo(0))
71
+ .addColumn('maxStorageBytes', 'bigint', (column) => column.defaultTo(0))
35
72
  .execute();
36
73
  }
37
74
 
38
75
  /**
39
76
  * Inserts or updates the tenant registration information.
40
77
  */
41
- public async insertOrUpdateTenantRegistration(registrationData: RegistrationData): Promise<void> {
78
+ public async insertOrUpdateTenantRegistration(registrationData: {
79
+ did: string;
80
+ termsOfServiceHash?: string;
81
+ accountId?: string;
82
+ registrationType?: string;
83
+ metadata?: string;
84
+ }): Promise<void> {
85
+ const insertValues: Record<string, unknown> = {
86
+ did : registrationData.did,
87
+ termsOfServiceHash : registrationData.termsOfServiceHash ?? '',
88
+ suspended : 0,
89
+ registeredAt : new Date().toISOString(),
90
+ };
91
+
92
+ if (registrationData.accountId !== undefined) {
93
+ insertValues.accountId = registrationData.accountId;
94
+ }
95
+ if (registrationData.registrationType !== undefined) {
96
+ insertValues.registrationType = registrationData.registrationType;
97
+ }
98
+ if (registrationData.metadata !== undefined) {
99
+ insertValues.metadata = registrationData.metadata;
100
+ }
101
+
42
102
  await this.db
43
103
  .insertInto(RegistrationStore.registeredTenantTableName)
44
- .values(registrationData)
104
+ .values(insertValues as any)
45
105
  .onConflict((oc) =>
46
- oc.column('did').doUpdateSet((eb) => ({
47
- termsOfServiceHash: eb.ref('excluded.termsOfServiceHash'),
48
- })),
106
+ oc.column('did').doUpdateSet((eb) => {
107
+ const updateSet: Record<string, any> = {
108
+ termsOfServiceHash: eb.ref('excluded.termsOfServiceHash'),
109
+ };
110
+ // Do NOT overwrite registeredAt on update.
111
+ if (registrationData.accountId !== undefined) {
112
+ updateSet.accountId = registrationData.accountId;
113
+ }
114
+ if (registrationData.registrationType !== undefined) {
115
+ updateSet.registrationType = registrationData.registrationType;
116
+ }
117
+ if (registrationData.metadata !== undefined) {
118
+ updateSet.metadata = registrationData.metadata;
119
+ }
120
+ return updateSet;
121
+ }),
49
122
  )
50
- // Executes the query. No error is thrown if the query doesn’t affect any rows (ie. if the insert or update didn’t change anything).
51
123
  .executeTakeFirst();
52
124
  }
53
125
 
54
126
  /**
55
127
  * Retrieves the tenant registration information.
56
128
  */
57
- public async getTenantRegistration(tenantDid: string): Promise<RegistrationData | undefined> {
129
+ public async getTenantRegistration(tenantDid: string): Promise<RegisteredTenantRow | undefined> {
58
130
  const result = await this.db
59
131
  .selectFrom(RegistrationStore.registeredTenantTableName)
60
- .select('did')
61
- .select('termsOfServiceHash')
132
+ .select(['did', 'termsOfServiceHash', 'suspended', 'accountId', 'registrationType', 'registeredAt', 'metadata'])
62
133
  .where('did', '=', tenantDid)
63
134
  .execute();
64
135
 
@@ -68,13 +139,251 @@ export class RegistrationStore {
68
139
 
69
140
  return result[0];
70
141
  }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // Admin operations
145
+ // ---------------------------------------------------------------------------
146
+
147
+ /**
148
+ * Returns a paginated list of registered tenants with optional search and status filtering.
149
+ *
150
+ * @see https://github.com/enboxorg/enbox/issues/390
151
+ */
152
+ public async listTenants(options?: {
153
+ cursor? : string;
154
+ limit? : number;
155
+ search? : string;
156
+ status? : 'active' | 'suspended';
157
+ }): Promise<{ tenants: RegisteredTenantRow[]; cursor?: string }> {
158
+ const limit = options?.limit ?? 20;
159
+
160
+ let query = this.db
161
+ .selectFrom(RegistrationStore.registeredTenantTableName)
162
+ .select(['did', 'termsOfServiceHash', 'suspended', 'accountId', 'registrationType', 'registeredAt', 'metadata'])
163
+ .orderBy('did', 'asc')
164
+ .limit(limit + 1); // fetch one extra to detect next page
165
+
166
+ if (options?.cursor) {
167
+ query = query.where('did', '>', options.cursor);
168
+ }
169
+
170
+ if (options?.search) {
171
+ query = query.where('did', 'like', `%${escapeLikeWildcards(options.search)}%`);
172
+ }
173
+
174
+ if (options?.status === 'suspended') {
175
+ query = query.where('suspended', '=', 1);
176
+ } else if (options?.status === 'active') {
177
+ query = query.where('suspended', '=', 0);
178
+ }
179
+
180
+ const results = await query.execute();
181
+
182
+ let cursor: string | undefined;
183
+ if (results.length > limit) {
184
+ results.pop();
185
+ cursor = results[results.length - 1].did;
186
+ }
187
+
188
+ return { tenants: results, cursor };
189
+ }
190
+
191
+ /**
192
+ * Returns the total count of registered tenants matching optional filters.
193
+ */
194
+ public async getTenantCount(options?: {
195
+ search? : string;
196
+ status? : 'active' | 'suspended';
197
+ }): Promise<number> {
198
+ let query = this.db
199
+ .selectFrom(RegistrationStore.registeredTenantTableName)
200
+ .select(sql<number>`count(*)`.as('count'));
201
+
202
+ if (options?.search) {
203
+ query = query.where('did', 'like', `%${escapeLikeWildcards(options.search)}%`);
204
+ }
205
+
206
+ if (options?.status === 'suspended') {
207
+ query = query.where('suspended', '=', 1);
208
+ } else if (options?.status === 'active') {
209
+ query = query.where('suspended', '=', 0);
210
+ }
211
+
212
+ const result = await query.executeTakeFirstOrThrow();
213
+ return Number(result.count);
214
+ }
215
+
216
+ /**
217
+ * Inserts a new tenant registration. Returns `false` if the tenant already exists.
218
+ *
219
+ * @see https://github.com/enboxorg/enbox/issues/393
220
+ */
221
+ public async createTenant(did: string): Promise<boolean> {
222
+ try {
223
+ await this.db
224
+ .insertInto(RegistrationStore.registeredTenantTableName)
225
+ .values({ did, termsOfServiceHash: '', suspended: 0 })
226
+ .execute();
227
+ return true;
228
+ } catch {
229
+ // Unique constraint violation — tenant already exists.
230
+ return false;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Suspends a tenant. Returns `true` if the tenant was found and suspended.
236
+ */
237
+ public async suspendTenant(did: string): Promise<boolean> {
238
+ const result = await this.db
239
+ .updateTable(RegistrationStore.registeredTenantTableName)
240
+ .set({ suspended: 1 })
241
+ .where('did', '=', did)
242
+ .executeTakeFirst();
243
+
244
+ return Number(result.numUpdatedRows) > 0;
245
+ }
246
+
247
+ /**
248
+ * Unsuspends a tenant. Returns `true` if the tenant was found and unsuspended.
249
+ */
250
+ public async unsuspendTenant(did: string): Promise<boolean> {
251
+ const result = await this.db
252
+ .updateTable(RegistrationStore.registeredTenantTableName)
253
+ .set({ suspended: 0 })
254
+ .where('did', '=', did)
255
+ .executeTakeFirst();
256
+
257
+ return Number(result.numUpdatedRows) > 0;
258
+ }
259
+
260
+ /**
261
+ * Deletes a tenant registration. Returns `true` if the tenant was found and deleted.
262
+ */
263
+ public async deleteTenant(did: string): Promise<boolean> {
264
+ const result = await this.db
265
+ .deleteFrom(RegistrationStore.registeredTenantTableName)
266
+ .where('did', '=', did)
267
+ .executeTakeFirst();
268
+
269
+ return Number(result.numDeletedRows) > 0;
270
+ }
271
+
272
+ /**
273
+ * Returns all tenant registrations associated with the given account ID.
274
+ *
275
+ * @see https://github.com/enboxorg/enbox/issues/404
276
+ */
277
+ public async getTenantsForAccount(accountId: string): Promise<RegisteredTenantRow[]> {
278
+ return this.db
279
+ .selectFrom(RegistrationStore.registeredTenantTableName)
280
+ .select(['did', 'termsOfServiceHash', 'suspended', 'accountId', 'registrationType', 'registeredAt', 'metadata'])
281
+ .where('accountId', '=', accountId)
282
+ .orderBy('did', 'asc')
283
+ .execute();
284
+ }
285
+
286
+ /**
287
+ * Returns the count of suspended tenants.
288
+ */
289
+ public async getSuspendedCount(): Promise<number> {
290
+ const result = await this.db
291
+ .selectFrom(RegistrationStore.registeredTenantTableName)
292
+ .select(sql<number>`count(*)`.as('count'))
293
+ .where('suspended', '=', 1)
294
+ .executeTakeFirstOrThrow();
295
+
296
+ return Number(result.count);
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // Tenant quota operations
301
+ // ---------------------------------------------------------------------------
302
+
303
+ /**
304
+ * Returns the quota for a tenant, or `undefined` if no per-tenant quota is set.
305
+ */
306
+ public async getQuota(did: string): Promise<TenantQuota | undefined> {
307
+ const result = await this.db
308
+ .selectFrom(RegistrationStore.tenantQuotasTableName)
309
+ .select(['did', 'maxMessages', 'maxStorageBytes'])
310
+ .where('did', '=', did)
311
+ .executeTakeFirst();
312
+
313
+ if (result === undefined) {
314
+ return undefined;
315
+ }
316
+
317
+ return {
318
+ did : result.did,
319
+ maxMessages : Number(result.maxMessages),
320
+ maxStorageBytes : Number(result.maxStorageBytes),
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Sets (inserts or updates) the quota for a tenant.
326
+ */
327
+ public async setQuota(quota: TenantQuota): Promise<void> {
328
+ await this.db
329
+ .insertInto(RegistrationStore.tenantQuotasTableName)
330
+ .values({
331
+ did : quota.did,
332
+ maxMessages : quota.maxMessages,
333
+ maxStorageBytes : quota.maxStorageBytes,
334
+ })
335
+ .onConflict((oc) =>
336
+ oc.column('did').doUpdateSet({
337
+ maxMessages : quota.maxMessages,
338
+ maxStorageBytes : quota.maxStorageBytes,
339
+ }),
340
+ )
341
+ .executeTakeFirst();
342
+ }
343
+
344
+ /**
345
+ * Deletes the per-tenant quota. Returns `true` if a quota existed and was deleted.
346
+ */
347
+ public async deleteQuota(did: string): Promise<boolean> {
348
+ const result = await this.db
349
+ .deleteFrom(RegistrationStore.tenantQuotasTableName)
350
+ .where('did', '=', did)
351
+ .executeTakeFirst();
352
+
353
+ return Number(result.numDeletedRows) > 0;
354
+ }
355
+ }
356
+
357
+ /**
358
+ * A row in the `registeredTenants` table.
359
+ */
360
+ export interface RegisteredTenantRow {
361
+ did : string;
362
+ termsOfServiceHash : string;
363
+ suspended? : number;
364
+ accountId? : string;
365
+ registrationType? : string;
366
+ registeredAt? : string;
367
+ metadata? : string;
71
368
  }
72
369
 
73
370
  interface RegisteredTenants {
74
- did: string;
75
- termsOfServiceHash: string;
371
+ did : string;
372
+ termsOfServiceHash : string;
373
+ suspended : number;
374
+ accountId? : string;
375
+ registrationType? : string;
376
+ registeredAt? : string;
377
+ metadata? : string;
378
+ }
379
+
380
+ interface TenantQuotasRow {
381
+ did : string;
382
+ maxMessages : number;
383
+ maxStorageBytes : number;
76
384
  }
77
385
 
78
386
  interface RegistrationDatabase {
79
- registeredTenants: RegisteredTenants;
387
+ registeredTenants : RegisteredTenants;
388
+ tenantQuotas : TenantQuotasRow;
80
389
  }
package/src/storage.ts CHANGED
@@ -5,9 +5,9 @@ import type {
5
5
  DataStore,
6
6
  DwnConfig,
7
7
  EventLog,
8
- EventStream,
9
8
  MessageStore,
10
9
  ResumableTaskStore,
10
+ StateIndex,
11
11
  TenantGate,
12
12
  } from '@enbox/dwn-sdk-js';
13
13
 
@@ -16,29 +16,32 @@ import Cursor from 'pg-cursor';
16
16
  import { createPool as MySQLCreatePool } from 'mysql2';
17
17
  import pg from 'pg';
18
18
 
19
+ import { Kysely } from 'kysely';
20
+
19
21
  import { createBunSqliteDatabase } from '@enbox/dwn-sql-store';
20
22
  import { PluginLoader } from './plugin-loader.js';
21
23
 
22
24
  import {
23
25
  DataStoreLevel,
24
- EventLogLevel,
25
26
  MessageStoreLevel,
26
27
  ResumableTaskStoreLevel,
28
+ StateIndexLevel,
27
29
  } from '@enbox/dwn-sdk-js';
28
30
  import {
29
31
  DataStoreSql,
30
- EventLogSql,
31
32
  MessageStoreSql,
32
33
  MysqlDialect,
33
34
  PostgresDialect,
34
35
  ResumableTaskStoreSql,
36
+ runDwnStoreMigrations,
35
37
  SqliteDialect,
38
+ StateIndexSql,
36
39
  } from '@enbox/dwn-sql-store';
37
40
 
38
41
  export enum StoreType {
39
42
  DataStore,
40
43
  MessageStore,
41
- EventLog,
44
+ StateIndex,
42
45
  ResumableTaskStore,
43
46
  }
44
47
 
@@ -49,23 +52,113 @@ export enum BackendTypes {
49
52
  POSTGRES = 'postgres',
50
53
  }
51
54
 
52
- export type DwnStore = DataStore | EventLog | MessageStore | ResumableTaskStore;
55
+ export type DwnStore = DataStore | StateIndex | MessageStore | ResumableTaskStore;
56
+
57
+ /**
58
+ * Cache of shared PostgreSQL dialects keyed by connection URL. When multiple
59
+ * DWN stores share the same Postgres URL, they reuse a single dialect (and
60
+ * thus a single `pg.Pool`) instead of each creating their own. This reduces
61
+ * connection count from 4 × pool_max to 1 × pool_max per DWN process.
62
+ */
63
+ const sharedDialectCache: Map<string, Dialect> = new Map();
64
+
65
+ /**
66
+ * Returns a (potentially cached) dialect for the given Postgres connection URL.
67
+ * Non-Postgres URLs always return a fresh dialect (no caching).
68
+ */
69
+ function getOrCreateDialect(connectionUrl: URL, config: DwnServerConfig): Dialect {
70
+ const protocol = connectionUrl.protocol.slice(0, -1);
71
+
72
+ if (protocol !== BackendTypes.POSTGRES) {
73
+ return getDialectFromUrl(connectionUrl);
74
+ }
75
+
76
+ const key = connectionUrl.toString();
77
+ const cached = sharedDialectCache.get(key);
78
+ if (cached !== undefined) {
79
+ return cached;
80
+ }
81
+
82
+ // Create a single pg.Pool instance with configurable sizing.
83
+ const pool = new pg.Pool({
84
+ connectionString : connectionUrl.toString(),
85
+ min : config.pgPoolMin,
86
+ max : config.pgPoolMax,
87
+ idleTimeoutMillis : config.pgPoolIdleTimeout,
88
+ });
89
+
90
+ const dialect = new PostgresDialect({
91
+ pool : async (): Promise<pg.Pool> => pool,
92
+ cursor : Cursor,
93
+ });
94
+
95
+ sharedDialectCache.set(key, dialect);
96
+ return dialect;
97
+ }
53
98
 
54
99
  export async function getDwnConfig(
55
100
  config : DwnServerConfig,
56
101
  options : {
57
102
  didResolver? : DidResolver,
58
103
  tenantGate? : TenantGate,
59
- eventStream? : EventStream,
104
+ eventLog? : EventLog,
60
105
  }
61
106
  ): Promise<DwnConfig> {
62
- const { tenantGate, eventStream, didResolver } = options;
63
- const dataStore: DataStore = await getStore(config.dataStore, StoreType.DataStore);
64
- const eventLog: EventLog = await getStore(config.eventLog, StoreType.EventLog);
65
- const messageStore: MessageStore = await getStore(config.messageStore, StoreType.MessageStore);
66
- const resumableTaskStore: ResumableTaskStore = await getStore(config.resumableTaskStore, StoreType.ResumableTaskStore);
107
+ const { tenantGate, eventLog, didResolver } = options;
67
108
 
68
- return { didResolver, eventStream, eventLog, dataStore, messageStore, resumableTaskStore, tenantGate };
109
+ // Run SQL schema migrations before creating stores. Uses the data store
110
+ // connection to determine the dialect — all SQL stores typically share the
111
+ // same database. Non-SQL backends (level://) are skipped.
112
+ await runSqlMigrationsIfNeeded(config);
113
+
114
+ const dataStore: DataStore = await getStore(config, config.dataStore, StoreType.DataStore);
115
+ const stateIndex: StateIndex = await getStore(config, config.stateIndex, StoreType.StateIndex);
116
+ const messageStore: MessageStore = await getStore(config, config.messageStore, StoreType.MessageStore);
117
+ const resumableTaskStore: ResumableTaskStore = await getStore(config, config.resumableTaskStore, StoreType.ResumableTaskStore);
118
+
119
+ return { didResolver, eventLog, stateIndex, dataStore, messageStore, resumableTaskStore, tenantGate };
120
+ }
121
+
122
+ /**
123
+ * Runs DWN SQL schema migrations if the data store is configured with a SQL
124
+ * backend. Creates a temporary Kysely instance, runs all pending migrations,
125
+ * then destroys it. The subsequent store `open()` calls will reuse the shared
126
+ * dialect/pool and find the schema already in place.
127
+ */
128
+ async function runSqlMigrationsIfNeeded(config: DwnServerConfig): Promise<void> {
129
+ // Skip if the data store config is a file path (plugin) or non-SQL backend
130
+ if (isFilePath(config.dataStore)) {
131
+ return;
132
+ }
133
+
134
+ let storeUrl: URL;
135
+ try {
136
+ storeUrl = new URL(config.dataStore);
137
+ } catch {
138
+ return; // Not a valid URL — skip
139
+ }
140
+
141
+ const protocol = storeUrl.protocol.slice(0, -1);
142
+ const sqlBackends: string[] = [BackendTypes.SQLITE, BackendTypes.MYSQL, BackendTypes.POSTGRES];
143
+ if (!sqlBackends.includes(protocol)) {
144
+ return;
145
+ }
146
+
147
+ const dialect = getOrCreateDialect(storeUrl, config);
148
+ const db = new Kysely<Record<string, unknown>>({ dialect });
149
+ try {
150
+ const applied = await runDwnStoreMigrations(db, dialect);
151
+ if (applied.length > 0) {
152
+ console.log(`DWN migrations applied: ${applied.join(', ')}`);
153
+ }
154
+ } finally {
155
+ // Don't destroy the Kysely instance if using a shared Postgres pool —
156
+ // the pool is cached in sharedDialectCache and will be reused by stores.
157
+ // Only destroy for non-cached dialects (SQLite, MySQL).
158
+ if (protocol !== BackendTypes.POSTGRES) {
159
+ await db.destroy();
160
+ }
161
+ }
69
162
  }
70
163
 
71
164
  function getLevelStore(
@@ -82,9 +175,9 @@ function getLevelStore(
82
175
  blockstoreLocation : storeURI.host + storeURI.pathname + '/MESSAGESTORE',
83
176
  indexLocation : storeURI.host + storeURI.pathname + '/INDEX',
84
177
  });
85
- case StoreType.EventLog:
86
- return new EventLogLevel({
87
- location: storeURI.host + storeURI.pathname + '/EVENTLOG',
178
+ case StoreType.StateIndex:
179
+ return new StateIndexLevel({
180
+ location: storeURI.host + storeURI.pathname + '/STATEINDEX',
88
181
  });
89
182
  case StoreType.ResumableTaskStore:
90
183
  return new ResumableTaskStoreLevel({
@@ -96,18 +189,19 @@ function getLevelStore(
96
189
  }
97
190
 
98
191
  function getSqlStore(
192
+ config: DwnServerConfig,
99
193
  connectionUrl: URL,
100
194
  storeType: StoreType,
101
195
  ): DwnStore {
102
- const dialect = getDialectFromUrl(connectionUrl);
196
+ const dialect = getOrCreateDialect(connectionUrl, config);
103
197
 
104
198
  switch (storeType) {
105
199
  case StoreType.DataStore:
106
200
  return new DataStoreSql(dialect);
107
201
  case StoreType.MessageStore:
108
202
  return new MessageStoreSql(dialect);
109
- case StoreType.EventLog:
110
- return new EventLogSql(dialect);
203
+ case StoreType.StateIndex:
204
+ return new StateIndexSql(dialect);
111
205
  case StoreType.ResumableTaskStore:
112
206
  return new ResumableTaskStoreSql(dialect);
113
207
  default:
@@ -123,11 +217,11 @@ function isFilePath(configString: string): boolean {
123
217
  return filePathPrefixes.some(prefix => configString.startsWith(prefix));
124
218
  }
125
219
 
126
- async function getStore(storeString: string, storeType: StoreType.DataStore): Promise<DataStore>;
127
- async function getStore(storeString: string, storeType: StoreType.EventLog): Promise<EventLog>;
128
- async function getStore(storeString: string, storeType: StoreType.MessageStore): Promise<MessageStore>;
129
- async function getStore(storeString: string, storeType: StoreType.ResumableTaskStore): Promise<ResumableTaskStore>;
130
- async function getStore(storeConfigString: string, storeType: StoreType): Promise<DwnStore> {
220
+ async function getStore(config: DwnServerConfig, storeString: string, storeType: StoreType.DataStore): Promise<DataStore>;
221
+ async function getStore(config: DwnServerConfig, storeString: string, storeType: StoreType.StateIndex): Promise<StateIndex>;
222
+ async function getStore(config: DwnServerConfig, storeString: string, storeType: StoreType.MessageStore): Promise<MessageStore>;
223
+ async function getStore(config: DwnServerConfig, storeString: string, storeType: StoreType.ResumableTaskStore): Promise<ResumableTaskStore>;
224
+ async function getStore(config: DwnServerConfig, storeConfigString: string, storeType: StoreType): Promise<DwnStore> {
131
225
  if (isFilePath(storeConfigString)) {
132
226
  return await loadStoreFromFilePath(storeConfigString, storeType);
133
227
  }
@@ -142,7 +236,7 @@ async function getStore(storeConfigString: string, storeType: StoreType): Promis
142
236
  case BackendTypes.SQLITE:
143
237
  case BackendTypes.MYSQL:
144
238
  case BackendTypes.POSTGRES:
145
- return getSqlStore(storeURI, storeType);
239
+ return getSqlStore(config, storeURI, storeType);
146
240
 
147
241
  default:
148
242
  throw invalidStorageSchemeMessage(storeURI.protocol);
@@ -159,8 +253,8 @@ async function loadStoreFromFilePath(
159
253
  switch (storeType) {
160
254
  case StoreType.DataStore:
161
255
  return await PluginLoader.loadPlugin<DataStore>(filePath);
162
- case StoreType.EventLog:
163
- return await PluginLoader.loadPlugin<EventLog>(filePath);
256
+ case StoreType.StateIndex:
257
+ return await PluginLoader.loadPlugin<StateIndex>(filePath);
164
258
  case StoreType.MessageStore:
165
259
  return await PluginLoader.loadPlugin<MessageStore>(filePath);
166
260
  case StoreType.ResumableTaskStore:
@@ -207,7 +301,7 @@ function invalidStorageSchemeMessage(protocol: string): string {
207
301
  }
208
302
  return (
209
303
  'Unknown storage protocol ' +
210
- protocol.slice(0, 1) +
304
+ protocol.slice(0, -1) +
211
305
  '! Please use one of: ' +
212
306
  schemes.join(', ') +
213
307
  '. For details, see README'
@@ -103,7 +103,13 @@ export class SqlTtlCache {
103
103
  return undefined;
104
104
  }
105
105
 
106
- return JSON.parse(entry.value);
106
+ try {
107
+ return JSON.parse(entry.value);
108
+ } catch {
109
+ // Corrupt entry — remove it and return undefined.
110
+ this.delete(key); // no need to await
111
+ return undefined;
112
+ }
107
113
  }
108
114
 
109
115
  /**