@dxos/client-services 0.10.0 → 0.11.1
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/dist/lib/chunk-client-services-layer.mjs +11425 -0
- package/dist/lib/chunk-client-services-layer.mjs.map +1 -0
- package/dist/lib/chunk-decorate.mjs +9 -0
- package/dist/lib/index.mjs +731 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/packlets/diagnostics/browser-diagnostics-broadcast.mjs +72 -0
- package/dist/lib/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +1 -0
- package/dist/lib/packlets/diagnostics/diagnostics-broadcast.mjs +14 -0
- package/dist/lib/packlets/diagnostics/diagnostics-broadcast.mjs.map +1 -0
- package/dist/lib/packlets/locks/browser.mjs +105 -0
- package/dist/lib/packlets/locks/browser.mjs.map +1 -0
- package/dist/lib/packlets/locks/node.mjs +53 -0
- package/dist/lib/packlets/locks/node.mjs.map +1 -0
- package/dist/lib/testing.mjs +570 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +21 -4
- package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/agents/edge-agent-service.d.ts +8 -7
- package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
- package/dist/types/src/packlets/devices/devices-service.d.ts +7 -5
- package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/devtools.d.ts +30 -29
- package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/network.d.ts +2 -1
- package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +2 -2
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +1 -8
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts +9 -6
- package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +32 -3
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +23 -4
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-service.d.ts +15 -20
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +2 -2
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts +17 -0
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +25 -4
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-service.d.ts +11 -11
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/logging/logging-service.d.ts +14 -12
- package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
- package/dist/types/src/packlets/metadata/index.d.ts +3 -0
- package/dist/types/src/packlets/metadata/index.d.ts.map +1 -0
- package/dist/types/src/packlets/metadata/metadata-store.d.ts +107 -0
- package/dist/types/src/packlets/metadata/metadata-store.d.ts.map +1 -0
- package/dist/types/src/packlets/metadata/metadata-store.test.d.ts +2 -0
- package/dist/types/src/packlets/metadata/metadata-store.test.d.ts.map +1 -0
- package/dist/types/src/packlets/metadata/sqlite-metadata-store.d.ts +65 -0
- package/dist/types/src/packlets/metadata/sqlite-metadata-store.d.ts.map +1 -0
- package/dist/types/src/packlets/network/network-service.d.ts +15 -14
- package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
- package/dist/types/src/packlets/pipeline/codec.d.ts +11 -0
- package/dist/types/src/packlets/pipeline/codec.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/feeds.d.ts +7 -0
- package/dist/types/src/packlets/pipeline/feeds.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/index.d.ts +5 -0
- package/dist/types/src/packlets/pipeline/index.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/message-selector.d.ts +11 -0
- package/dist/types/src/packlets/pipeline/message-selector.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/pipeline-stress.test.d.ts +2 -0
- package/dist/types/src/packlets/pipeline/pipeline-stress.test.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/pipeline.d.ts +131 -0
- package/dist/types/src/packlets/pipeline/pipeline.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/pipeline.test.d.ts +2 -0
- package/dist/types/src/packlets/pipeline/pipeline.test.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/testing/index.d.ts +2 -0
- package/dist/types/src/packlets/pipeline/testing/index.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/testing/test-feed-builder.d.ts +9 -0
- package/dist/types/src/packlets/pipeline/testing/test-feed-builder.d.ts.map +1 -0
- package/dist/types/src/packlets/pipeline/timeframe-clock.d.ts +31 -0
- package/dist/types/src/packlets/pipeline/timeframe-clock.d.ts.map +1 -0
- package/dist/types/src/packlets/services/client-services-layer.d.ts +21 -0
- package/dist/types/src/packlets/services/client-services-layer.d.ts.map +1 -0
- package/dist/types/src/packlets/services/cross-device-space-synchronizer.d.ts +28 -0
- package/dist/types/src/packlets/services/cross-device-space-synchronizer.d.ts.map +1 -0
- package/dist/types/src/packlets/services/effect-rpc.test.d.ts +2 -0
- package/dist/types/src/packlets/services/effect-rpc.test.d.ts.map +1 -0
- package/dist/types/src/packlets/services/feed-syncer.d.ts +22 -2
- package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -1
- package/dist/types/src/packlets/services/index.d.ts +2 -2
- package/dist/types/src/packlets/services/index.d.ts.map +1 -1
- package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +38 -57
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +82 -31
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/sqlite-storage.d.ts +29 -0
- package/dist/types/src/packlets/services/sqlite-storage.d.ts.map +1 -1
- package/dist/types/src/packlets/services/sqlite-storage.test.d.ts +2 -0
- package/dist/types/src/packlets/services/sqlite-storage.test.d.ts.map +1 -0
- package/dist/types/src/packlets/space/admission-discovery-extension.d.ts +30 -0
- package/dist/types/src/packlets/space/admission-discovery-extension.d.ts.map +1 -0
- package/dist/types/src/packlets/space/auth.d.ts +24 -0
- package/dist/types/src/packlets/space/auth.d.ts.map +1 -0
- package/dist/types/src/packlets/space/control-pipeline.d.ts +46 -0
- package/dist/types/src/packlets/space/control-pipeline.d.ts.map +1 -0
- package/dist/types/src/packlets/space/control-pipeline.test.d.ts +2 -0
- package/dist/types/src/packlets/space/control-pipeline.test.d.ts.map +1 -0
- package/dist/types/src/packlets/space/index.d.ts +6 -0
- package/dist/types/src/packlets/space/index.d.ts.map +1 -0
- package/dist/types/src/packlets/space/replication.browser.test.d.ts +2 -0
- package/dist/types/src/packlets/space/replication.browser.test.d.ts.map +1 -0
- package/dist/types/src/packlets/space/space-manager.browser.test.d.ts +2 -0
- package/dist/types/src/packlets/space/space-manager.browser.test.d.ts.map +1 -0
- package/dist/types/src/packlets/space/space-manager.d.ts +72 -0
- package/dist/types/src/packlets/space/space-manager.d.ts.map +1 -0
- package/dist/types/src/packlets/space/space-protocol.browser.test.d.ts +2 -0
- package/dist/types/src/packlets/space/space-protocol.browser.test.d.ts.map +1 -0
- package/dist/types/src/packlets/space/space-protocol.d.ts +99 -0
- package/dist/types/src/packlets/space/space-protocol.d.ts.map +1 -0
- package/dist/types/src/packlets/space/space-protocol.test.d.ts +2 -0
- package/dist/types/src/packlets/space/space-protocol.test.d.ts.map +1 -0
- package/dist/types/src/packlets/space/space.d.ts +65 -0
- package/dist/types/src/packlets/space/space.d.ts.map +1 -0
- package/dist/types/src/packlets/space/space.test.d.ts +2 -0
- package/dist/types/src/packlets/space/space.test.d.ts.map +1 -0
- package/dist/types/src/packlets/space/testing/change-metadata.d.ts +8 -0
- package/dist/types/src/packlets/space/testing/change-metadata.d.ts.map +1 -0
- package/dist/types/src/packlets/space/testing/index.d.ts +4 -0
- package/dist/types/src/packlets/space/testing/index.d.ts.map +1 -0
- package/dist/types/src/packlets/space/testing/test-agent-builder.d.ts +62 -0
- package/dist/types/src/packlets/space/testing/test-agent-builder.d.ts.map +1 -0
- package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +1 -1
- package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +40 -7
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +3 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/genesis.d.ts +1 -1
- package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +25 -23
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts +18 -18
- package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +7 -6
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts +41 -58
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-session.d.ts +21 -8
- package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +52 -48
- package/src/index.ts +2 -0
- package/src/packlets/agents/edge-agent-manager.ts +56 -15
- package/src/packlets/agents/edge-agent-service.ts +25 -15
- package/src/packlets/devices/devices-service.test.ts +20 -19
- package/src/packlets/devices/devices-service.ts +17 -17
- package/src/packlets/devtools/devtools.ts +133 -75
- package/src/packlets/devtools/feeds.ts +2 -1
- package/src/packlets/devtools/network.ts +42 -12
- package/src/packlets/devtools/spaces.ts +1 -1
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +4 -2
- package/src/packlets/diagnostics/diagnostics-collector.ts +5 -18
- package/src/packlets/diagnostics/diagnostics.ts +0 -11
- package/src/packlets/identity/authenticator.ts +2 -1
- package/src/packlets/identity/contacts-service.ts +46 -37
- package/src/packlets/identity/identity-manager.test.ts +3 -1
- package/src/packlets/identity/identity-manager.ts +73 -5
- package/src/packlets/identity/identity-recovery-manager.ts +56 -7
- package/src/packlets/identity/identity-service.test.ts +27 -18
- package/src/packlets/identity/identity-service.ts +114 -71
- package/src/packlets/identity/identity.test.ts +4 -9
- package/src/packlets/identity/identity.ts +1 -2
- package/src/packlets/invitations/edge-invitation-handler.ts +0 -1
- package/src/packlets/invitations/invitations-handler.ts +39 -3
- package/src/packlets/invitations/invitations-manager.ts +52 -4
- package/src/packlets/invitations/invitations-service.ts +54 -28
- package/src/packlets/logging/logging-service.ts +41 -29
- package/src/packlets/logging/logging.test.ts +28 -15
- package/src/packlets/metadata/index.ts +6 -0
- package/src/packlets/metadata/metadata-store.test.ts +49 -0
- package/src/packlets/metadata/metadata-store.ts +446 -0
- package/src/packlets/metadata/sqlite-metadata-store.ts +409 -0
- package/src/packlets/network/network-service.test.ts +19 -12
- package/src/packlets/network/network-service.ts +96 -29
- package/src/packlets/pipeline/codec.ts +18 -0
- package/src/packlets/pipeline/feeds.ts +22 -0
- package/src/packlets/pipeline/index.ts +8 -0
- package/src/packlets/pipeline/message-selector.ts +35 -0
- package/src/packlets/pipeline/pipeline-stress.test.ts +230 -0
- package/src/packlets/pipeline/pipeline.test.ts +140 -0
- package/src/packlets/pipeline/pipeline.ts +439 -0
- package/src/packlets/pipeline/testing/index.ts +5 -0
- package/src/packlets/pipeline/testing/test-feed-builder.ts +19 -0
- package/src/packlets/pipeline/timeframe-clock.ts +81 -0
- package/src/packlets/services/client-services-layer.ts +186 -0
- package/src/packlets/services/cross-device-space-synchronizer.ts +132 -0
- package/src/packlets/services/effect-rpc.test.ts +314 -0
- package/src/packlets/services/feed-syncer.ts +57 -3
- package/src/packlets/services/index.ts +2 -2
- package/src/packlets/services/platform.ts +0 -6
- package/src/packlets/services/service-context.ts +219 -522
- package/src/packlets/services/service-host.test.ts +34 -11
- package/src/packlets/services/service-host.ts +693 -213
- package/src/packlets/services/spec.md +106 -0
- package/src/packlets/services/sqlite-storage.test.ts +48 -0
- package/src/packlets/services/sqlite-storage.ts +117 -20
- package/src/packlets/space/admission-discovery-extension.ts +91 -0
- package/src/packlets/space/auth.ts +92 -0
- package/src/packlets/space/control-pipeline.test.ts +165 -0
- package/src/packlets/space/control-pipeline.ts +229 -0
- package/src/packlets/space/index.ts +9 -0
- package/src/packlets/space/replication.browser.test.ts +58 -0
- package/src/packlets/space/space-manager.browser.test.ts +40 -0
- package/src/packlets/space/space-manager.ts +224 -0
- package/src/packlets/space/space-protocol.browser.test.ts +129 -0
- package/src/packlets/space/space-protocol.test.ts +158 -0
- package/src/packlets/space/space-protocol.ts +308 -0
- package/src/packlets/space/space.test.ts +181 -0
- package/src/packlets/space/space.ts +196 -0
- package/src/packlets/space/testing/change-metadata.ts +27 -0
- package/src/packlets/space/testing/index.ts +7 -0
- package/src/packlets/space/testing/test-agent-builder.ts +252 -0
- package/src/packlets/space-export/serialized-space-reader.ts +3 -1
- package/src/packlets/space-export/serialized-space-writer.ts +53 -30
- package/src/packlets/space-export/space-archive.test.ts +30 -0
- package/src/packlets/spaces/data-space-manager.test.ts +1 -1
- package/src/packlets/spaces/data-space-manager.ts +148 -33
- package/src/packlets/spaces/data-space.ts +29 -18
- package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
- package/src/packlets/spaces/genesis.ts +1 -1
- package/src/packlets/spaces/spaces-service.test.ts +35 -22
- package/src/packlets/spaces/spaces-service.ts +259 -180
- package/src/packlets/storage/level.ts +3 -1
- package/src/packlets/system/system-service.test.ts +31 -13
- package/src/packlets/system/system-service.ts +65 -37
- package/src/packlets/testing/invitation-utils.ts +3 -3
- package/src/packlets/testing/test-builder.ts +35 -20
- package/src/packlets/worker/worker-runtime.ts +222 -260
- package/src/packlets/worker/worker-session.ts +108 -91
- package/src/testing/index.ts +1 -0
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-QCWEHHJW.mjs +0 -24
- package/dist/lib/browser/chunk-QCWEHHJW.mjs.map +0 -7
- package/dist/lib/browser/chunk-XJRPB3GA.mjs +0 -22
- package/dist/lib/browser/chunk-XJRPB3GA.mjs.map +0 -7
- package/dist/lib/browser/chunk-XU5R2BMF.mjs +0 -8837
- package/dist/lib/browser/chunk-XU5R2BMF.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -876
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +0 -88
- package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +0 -7
- package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +0 -11
- package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs.map +0 -7
- package/dist/lib/browser/packlets/locks/browser.mjs +0 -86
- package/dist/lib/browser/packlets/locks/browser.mjs.map +0 -7
- package/dist/lib/browser/packlets/locks/node.mjs +0 -48
- package/dist/lib/browser/packlets/locks/node.mjs.map +0 -7
- package/dist/lib/browser/testing/index.mjs +0 -436
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-2DT3MZRL.mjs +0 -22
- package/dist/lib/node-esm/chunk-2DT3MZRL.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +0 -24
- package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-H5YTUD35.mjs +0 -8837
- package/dist/lib/node-esm/chunk-H5YTUD35.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -876
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +0 -88
- package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +0 -7
- package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +0 -11
- package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs.map +0 -7
- package/dist/lib/node-esm/packlets/locks/browser.mjs +0 -86
- package/dist/lib/node-esm/packlets/locks/browser.mjs.map +0 -7
- package/dist/lib/node-esm/packlets/locks/node.mjs +0 -48
- package/dist/lib/node-esm/packlets/locks/node.mjs.map +0 -7
- package/dist/lib/node-esm/testing/index.mjs +0 -436
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/packlets/services/client-rpc-server.d.ts +0 -24
- package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +0 -1
- package/dist/types/src/packlets/services/service-registry.d.ts +0 -15
- package/dist/types/src/packlets/services/service-registry.d.ts.map +0 -1
- package/dist/types/src/packlets/services/service-registry.test.d.ts +0 -2
- package/dist/types/src/packlets/services/service-registry.test.d.ts.map +0 -1
- package/dist/types/src/testing/setup.d.ts +0 -3
- package/dist/types/src/testing/setup.d.ts.map +0 -1
- package/src/packlets/services/client-rpc-server.ts +0 -111
- package/src/packlets/services/service-registry.test.ts +0 -78
- package/src/packlets/services/service-registry.ts +0 -36
- package/src/testing/setup.ts +0 -11
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# ServiceContext → Effect Layers
|
|
2
|
+
|
|
3
|
+
Replace `ServiceContext` (monolithic `Resource`) with a composed stack of Effect layers.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
| Item | Rule |
|
|
8
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| **Tag name** | Prefer interface name + `Service` (`IMetadataStore` → `IMetadataStoreService`). No interface → impl name + `Service` (`SpaceManager` → `SpaceManagerService`). |
|
|
10
|
+
| **Tag placement** | Top of defining file: after interface/type, before impl class. |
|
|
11
|
+
| **Layer placement** | Bottom of defining file. |
|
|
12
|
+
| **Layer options** | Value-based config (flags, callbacks, runtime props) go in the layer constructor `options` object. |
|
|
13
|
+
| **Layer requirements** | Injected components go in the layer's `Requirements` type (resolved via `yield*` on sibling tags). |
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// Tag (after interface)
|
|
17
|
+
export class IMetadataStoreService extends EffectContext.Tag('...')<
|
|
18
|
+
IMetadataStoreService,
|
|
19
|
+
IMetadataStore
|
|
20
|
+
>() {}
|
|
21
|
+
|
|
22
|
+
// Layer (bottom of file)
|
|
23
|
+
export const SqliteMetadataStoreLayer = (): Layer.Layer<
|
|
24
|
+
IMetadataStoreService,
|
|
25
|
+
never,
|
|
26
|
+
SqlClient.SqlClient | SqlTransactionTag
|
|
27
|
+
> => Layer.effect(IMetadataStoreService, Effect.gen(function* () { ... }));
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Component map
|
|
31
|
+
|
|
32
|
+
Status: `done` | `todo`
|
|
33
|
+
|
|
34
|
+
### Storage / persistence
|
|
35
|
+
|
|
36
|
+
| Component | Tag | Layer | Options | Requirements | Status |
|
|
37
|
+
| ---------------------------------------- | ----------------------- | ------------------------------- | ------- | ----------------------------- | ------ |
|
|
38
|
+
| `IMetadataStore` / `SqliteMetadataStore` | `IMetadataStoreService` | `SqliteMetadataStoreLayer()` | — | `SqlClient \| SqlTransaction` | done |
|
|
39
|
+
| `BlobStoreApi` / `SqliteBlobStore` | `BlobStoreApiService` | `SqliteBlobStoreLayer()` | — | `SqlClient \| SqlTransaction` | done |
|
|
40
|
+
| `KeyringApi` / `SqliteKeyring` | `KeyringApiService` | `SqliteKeyringLayer()` | — | `SqlClient \| SqlTransaction` | done |
|
|
41
|
+
| `SqliteStorage` | `SqliteStorageService` | `SqliteStorageLayer({ path? })` | `path?` | `SqlClient \| SqlTransaction` | done |
|
|
42
|
+
|
|
43
|
+
### Feeds
|
|
44
|
+
|
|
45
|
+
| Component | Tag | Layer | Options | Requirements | Status |
|
|
46
|
+
| ------------------------------ | ----------------------------- | ------------------------------------- | ------------ | -------------------------------------------------- | ------ |
|
|
47
|
+
| `FeedFactory` | `FeedFactoryService` | `FeedFactoryLayer({ hypercore? })` | `hypercore?` | `KeyringApiService`, `FeedStorageDirectoryService` | done |
|
|
48
|
+
| `FeedStore` (@dxos/feed-store) | `FeedStoreService` | `FeedStoreLayer()` | — | `FeedFactoryService` | done |
|
|
49
|
+
| `FeedStorageDirectory` | `FeedStorageDirectoryService` | `FeedStorageDirectoryLayer({ sub? })` | `sub?` | `SqliteStorageService` | done |
|
|
50
|
+
|
|
51
|
+
### Spaces / echo
|
|
52
|
+
|
|
53
|
+
| Component | Tag | Layer | Options | Requirements | Status |
|
|
54
|
+
| ------------------------------ | -------------------------------- | ------------------------------------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------------ | ------ |
|
|
55
|
+
| `SpaceManager` | `SpaceManagerService` | `SpaceManagerLayer({ disableP2pReplication? })` | `disableP2pReplication?` | `FeedStoreService`, `SwarmNetworkManagerService`, `IMetadataStoreService`, `BlobStoreApiService` | done |
|
|
56
|
+
| `EchoHost` | `EchoHostService` | `EchoHostLayer({ ...callbacks, useSubduction?, assignQueuePositions? })` | callbacks, flags | `SqlClient \| SqlTransaction` | done |
|
|
57
|
+
| `MeshEchoReplicator` | `AutomergeReplicatorService` | `MeshEchoReplicatorLayer()` | — | — | done |
|
|
58
|
+
| `EchoEdgeReplicator` | `EdgeAutomergeReplicatorService` | `EchoEdgeReplicatorLayer({ disableSharePolicy? })` | `disableSharePolicy?` | `EdgeConnectionService`, `EdgeHttpClientService` | done |
|
|
59
|
+
| `EchoEdgeSubductionReplicator` | `EdgeAutomergeReplicatorService` | `EchoEdgeSubductionReplicatorLayer({ disableSharePolicy? })` | `disableSharePolicy?` | `EdgeConnectionService`, `EdgeHttpClientService` | done |
|
|
60
|
+
|
|
61
|
+
### Identity / invitations / spaces (client-services)
|
|
62
|
+
|
|
63
|
+
| Component | Tag | Layer | Options | Requirements | Status |
|
|
64
|
+
| ------------------------------ | ------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
|
|
65
|
+
| `IdentityManager` | `IdentityManagerService` | `IdentityManagerLayer({ devicePresence*, edgeFeatures? })` | presence intervals, edge features | `IMetadataStoreService`, `KeyringApiService`, `FeedStoreService`, `SpaceManagerService`, `EdgeConnectionService?` | done |
|
|
66
|
+
| `EdgeIdentityRecoveryManager` | `EdgeIdentityRecoveryManagerService` | `EdgeIdentityRecoveryManagerLayer()` | — (wire `setAcceptRecoveredIdentity` after stack compose) | `KeyringApiService`, `EdgeHttpClientService?`, `IdentityManagerService` | done |
|
|
67
|
+
| `InvitationsHandler` | `InvitationsHandlerService` | `InvitationsHandlerLayer({ connectionProps? })` | `connectionProps?` | `SwarmNetworkManagerService`, `EdgeHttpClientService?` | done |
|
|
68
|
+
| `InvitationsManager` | `InvitationsManagerService` | `InvitationsManagerLayer({ getHandler })` | `getHandler` callback | `InvitationsHandlerService`, `IMetadataStoreService` | done |
|
|
69
|
+
| `DataSpaceManager` | `DataSpaceManagerService` | `DataSpaceManagerLayer({ runtimeProps?, edgeFeatures? })` | runtime props, edge features | `SpaceManagerService`, `IMetadataStoreService`, `KeyringApiService`, `SigningContextProviderService`, `FeedStoreService`, `EchoHostService`, `InvitationsManagerService`, edge/replicator deps (optional) | done |
|
|
70
|
+
| `SigningContextProvider` | `SigningContextProviderService` | `SigningContextProviderLayer` | — | `IdentityProviderService` | done |
|
|
71
|
+
| `EdgeAgentManager` | `EdgeAgentManagerService` | `EdgeAgentManagerLayer({ edgeFeatures? })` | edge features | `DataSpaceManagerService`, `IdentityProviderService`, `EdgeHttpClientService?` | done |
|
|
72
|
+
| `CrossDeviceSpaceSynchronizer` | `CrossDeviceSpaceSynchronizerService` | `CrossDeviceSpaceSynchronizerLayer` | — | `DataSpaceManagerService` | done |
|
|
73
|
+
| `IdentityProvider` | `IdentityProviderService` | supplied at runtime (e.g. `identityProviderFromManager`) | — | — | done |
|
|
74
|
+
| `FeedSyncer` | `FeedSyncerService` | `FeedSyncerLayer({ peerId, syncNamespaces, getSpaceIds, ...tuning })` | peer/sync config | `SqlClient \| SqlTransaction`, `FeedStoreService` (@dxos/feed), `EdgeConnectionService` | done |
|
|
75
|
+
|
|
76
|
+
### External inputs (provided by host, not constructed inside ServiceContext)
|
|
77
|
+
|
|
78
|
+
| Component | Tag | Notes |
|
|
79
|
+
| --------------------- | ------------------------------------------------- | -------------------------- |
|
|
80
|
+
| `SwarmNetworkManager` | `SwarmNetworkManagerService` | host-provided |
|
|
81
|
+
| `SignalManager` | `SignalManagerService` | todo |
|
|
82
|
+
| `EdgeConnection` | `EdgeConnectionService` | host-provided |
|
|
83
|
+
| `EdgeHttpClient` | `EdgeHttpClientService` | host-provided |
|
|
84
|
+
| `RuntimeProvider` | ambient via `SqlClient` + `SqlTransaction` layers | not a ServiceContext field |
|
|
85
|
+
|
|
86
|
+
## Target layer stack (sketch)
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const ServiceContextLive = Layer.mergeAll(
|
|
90
|
+
SqliteMetadataStoreLayer(),
|
|
91
|
+
SqliteBlobStoreLayer(),
|
|
92
|
+
SqliteKeyringLayer(),
|
|
93
|
+
SqliteStorageLayer(),
|
|
94
|
+
FeedFactoryLayer({ hypercore: { valueEncoding, stats: true } }),
|
|
95
|
+
FeedStoreLayer(),
|
|
96
|
+
SpaceManagerLayer({ disableP2pReplication }),
|
|
97
|
+
EchoHostLayer({ peerIdProvider, getSpaceKeyByRootDocumentId, syncFeed, getSyncState, useSubduction }),
|
|
98
|
+
// ...identity, invitations, data-space layers
|
|
99
|
+
).pipe(Layer.provideMerge(SqlTransaction.layer), Layer.provideMerge(sqliteLayer));
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Open questions
|
|
103
|
+
|
|
104
|
+
1. **Lifecycle** — identity-bound services (`DataSpaceManager`, `EdgeAgentManager`, `CrossDeviceSpaceSynchronizer`) are constructed dormant by their layers; `open`/`close` is called explicitly after identity is ready (e.g. in `_initialize`).
|
|
105
|
+
2. **Provider wiring** — `SigningContextProviderLayer` and `IdentityProviderService` must be provided before `DataSpaceManagerLayer` / `EdgeAgentManagerLayer` can run.
|
|
106
|
+
3. **Callbacks** — `EchoHost` and `InvitationsManager` need callbacks that close over other services; pass as layer options or resolve via `Effect.gen` at a higher layer.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { isClosedConnectionError } from './sqlite-storage';
|
|
8
|
+
|
|
9
|
+
// `SqliteRandomAccessFile._loadFromDb` swallows a read that races client teardown only when the
|
|
10
|
+
// error signals a closed connection. Getting this predicate wrong reintroduces the flake where a
|
|
11
|
+
// background load rejects unhandled after the SQL connection closed, crashing the whole test
|
|
12
|
+
// worker (exit 1 with no failing test).
|
|
13
|
+
describe('isClosedConnectionError', () => {
|
|
14
|
+
test('matches the raw sqlite closed-connection error', () => {
|
|
15
|
+
expect(isClosedConnectionError(new TypeError('The database connection is not open'))).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('matches when wrapped in an effect SqlError cause chain', () => {
|
|
19
|
+
const wrapped = Object.assign(new Error('Failed to execute statement'), {
|
|
20
|
+
_tag: 'SqlError',
|
|
21
|
+
cause: new TypeError('The database connection is not open'),
|
|
22
|
+
});
|
|
23
|
+
expect(isClosedConnectionError(wrapped)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('matches a plain string message', () => {
|
|
27
|
+
expect(isClosedConnectionError('SqliteError: the database connection is not open')).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('does not match a genuine query error against a live connection', () => {
|
|
31
|
+
const real = Object.assign(new Error('Failed to execute statement'), {
|
|
32
|
+
cause: new Error('no such table: hypercore_files'),
|
|
33
|
+
});
|
|
34
|
+
expect(isClosedConnectionError(real)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('is safe on null / undefined / non-error values', () => {
|
|
38
|
+
expect(isClosedConnectionError(null)).toBe(false);
|
|
39
|
+
expect(isClosedConnectionError(undefined)).toBe(false);
|
|
40
|
+
expect(isClosedConnectionError(42)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('terminates on a self-referential cause chain', () => {
|
|
44
|
+
const cyclic: any = new Error('boom');
|
|
45
|
+
cyclic.cause = cyclic;
|
|
46
|
+
expect(isClosedConnectionError(cyclic)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
import * as SqlClient from '@effect/sql/SqlClient';
|
|
6
6
|
import type * as SqlError from '@effect/sql/SqlError';
|
|
7
|
+
import * as EffectContext from 'effect/Context';
|
|
7
8
|
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Layer from 'effect/Layer';
|
|
8
10
|
import type { Callback, FileStat, RandomAccessStorage } from 'random-access-storage';
|
|
9
11
|
|
|
10
12
|
import { RuntimeProvider } from '@dxos/effect';
|
|
13
|
+
import { FeedStorageDirectoryService } from '@dxos/feed-store';
|
|
11
14
|
import { log } from '@dxos/log';
|
|
12
15
|
import { Directory, type File, type Storage, StorageType, wrapFile } from '@dxos/random-access-storage';
|
|
13
16
|
import { SqlTransaction } from '@dxos/sql-sqlite';
|
|
@@ -15,10 +18,33 @@ import { SqlTransaction } from '@dxos/sql-sqlite';
|
|
|
15
18
|
// SqlTransaction.SqlTransaction is the Tag class exported from the SqlTransaction namespace.
|
|
16
19
|
type SqlTransactionTag = SqlTransaction.SqlTransaction;
|
|
17
20
|
|
|
21
|
+
/**
|
|
22
|
+
* True when a rejected SQL op failed because its connection was already closed — the message
|
|
23
|
+
* `The database connection is not open` (raised by `@effect/sql-sqlite-node`), which arrives
|
|
24
|
+
* wrapped as an effect `SqlError`, so walk the `cause` chain. Signals teardown, not a real fault.
|
|
25
|
+
*/
|
|
26
|
+
export const isClosedConnectionError = (err: unknown): boolean => {
|
|
27
|
+
for (let cur: any = err, depth = 0; cur != null && depth < 5; cur = cur.cause, depth++) {
|
|
28
|
+
const message = typeof cur === 'string' ? cur : cur.message;
|
|
29
|
+
if (typeof message === 'string' && /connection is not open/i.test(message)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
};
|
|
35
|
+
|
|
18
36
|
export type SqliteStorageOptions = {
|
|
19
37
|
runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>;
|
|
20
38
|
};
|
|
21
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Effect service tag for {@link SqliteStorage}.
|
|
42
|
+
*/
|
|
43
|
+
export class SqliteStorageService extends EffectContext.Tag('@dxos/client-services/SqliteStorage')<
|
|
44
|
+
SqliteStorageService,
|
|
45
|
+
SqliteStorage
|
|
46
|
+
>() {}
|
|
47
|
+
|
|
22
48
|
/** Minimal cross-platform EventEmitter needed by the RandomAccessStorage contract. */
|
|
23
49
|
class BaseEventEmitter {
|
|
24
50
|
readonly #events: Record<string, ((...args: unknown[]) => void)[]> = Object.create(null);
|
|
@@ -135,7 +161,11 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
|
|
|
135
161
|
|
|
136
162
|
#buffer: Buffer = Buffer.alloc(0);
|
|
137
163
|
#loaded = false;
|
|
164
|
+
// The one-shot initial DB read (populates `#buffer`); set once, on first access.
|
|
138
165
|
#loading: Promise<void> | null = null;
|
|
166
|
+
// The latest in-flight DB write (from `write`/`del`); replaced on each save, so it always
|
|
167
|
+
// tracks the most recent one. `close()` drains both before reporting closed.
|
|
168
|
+
#saving: Promise<void> | null = null;
|
|
139
169
|
|
|
140
170
|
constructor(
|
|
141
171
|
private readonly filePath: string,
|
|
@@ -161,6 +191,11 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
|
|
|
161
191
|
}
|
|
162
192
|
if (!this.#loading) {
|
|
163
193
|
this.#loading = this._loadFromDb();
|
|
194
|
+
// Mark the cached load promise as handled so that if it rejects after every awaiter is gone
|
|
195
|
+
// (e.g. the SQL runtime is disposed at teardown without close() being called on this file),
|
|
196
|
+
// the rejection is never surfaced as an unhandled rejection. Legitimate awaiters still observe
|
|
197
|
+
// the error through their own `.then`/`.catch` on the same promise returned below.
|
|
198
|
+
this.#loading.catch(() => {});
|
|
164
199
|
}
|
|
165
200
|
return this.#loading;
|
|
166
201
|
}
|
|
@@ -176,11 +211,14 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
|
|
|
176
211
|
);
|
|
177
212
|
this.#buffer = rows.length > 0 ? Buffer.from(rows[0].data) : Buffer.alloc(0);
|
|
178
213
|
} catch (err) {
|
|
179
|
-
//
|
|
180
|
-
// only catches reads
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
|
|
214
|
+
// A read racing teardown rejects with "database connection is not open". The `#closed` guard
|
|
215
|
+
// in `_ensureLoaded` only catches reads not yet started, and `#closed` covers only this file
|
|
216
|
+
// being closed — during client disposal the shared SQL connection can be torn down while this
|
|
217
|
+
// file's `#closed` is still false (the container closed the connection without closing every
|
|
218
|
+
// file first). In either case a closed connection means there is nothing to load, so fall back
|
|
219
|
+
// to the empty `#buffer`; rethrow only a genuine error against a live connection. Swallowing an
|
|
220
|
+
// unhandled rejection here otherwise crashes the whole test worker (exit 1, no test failure).
|
|
221
|
+
if (!this.#closed && !isClosedConnectionError(err)) {
|
|
184
222
|
throw err;
|
|
185
223
|
}
|
|
186
224
|
}
|
|
@@ -190,16 +228,27 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
|
|
|
190
228
|
private async _saveToDb(): Promise<void> {
|
|
191
229
|
const filePath = this.filePath;
|
|
192
230
|
const data = this.#buffer;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
231
|
+
try {
|
|
232
|
+
await RuntimeProvider.runPromise(this.runtime)(
|
|
233
|
+
Effect.gen(function* () {
|
|
234
|
+
const sql = yield* SqlClient.SqlClient;
|
|
235
|
+
yield* sql`INSERT OR REPLACE INTO hypercore_files (path, data) VALUES (${filePath}, ${data})`;
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
// Symmetric with `_loadFromDb`: a write racing teardown rejects (as `SqlError: Failed to
|
|
240
|
+
// execute statement` wrapping "connection is not open") when the shared SQL connection is
|
|
241
|
+
// torn down before this file's `#closed` flips. Persisting to a dead connection is moot
|
|
242
|
+
// during disposal, so swallow it; rethrow only a genuine failure against a live connection.
|
|
243
|
+
// An unswallowed rejection here surfaces as an unhandled rejection that fails the test worker.
|
|
244
|
+
if (!this.#closed && !isClosedConnectionError(err)) {
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
199
248
|
}
|
|
200
249
|
|
|
201
250
|
write(offset: number, data: Buffer, cb: Callback<any>): void {
|
|
202
|
-
this._ensureLoaded()
|
|
251
|
+
const saving = this._ensureLoaded()
|
|
203
252
|
.then(() => {
|
|
204
253
|
const end = offset + data.length;
|
|
205
254
|
if (end > this.#buffer.length) {
|
|
@@ -210,8 +259,14 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
|
|
|
210
259
|
data.copy(this.#buffer, offset);
|
|
211
260
|
return this._saveToDb();
|
|
212
261
|
})
|
|
213
|
-
.then(
|
|
214
|
-
|
|
262
|
+
.then(
|
|
263
|
+
() => cb(null),
|
|
264
|
+
(err) => cb(err),
|
|
265
|
+
);
|
|
266
|
+
// Track the in-flight save so `close()` can drain it before reporting closed — otherwise a
|
|
267
|
+
// write racing `close()` (e.g. a background sync write racing `client.reset()`'s teardown)
|
|
268
|
+
// keeps running against a connection the container may tear down immediately after.
|
|
269
|
+
this.#saving = saving.catch(() => {});
|
|
215
270
|
}
|
|
216
271
|
|
|
217
272
|
read(offset: number, size: number, cb: Callback<Buffer>): void {
|
|
@@ -231,7 +286,7 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
|
|
|
231
286
|
}
|
|
232
287
|
|
|
233
288
|
del(offset: number, size: number, cb: Callback<any>): void {
|
|
234
|
-
this._ensureLoaded()
|
|
289
|
+
const saving = this._ensureLoaded()
|
|
235
290
|
.then(() => {
|
|
236
291
|
const end = Math.min(offset + size, this.#buffer.length);
|
|
237
292
|
if (offset < end) {
|
|
@@ -239,8 +294,12 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
|
|
|
239
294
|
}
|
|
240
295
|
return this._saveToDb();
|
|
241
296
|
})
|
|
242
|
-
.then(
|
|
243
|
-
|
|
297
|
+
.then(
|
|
298
|
+
() => cb(null),
|
|
299
|
+
(err) => cb(err),
|
|
300
|
+
);
|
|
301
|
+
// See `write()`: track the in-flight save so `close()` can drain it before reporting closed.
|
|
302
|
+
this.#saving = saving.catch(() => {});
|
|
244
303
|
}
|
|
245
304
|
|
|
246
305
|
stat(cb: Callback<FileStat>): void {
|
|
@@ -253,9 +312,11 @@ class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessSto
|
|
|
253
312
|
|
|
254
313
|
close(cb: Callback<Error>): void {
|
|
255
314
|
this.#closed = true;
|
|
256
|
-
// Drain any in-flight initial load before reporting closed, so
|
|
257
|
-
// torn-down SQL connection (avoids "database connection is not open"
|
|
258
|
-
|
|
315
|
+
// Drain any in-flight initial load and any in-flight write/del before reporting closed, so
|
|
316
|
+
// neither races a torn-down SQL connection (avoids "database connection is not open" errors,
|
|
317
|
+
// and the resulting stall in callers — e.g. `client.reset()`'s feed teardown — that wait on
|
|
318
|
+
// this file's `close()` to settle before tearing down the connection itself).
|
|
319
|
+
Promise.all([this.#loading ?? Promise.resolve(), this.#saving ?? Promise.resolve()]).then(
|
|
259
320
|
() => cb(null),
|
|
260
321
|
() => cb(null),
|
|
261
322
|
);
|
|
@@ -398,3 +459,39 @@ export class SqliteStorage implements Storage {
|
|
|
398
459
|
this.#nativeFiles.clear();
|
|
399
460
|
}
|
|
400
461
|
}
|
|
462
|
+
|
|
463
|
+
export type SqliteStorageLayerOptions = {
|
|
464
|
+
path?: string;
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Effect Layer constructing a {@link SqliteStorage} from the ambient SQL runtime.
|
|
469
|
+
*/
|
|
470
|
+
export const SqliteStorageLayer = (
|
|
471
|
+
options: SqliteStorageLayerOptions = {},
|
|
472
|
+
): Layer.Layer<SqliteStorageService, never, SqlClient.SqlClient | SqlTransactionTag> =>
|
|
473
|
+
Layer.effect(
|
|
474
|
+
SqliteStorageService,
|
|
475
|
+
Effect.gen(function* () {
|
|
476
|
+
const runtime = yield* RuntimeProvider.currentRuntime<SqlClient.SqlClient | SqlTransactionTag>();
|
|
477
|
+
return new SqliteStorage({ runtime }, options.path);
|
|
478
|
+
}),
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
export type FeedStorageDirectoryLayerOptions = {
|
|
482
|
+
sub?: string;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Effect Layer providing the hypercore feeds root directory from {@link SqliteStorage}.
|
|
487
|
+
*/
|
|
488
|
+
export const FeedStorageDirectoryLayer = (
|
|
489
|
+
options: FeedStorageDirectoryLayerOptions = {},
|
|
490
|
+
): Layer.Layer<FeedStorageDirectoryService, never, SqliteStorageService> =>
|
|
491
|
+
Layer.effect(
|
|
492
|
+
FeedStorageDirectoryService,
|
|
493
|
+
Effect.gen(function* () {
|
|
494
|
+
const storage = yield* SqliteStorageService;
|
|
495
|
+
return storage.createDirectory(options.sub ?? 'feeds');
|
|
496
|
+
}),
|
|
497
|
+
);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Trigger, scheduleTask } from '@dxos/async';
|
|
6
|
+
import { Context } from '@dxos/context';
|
|
7
|
+
import { ProtocolError } from '@dxos/protocols';
|
|
8
|
+
import { schema } from '@dxos/protocols/proto';
|
|
9
|
+
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
10
|
+
import {
|
|
11
|
+
type AdmissionDiscoveryService,
|
|
12
|
+
type GetAdmissionCredentialRequest,
|
|
13
|
+
type GetAdmissionCredentialResponse,
|
|
14
|
+
} from '@dxos/protocols/proto/dxos/mesh/teleport';
|
|
15
|
+
import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
16
|
+
|
|
17
|
+
import { type Space } from './space';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Guest's side for a connection to a concrete peer in p2p network during invitation.
|
|
21
|
+
*/
|
|
22
|
+
export class CredentialRetrieverExtension extends RpcExtension<
|
|
23
|
+
{ AdmissionDiscoveryService: AdmissionDiscoveryService },
|
|
24
|
+
{}
|
|
25
|
+
> {
|
|
26
|
+
private _ctx = new Context();
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly _request: GetAdmissionCredentialRequest,
|
|
30
|
+
private readonly _onResult: Trigger<Credential>,
|
|
31
|
+
) {
|
|
32
|
+
super({
|
|
33
|
+
requested: {
|
|
34
|
+
AdmissionDiscoveryService: schema.getService('dxos.mesh.teleport.AdmissionDiscoveryService'),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected override async getHandlers(): Promise<{}> {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override async onOpen(context: ExtensionContext): Promise<void> {
|
|
44
|
+
await super.onOpen(context);
|
|
45
|
+
scheduleTask(this._ctx, async () => {
|
|
46
|
+
try {
|
|
47
|
+
const result = await this.rpc.AdmissionDiscoveryService.getAdmissionCredential(this._request);
|
|
48
|
+
this._onResult.wake(result.admissionCredential);
|
|
49
|
+
} catch (err: any) {
|
|
50
|
+
context.close(err);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override async onClose(): Promise<void> {
|
|
56
|
+
await this._ctx.dispose();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override async onAbort(): Promise<void> {
|
|
60
|
+
await this._ctx.dispose();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class CredentialServerExtension extends RpcExtension<
|
|
65
|
+
{},
|
|
66
|
+
{ AdmissionDiscoveryService: AdmissionDiscoveryService }
|
|
67
|
+
> {
|
|
68
|
+
constructor(private readonly _space: Space) {
|
|
69
|
+
super({
|
|
70
|
+
exposed: {
|
|
71
|
+
AdmissionDiscoveryService: schema.getService('dxos.mesh.teleport.AdmissionDiscoveryService'),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected override async getHandlers(): Promise<{ AdmissionDiscoveryService: AdmissionDiscoveryService }> {
|
|
77
|
+
return {
|
|
78
|
+
AdmissionDiscoveryService: {
|
|
79
|
+
getAdmissionCredential: async (
|
|
80
|
+
request: GetAdmissionCredentialRequest,
|
|
81
|
+
): Promise<GetAdmissionCredentialResponse> => {
|
|
82
|
+
const memberInfo = this._space.spaceState.members.get(request.memberKey);
|
|
83
|
+
if (!memberInfo?.credential) {
|
|
84
|
+
throw new ProtocolError({ message: 'Space member not found.', context: { ...request } });
|
|
85
|
+
}
|
|
86
|
+
return { admissionCredential: memberInfo.credential };
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2019 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { runInContext, scheduleTask } from '@dxos/async';
|
|
6
|
+
import { Context } from '@dxos/context';
|
|
7
|
+
import { randomBytes } from '@dxos/crypto';
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { log } from '@dxos/log';
|
|
10
|
+
import { schema } from '@dxos/protocols/proto';
|
|
11
|
+
import { type AuthService } from '@dxos/protocols/proto/dxos/mesh/teleport/auth';
|
|
12
|
+
import { type ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
13
|
+
|
|
14
|
+
export type AuthProvider = (nonce: Uint8Array) => Promise<Uint8Array | undefined>;
|
|
15
|
+
|
|
16
|
+
export type AuthVerifier = (nonce: Uint8Array, credential: Uint8Array) => Promise<boolean>;
|
|
17
|
+
|
|
18
|
+
export type AuthExtensionProps = {
|
|
19
|
+
provider: AuthProvider;
|
|
20
|
+
verifier: AuthVerifier;
|
|
21
|
+
onAuthSuccess: () => void;
|
|
22
|
+
onAuthFailure: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class AuthExtension extends RpcExtension<Services, Services> {
|
|
26
|
+
private readonly _ctx = new Context({
|
|
27
|
+
onError: (err) => {
|
|
28
|
+
log.catch(err);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
constructor(private readonly _authProps: AuthExtensionProps) {
|
|
33
|
+
super({
|
|
34
|
+
requested: {
|
|
35
|
+
AuthService: schema.getService('dxos.mesh.teleport.auth.AuthService'),
|
|
36
|
+
},
|
|
37
|
+
exposed: {
|
|
38
|
+
AuthService: schema.getService('dxos.mesh.teleport.auth.AuthService'),
|
|
39
|
+
},
|
|
40
|
+
timeout: 60 * 1000, // Long timeout because auth can wait for sync in certain cases.
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected async getHandlers(): Promise<Services> {
|
|
45
|
+
return {
|
|
46
|
+
AuthService: {
|
|
47
|
+
authenticate: async ({ challenge }) => {
|
|
48
|
+
try {
|
|
49
|
+
const credential = await this._authProps.provider(challenge);
|
|
50
|
+
if (!credential) {
|
|
51
|
+
throw new Error('auth rejected');
|
|
52
|
+
}
|
|
53
|
+
return { credential };
|
|
54
|
+
} catch (err) {
|
|
55
|
+
log.error('failed to generate auth credentials', err);
|
|
56
|
+
throw new Error('auth rejected');
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override async onOpen(context: ExtensionContext): Promise<void> {
|
|
64
|
+
await super.onOpen(context);
|
|
65
|
+
scheduleTask(this._ctx, async () => {
|
|
66
|
+
try {
|
|
67
|
+
const challenge = randomBytes(32);
|
|
68
|
+
const { credential } = await this.rpc.AuthService.authenticate({ challenge });
|
|
69
|
+
invariant(credential?.length > 0, 'invalid credential');
|
|
70
|
+
const success = await this._authProps.verifier(challenge, credential);
|
|
71
|
+
invariant(success, 'credential not verified');
|
|
72
|
+
runInContext(this._ctx, () => this._authProps.onAuthSuccess());
|
|
73
|
+
} catch (err) {
|
|
74
|
+
log('auth failed', err);
|
|
75
|
+
this.close();
|
|
76
|
+
this._authProps.onAuthFailure();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override async onClose(): Promise<void> {
|
|
82
|
+
await this._ctx.dispose();
|
|
83
|
+
await super.onClose();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override async onAbort(): Promise<void> {
|
|
87
|
+
await this._ctx.dispose();
|
|
88
|
+
await super.onAbort();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type Services = { AuthService: AuthService };
|