@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.
- package/LICENSE +3 -2
- package/README.md +112 -212
- package/dist/esm/src/admin/activity-log.d.ts +44 -0
- package/dist/esm/src/admin/activity-log.d.ts.map +1 -0
- package/dist/esm/src/admin/activity-log.js +85 -0
- package/dist/esm/src/admin/activity-log.js.map +1 -0
- package/dist/esm/src/admin/admin-api.d.ts +61 -0
- package/dist/esm/src/admin/admin-api.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-api.js +1047 -0
- package/dist/esm/src/admin/admin-api.js.map +1 -0
- package/dist/esm/src/admin/admin-auth.d.ts +9 -0
- package/dist/esm/src/admin/admin-auth.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-auth.js +45 -0
- package/dist/esm/src/admin/admin-auth.js.map +1 -0
- package/dist/esm/src/admin/admin-store.d.ts +111 -0
- package/dist/esm/src/admin/admin-store.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-store.js +376 -0
- package/dist/esm/src/admin/admin-store.js.map +1 -0
- package/dist/esm/src/admin/audit-log.d.ts +94 -0
- package/dist/esm/src/admin/audit-log.d.ts.map +1 -0
- package/dist/esm/src/admin/audit-log.js +220 -0
- package/dist/esm/src/admin/audit-log.js.map +1 -0
- package/dist/esm/src/admin/index.d.ts +10 -0
- package/dist/esm/src/admin/index.d.ts.map +1 -0
- package/dist/esm/src/admin/index.js +7 -0
- package/dist/esm/src/admin/index.js.map +1 -0
- package/dist/esm/src/admin/types.d.ts +306 -0
- package/dist/esm/src/admin/types.d.ts.map +1 -0
- package/dist/esm/src/admin/types.js +2 -0
- package/dist/esm/src/admin/types.js.map +1 -0
- package/dist/esm/src/admin/webhook-manager.d.ts +55 -0
- package/dist/esm/src/admin/webhook-manager.d.ts.map +1 -0
- package/dist/esm/src/admin/webhook-manager.js +184 -0
- package/dist/esm/src/admin/webhook-manager.js.map +1 -0
- package/dist/esm/src/config.d.ts +122 -3
- package/dist/esm/src/config.d.ts.map +1 -1
- package/dist/esm/src/config.js +151 -5
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/connection/connection-manager.d.ts +24 -1
- package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
- package/dist/esm/src/connection/connection-manager.js +33 -2
- package/dist/esm/src/connection/connection-manager.js.map +1 -1
- package/dist/esm/src/connection/flow-controller.d.ts +53 -0
- package/dist/esm/src/connection/flow-controller.d.ts.map +1 -0
- package/dist/esm/src/connection/flow-controller.js +101 -0
- package/dist/esm/src/connection/flow-controller.js.map +1 -0
- package/dist/esm/src/connection/socket-connection.d.ts +39 -4
- package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
- package/dist/esm/src/connection/socket-connection.js +80 -9
- package/dist/esm/src/connection/socket-connection.js.map +1 -1
- package/dist/esm/src/delivery-service.d.ts +43 -0
- package/dist/esm/src/delivery-service.d.ts.map +1 -0
- package/dist/esm/src/delivery-service.js +574 -0
- package/dist/esm/src/delivery-service.js.map +1 -0
- package/dist/esm/src/dwn-error.d.ts +10 -1
- package/dist/esm/src/dwn-error.d.ts.map +1 -1
- package/dist/esm/src/dwn-error.js +9 -0
- package/dist/esm/src/dwn-error.js.map +1 -1
- package/dist/esm/src/dwn-server.d.ts +8 -0
- package/dist/esm/src/dwn-server.d.ts.map +1 -1
- package/dist/esm/src/dwn-server.js +198 -12
- package/dist/esm/src/dwn-server.js.map +1 -1
- package/dist/esm/src/http-api.d.ts +19 -2
- package/dist/esm/src/http-api.d.ts.map +1 -1
- package/dist/esm/src/http-api.js +219 -19
- package/dist/esm/src/http-api.js.map +1 -1
- package/dist/esm/src/index.d.ts +6 -2
- package/dist/esm/src/index.d.ts.map +1 -1
- package/dist/esm/src/index.js +4 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/json-rpc-api.js +2 -1
- package/dist/esm/src/json-rpc-api.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +106 -4
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts +20 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.js +41 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/close.js +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/index.js +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -1
- package/dist/esm/src/lib/json-rpc-router.d.ts +22 -4
- package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -1
- package/dist/esm/src/lib/json-rpc-router.js.map +1 -1
- package/dist/esm/src/lib/sql-utils.d.ts +6 -0
- package/dist/esm/src/lib/sql-utils.d.ts.map +1 -0
- package/dist/esm/src/lib/sql-utils.js +8 -0
- package/dist/esm/src/lib/sql-utils.js.map +1 -0
- package/dist/esm/src/main.js +0 -6
- package/dist/esm/src/main.js.map +1 -1
- package/dist/esm/src/message-processed-hook.d.ts +35 -0
- package/dist/esm/src/message-processed-hook.d.ts.map +1 -0
- package/dist/esm/src/message-processed-hook.js +2 -0
- package/dist/esm/src/message-processed-hook.js.map +1 -0
- package/dist/esm/src/metrics.d.ts +13 -1
- package/dist/esm/src/metrics.d.ts.map +1 -1
- package/dist/esm/src/metrics.js +41 -1
- package/dist/esm/src/metrics.js.map +1 -1
- package/dist/esm/src/plugins/event-log-nats.d.ts +25 -0
- package/dist/esm/src/plugins/event-log-nats.d.ts.map +1 -0
- package/dist/esm/src/plugins/event-log-nats.js +379 -0
- package/dist/esm/src/plugins/event-log-nats.js.map +1 -0
- package/dist/esm/src/rate-limiter.d.ts +60 -0
- package/dist/esm/src/rate-limiter.d.ts.map +1 -0
- package/dist/esm/src/rate-limiter.js +116 -0
- package/dist/esm/src/rate-limiter.js.map +1 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts +53 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts.map +1 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.js +90 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -0
- package/dist/esm/src/registration/open-auth-handler.d.ts +37 -0
- package/dist/esm/src/registration/open-auth-handler.d.ts.map +1 -0
- package/dist/esm/src/registration/open-auth-handler.js +214 -0
- package/dist/esm/src/registration/open-auth-handler.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work-manager.d.ts +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/provider-auth-plugin.d.ts +46 -0
- package/dist/esm/src/registration/provider-auth-plugin.d.ts.map +1 -0
- package/dist/esm/src/registration/provider-auth-plugin.js +29 -0
- package/dist/esm/src/registration/provider-auth-plugin.js.map +1 -0
- package/dist/esm/src/registration/registration-manager.d.ts +27 -4
- package/dist/esm/src/registration/registration-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-manager.js +77 -6
- package/dist/esm/src/registration/registration-manager.js.map +1 -1
- package/dist/esm/src/registration/registration-store.d.ts +83 -3
- package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-store.js +248 -11
- package/dist/esm/src/registration/registration-store.js.map +1 -1
- package/dist/esm/src/storage.d.ts +4 -4
- package/dist/esm/src/storage.d.ts.map +1 -1
- package/dist/esm/src/storage.js +100 -20
- package/dist/esm/src/storage.js.map +1 -1
- package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -1
- package/dist/esm/src/web5-connect/sql-ttl-cache.js +8 -1
- package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -1
- package/dist/esm/src/ws-api.d.ts +17 -1
- package/dist/esm/src/ws-api.d.ts.map +1 -1
- package/dist/esm/src/ws-api.js +9 -2
- package/dist/esm/src/ws-api.js.map +1 -1
- package/package.json +16 -16
- package/src/admin/activity-log.ts +100 -0
- package/src/admin/admin-api.ts +1308 -0
- package/src/admin/admin-auth.ts +56 -0
- package/src/admin/admin-store.ts +515 -0
- package/src/admin/audit-log.ts +327 -0
- package/src/admin/index.ts +34 -0
- package/src/admin/types.ts +352 -0
- package/src/admin/webhook-manager.ts +245 -0
- package/src/config.ts +177 -5
- package/src/connection/connection-manager.ts +50 -6
- package/src/connection/flow-controller.ts +117 -0
- package/src/connection/socket-connection.ts +103 -21
- package/src/delivery-service.ts +740 -0
- package/src/dwn-error.ts +9 -0
- package/src/dwn-server.ts +242 -14
- package/src/http-api.ts +271 -30
- package/src/index.ts +13 -2
- package/src/json-rpc-api.ts +2 -1
- package/src/json-rpc-handlers/dwn/process-message.ts +140 -5
- package/src/json-rpc-handlers/subscription/ack.ts +63 -0
- package/src/json-rpc-handlers/subscription/close.ts +2 -6
- package/src/json-rpc-handlers/subscription/index.ts +1 -0
- package/src/lib/json-rpc-router.ts +22 -6
- package/src/lib/sql-utils.ts +7 -0
- package/src/main.ts +0 -8
- package/src/message-processed-hook.ts +33 -0
- package/src/metrics.ts +50 -1
- package/src/plugins/event-log-nats.ts +466 -0
- package/src/rate-limiter.ts +143 -0
- package/src/registration/jwt-provider-auth-plugin.ts +119 -0
- package/src/registration/open-auth-handler.ts +263 -0
- package/src/registration/proof-of-work-manager.ts +1 -1
- package/src/registration/provider-auth-plugin.ts +84 -0
- package/src/registration/registration-manager.ts +108 -12
- package/src/registration/registration-store.ts +326 -17
- package/src/storage.ts +121 -27
- package/src/web5-connect/sql-ttl-cache.ts +7 -1
- package/src/ws-api.ts +30 -2
- package/dist/esm/src/json-rpc-socket.d.ts +0 -39
- package/dist/esm/src/json-rpc-socket.d.ts.map +0 -1
- package/dist/esm/src/json-rpc-socket.js +0 -125
- package/dist/esm/src/json-rpc-socket.js.map +0 -1
- package/dist/esm/src/lib/json-rpc.d.ts +0 -54
- package/dist/esm/src/lib/json-rpc.d.ts.map +0 -1
- package/dist/esm/src/lib/json-rpc.js +0 -60
- package/dist/esm/src/lib/json-rpc.js.map +0 -1
- package/dist/esm/src/registration/proof-of-work-types.d.ts +0 -8
- package/dist/esm/src/registration/proof-of-work-types.d.ts.map +0 -1
- package/dist/esm/src/registration/proof-of-work-types.js +0 -2
- package/dist/esm/src/registration/proof-of-work-types.js.map +0 -1
- package/dist/esm/src/registration/registration-types.d.ts +0 -18
- package/dist/esm/src/registration/registration-types.d.ts.map +0 -1
- package/dist/esm/src/registration/registration-types.js +0 -2
- package/dist/esm/src/registration/registration-types.js.map +0 -1
- package/dist/esm/tests/common-scenario-validator.d.ts +0 -11
- package/dist/esm/tests/common-scenario-validator.d.ts.map +0 -1
- package/dist/esm/tests/common-scenario-validator.js +0 -113
- package/dist/esm/tests/common-scenario-validator.js.map +0 -1
- package/dist/esm/tests/connection/connection-manager.spec.d.ts +0 -2
- package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +0 -1
- package/dist/esm/tests/connection/connection-manager.spec.js +0 -49
- package/dist/esm/tests/connection/connection-manager.spec.js.map +0 -1
- package/dist/esm/tests/connection/socket-connection.spec.d.ts +0 -2
- package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +0 -1
- package/dist/esm/tests/connection/socket-connection.spec.js +0 -147
- package/dist/esm/tests/connection/socket-connection.spec.js.map +0 -1
- package/dist/esm/tests/cors/http-api.browser.d.ts +0 -2
- package/dist/esm/tests/cors/http-api.browser.d.ts.map +0 -1
- package/dist/esm/tests/cors/http-api.browser.js +0 -60
- package/dist/esm/tests/cors/http-api.browser.js.map +0 -1
- package/dist/esm/tests/cors/ping.browser.d.ts +0 -2
- package/dist/esm/tests/cors/ping.browser.d.ts.map +0 -1
- package/dist/esm/tests/cors/ping.browser.js +0 -7
- package/dist/esm/tests/cors/ping.browser.js.map +0 -1
- package/dist/esm/tests/dwn-process-message.spec.d.ts +0 -2
- package/dist/esm/tests/dwn-process-message.spec.d.ts.map +0 -1
- package/dist/esm/tests/dwn-process-message.spec.js +0 -172
- package/dist/esm/tests/dwn-process-message.spec.js.map +0 -1
- package/dist/esm/tests/dwn-server.spec.d.ts +0 -2
- package/dist/esm/tests/dwn-server.spec.d.ts.map +0 -1
- package/dist/esm/tests/dwn-server.spec.js +0 -48
- package/dist/esm/tests/dwn-server.spec.js.map +0 -1
- package/dist/esm/tests/http-api.spec.d.ts +0 -2
- package/dist/esm/tests/http-api.spec.d.ts.map +0 -1
- package/dist/esm/tests/http-api.spec.js +0 -782
- package/dist/esm/tests/http-api.spec.js.map +0 -1
- package/dist/esm/tests/json-rpc-socket.spec.d.ts +0 -2
- package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +0 -1
- package/dist/esm/tests/json-rpc-socket.spec.js +0 -227
- package/dist/esm/tests/json-rpc-socket.spec.js.map +0 -1
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/data-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/data-store-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/event-log-sqlite.js +0 -23
- package/dist/esm/tests/plugins/event-log-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +0 -17
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +0 -1
- package/dist/esm/tests/plugins/event-stream-in-memory.js +0 -21
- package/dist/esm/tests/plugins/event-stream-in-memory.js.map +0 -1
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/message-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/message-store-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +0 -1
- package/dist/esm/tests/process-handler.spec.d.ts +0 -2
- package/dist/esm/tests/process-handler.spec.d.ts.map +0 -1
- package/dist/esm/tests/process-handler.spec.js +0 -60
- package/dist/esm/tests/process-handler.spec.js.map +0 -1
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +0 -2
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +0 -1
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js +0 -156
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +0 -1
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +0 -2
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +0 -1
- package/dist/esm/tests/rpc-subscribe-close.spec.js +0 -81
- package/dist/esm/tests/rpc-subscribe-close.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +0 -74
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/registration.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/registration.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/registration.spec.js +0 -511
- package/dist/esm/tests/scenarios/registration.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/web5-connect.spec.js +0 -141
- package/dist/esm/tests/scenarios/web5-connect.spec.js.map +0 -1
- package/dist/esm/tests/test-dwn.d.ts +0 -7
- package/dist/esm/tests/test-dwn.d.ts.map +0 -1
- package/dist/esm/tests/test-dwn.js +0 -28
- package/dist/esm/tests/test-dwn.js.map +0 -1
- package/dist/esm/tests/utils.d.ts +0 -43
- package/dist/esm/tests/utils.d.ts.map +0 -1
- package/dist/esm/tests/utils.js +0 -107
- package/dist/esm/tests/utils.js.map +0 -1
- package/dist/esm/tests/ws-api.spec.d.ts +0 -2
- package/dist/esm/tests/ws-api.spec.d.ts.map +0 -1
- package/dist/esm/tests/ws-api.spec.js +0 -332
- package/dist/esm/tests/ws-api.spec.js.map +0 -1
- package/src/json-rpc-socket.ts +0 -156
- package/src/lib/json-rpc.ts +0 -126
- package/src/registration/proof-of-work-types.ts +0 -7
- 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 {
|
|
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
|
|
25
|
+
const store = new RegistrationStore(sqlDialect);
|
|
23
26
|
|
|
24
|
-
await
|
|
27
|
+
await store.initialize();
|
|
25
28
|
|
|
26
|
-
return
|
|
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:
|
|
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(
|
|
104
|
+
.values(insertValues as any)
|
|
45
105
|
.onConflict((oc) =>
|
|
46
|
-
oc.column('did').doUpdateSet((eb) =>
|
|
47
|
-
|
|
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<
|
|
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
|
-
|
|
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 |
|
|
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
|
-
|
|
104
|
+
eventLog? : EventLog,
|
|
60
105
|
}
|
|
61
106
|
): Promise<DwnConfig> {
|
|
62
|
-
const { tenantGate,
|
|
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
|
-
|
|
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.
|
|
86
|
-
return new
|
|
87
|
-
location: storeURI.host + storeURI.pathname + '/
|
|
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 =
|
|
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.
|
|
110
|
-
return new
|
|
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.
|
|
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.
|
|
163
|
-
return await PluginLoader.loadPlugin<
|
|
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
|
-
|
|
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
|
/**
|