@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,186 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import * as Layer from 'effect/Layer';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
8
|
+
|
|
9
|
+
import { EchoHostService } from '@dxos/echo-host';
|
|
10
|
+
import { EdgeConnectionService } from '@dxos/edge-client';
|
|
11
|
+
import { KeyringApiService } from '@dxos/keyring';
|
|
12
|
+
import { SignalManagerService } from '@dxos/messaging';
|
|
13
|
+
import { SwarmNetworkManagerService } from '@dxos/network-manager';
|
|
14
|
+
import {
|
|
15
|
+
ContactsService,
|
|
16
|
+
DataService,
|
|
17
|
+
DevicesService,
|
|
18
|
+
EdgeAgentService,
|
|
19
|
+
FeedService,
|
|
20
|
+
IdentityService,
|
|
21
|
+
InvitationsService,
|
|
22
|
+
NetworkService,
|
|
23
|
+
QueryService,
|
|
24
|
+
SpacesService,
|
|
25
|
+
} from '@dxos/protocols/rpc';
|
|
26
|
+
|
|
27
|
+
import { EdgeAgentServiceImpl } from '../agents';
|
|
28
|
+
import { DevicesServiceImpl } from '../devices';
|
|
29
|
+
import { IdentityManagerService, IdentityServiceImpl } from '../identity';
|
|
30
|
+
import { ContactsServiceImpl } from '../identity/contacts-service';
|
|
31
|
+
import { EdgeIdentityRecoveryManagerService } from '../identity/identity-recovery-manager';
|
|
32
|
+
import { InvitationsManagerService, InvitationsServiceImpl } from '../invitations';
|
|
33
|
+
import { NetworkServiceImpl } from '../network';
|
|
34
|
+
import { SpaceManagerService } from '../space';
|
|
35
|
+
import { SpacesServiceImpl } from '../spaces';
|
|
36
|
+
import { ClientServicesHostService } from './service-host';
|
|
37
|
+
|
|
38
|
+
//
|
|
39
|
+
// Each client RPC service handler is exposed as an individual Effect service tag. Handlers depend
|
|
40
|
+
// directly on the lower-level component tags they consume (EchoHostService, IdentityManagerService,
|
|
41
|
+
// …); only handlers that need lifecycle orchestration (identity creation, readiness gates)
|
|
42
|
+
// additionally depend on {@link ClientServicesHostService} (the host provides itself into the stack).
|
|
43
|
+
//
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Union of every client RPC service tag resolved from the stack.
|
|
47
|
+
*/
|
|
48
|
+
export type ClientServicesRpcContext =
|
|
49
|
+
| IdentityService.Tag
|
|
50
|
+
| ContactsService.Tag
|
|
51
|
+
| InvitationsService.Tag
|
|
52
|
+
| DevicesService.Tag
|
|
53
|
+
| SpacesService.Tag
|
|
54
|
+
| NetworkService.Tag
|
|
55
|
+
| EdgeAgentService.Tag
|
|
56
|
+
| DataService.Tag
|
|
57
|
+
| QueryService.Tag
|
|
58
|
+
| FeedService.Tag;
|
|
59
|
+
|
|
60
|
+
// Identity creation is a lifecycle sequence and profile broadcast iterates live spaces, so both
|
|
61
|
+
// remain orchestrator responsibilities resolved from {@link ClientServicesHostService}.
|
|
62
|
+
// The impl is a {@link Resource}; its open/close lifecycle is bound to the layer scope.
|
|
63
|
+
const identityServiceLayer = Layer.scoped(
|
|
64
|
+
IdentityService.Tag,
|
|
65
|
+
Effect.gen(function* () {
|
|
66
|
+
const identityManager = yield* IdentityManagerService;
|
|
67
|
+
const recoveryManager = yield* EdgeIdentityRecoveryManagerService;
|
|
68
|
+
const keyring = yield* KeyringApiService;
|
|
69
|
+
const host = yield* ClientServicesHostService;
|
|
70
|
+
const service = new IdentityServiceImpl(
|
|
71
|
+
identityManager,
|
|
72
|
+
recoveryManager,
|
|
73
|
+
keyring,
|
|
74
|
+
async (params, ctx) => {
|
|
75
|
+
const identity = await host.createIdentity(params, ctx);
|
|
76
|
+
await host.initialized.wait();
|
|
77
|
+
return identity;
|
|
78
|
+
},
|
|
79
|
+
(profile) => host.broadcastProfileUpdate(profile),
|
|
80
|
+
);
|
|
81
|
+
yield* Effect.acquireRelease(
|
|
82
|
+
Effect.promise(() => service.open()),
|
|
83
|
+
() => Effect.promise(() => service.close()),
|
|
84
|
+
);
|
|
85
|
+
return service;
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const contactsServiceLayer = Layer.effect(
|
|
90
|
+
ContactsService.Tag,
|
|
91
|
+
Effect.gen(function* () {
|
|
92
|
+
const identityManager = yield* IdentityManagerService;
|
|
93
|
+
const spaceManager = yield* SpaceManagerService;
|
|
94
|
+
const host = yield* ClientServicesHostService;
|
|
95
|
+
return new ContactsServiceImpl(identityManager, spaceManager, () => host.whenDataSpaceManagerReady());
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const invitationsServiceLayer = Layer.effect(
|
|
100
|
+
InvitationsService.Tag,
|
|
101
|
+
Effect.map(InvitationsManagerService, (invitationsManager) => new InvitationsServiceImpl(invitationsManager)),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const devicesServiceLayer = Layer.effect(
|
|
105
|
+
DevicesService.Tag,
|
|
106
|
+
Effect.gen(function* () {
|
|
107
|
+
const identityManager = yield* IdentityManagerService;
|
|
108
|
+
// Edge connection is absent in the non-edge stack, so resolve it optionally.
|
|
109
|
+
const edgeConnection = Option.getOrUndefined(yield* Effect.serviceOption(EdgeConnectionService));
|
|
110
|
+
return new DevicesServiceImpl(identityManager, edgeConnection);
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const spacesServiceLayer = Layer.effect(
|
|
115
|
+
SpacesService.Tag,
|
|
116
|
+
Effect.gen(function* () {
|
|
117
|
+
const identityManager = yield* IdentityManagerService;
|
|
118
|
+
const spaceManager = yield* SpaceManagerService;
|
|
119
|
+
const echoHost = yield* EchoHostService;
|
|
120
|
+
const host = yield* ClientServicesHostService;
|
|
121
|
+
return new SpacesServiceImpl(identityManager, spaceManager, echoHost, () => host.whenDataSpaceManagerReady());
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const networkServiceLayer = Layer.effect(
|
|
126
|
+
NetworkService.Tag,
|
|
127
|
+
Effect.gen(function* () {
|
|
128
|
+
const networkManager = yield* SwarmNetworkManagerService;
|
|
129
|
+
const signalManager = yield* SignalManagerService;
|
|
130
|
+
const edgeConnection = Option.getOrUndefined(yield* Effect.serviceOption(EdgeConnectionService));
|
|
131
|
+
return new NetworkServiceImpl(networkManager, signalManager, edgeConnection);
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const edgeAgentServiceLayer = Layer.effect(
|
|
136
|
+
EdgeAgentService.Tag,
|
|
137
|
+
Effect.gen(function* () {
|
|
138
|
+
const edgeConnection = Option.getOrUndefined(yield* Effect.serviceOption(EdgeConnectionService));
|
|
139
|
+
const host = yield* ClientServicesHostService;
|
|
140
|
+
return new EdgeAgentServiceImpl(() => host.whenEdgeAgentManagerReady(), edgeConnection);
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const dataServiceLayer = Layer.effect(
|
|
145
|
+
DataService.Tag,
|
|
146
|
+
Effect.map(EchoHostService, (echoHost) => echoHost.dataService),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const queryServiceLayer = Layer.effect(
|
|
150
|
+
QueryService.Tag,
|
|
151
|
+
Effect.map(EchoHostService, (echoHost) => echoHost.queryService),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const feedServiceLayer = Layer.effect(
|
|
155
|
+
FeedService.Tag,
|
|
156
|
+
Effect.map(EchoHostService, (echoHost) => echoHost.feedService),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Composes every client RPC service handler on top of the component tags exposed by the stack.
|
|
161
|
+
* Each handler keeps its own tag so callers resolve them individually from the stack runtime.
|
|
162
|
+
*/
|
|
163
|
+
export const ClientServicesRpcLayer: Layer.Layer<
|
|
164
|
+
ClientServicesRpcContext,
|
|
165
|
+
never,
|
|
166
|
+
| EchoHostService
|
|
167
|
+
| IdentityManagerService
|
|
168
|
+
| SpaceManagerService
|
|
169
|
+
| InvitationsManagerService
|
|
170
|
+
| EdgeIdentityRecoveryManagerService
|
|
171
|
+
| KeyringApiService
|
|
172
|
+
| SwarmNetworkManagerService
|
|
173
|
+
| SignalManagerService
|
|
174
|
+
| ClientServicesHostService
|
|
175
|
+
> = Layer.mergeAll(
|
|
176
|
+
identityServiceLayer,
|
|
177
|
+
contactsServiceLayer,
|
|
178
|
+
invitationsServiceLayer,
|
|
179
|
+
devicesServiceLayer,
|
|
180
|
+
spacesServiceLayer,
|
|
181
|
+
networkServiceLayer,
|
|
182
|
+
edgeAgentServiceLayer,
|
|
183
|
+
dataServiceLayer,
|
|
184
|
+
queryServiceLayer,
|
|
185
|
+
feedServiceLayer,
|
|
186
|
+
);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as EffectContext from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
8
|
+
|
|
9
|
+
import { type Context, type Lifecycle, Resource } from '@dxos/context';
|
|
10
|
+
import { type CredentialProcessor, getCredentialAssertion } from '@dxos/credentials';
|
|
11
|
+
import { assertState } from '@dxos/invariant';
|
|
12
|
+
import { log } from '@dxos/log';
|
|
13
|
+
import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
14
|
+
|
|
15
|
+
import { type Identity } from '../identity';
|
|
16
|
+
import { DataSpaceManager, DataSpaceManagerService } from '../spaces';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Replicates cross-device space membership and deletion credentials from the halo space.
|
|
20
|
+
*/
|
|
21
|
+
export interface CrossDeviceSpaceSynchronizer extends CredentialProcessor, Lifecycle {
|
|
22
|
+
setIdentity(identity: Identity): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Effect service tag for {@link CrossDeviceSpaceSynchronizer}.
|
|
27
|
+
*/
|
|
28
|
+
export class CrossDeviceSpaceSynchronizerService extends EffectContext.Tag(
|
|
29
|
+
'@dxos/client-services/CrossDeviceSpaceSynchronizer',
|
|
30
|
+
)<CrossDeviceSpaceSynchronizerService, CrossDeviceSpaceSynchronizer>() {}
|
|
31
|
+
|
|
32
|
+
class CrossDeviceSpaceSynchronizerImpl extends Resource implements CrossDeviceSpaceSynchronizer {
|
|
33
|
+
private _identity?: Identity;
|
|
34
|
+
|
|
35
|
+
constructor(private readonly dataSpaceManager: DataSpaceManager) {
|
|
36
|
+
super();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setIdentity(identity: Identity): void {
|
|
40
|
+
this._identity = identity;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
protected override async _open(ctx: Context): Promise<void> {
|
|
44
|
+
assertState(this._identity, 'identity not set');
|
|
45
|
+
await this._identity.space.spaceState.addCredentialProcessor(this);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected override async _close(ctx: Context): Promise<void> {
|
|
49
|
+
if (!this._identity) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
await this._identity.space.spaceState.removeCredentialProcessor(this);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async processCredential(credential: Credential): Promise<void> {
|
|
56
|
+
assertState(this._identity, 'identity not set');
|
|
57
|
+
const assertion = getCredentialAssertion(credential);
|
|
58
|
+
|
|
59
|
+
// A space was tombstoned on another device: replicate the deletion locally.
|
|
60
|
+
if (assertion['@type'] === 'dxos.halo.credentials.SpaceDeleted') {
|
|
61
|
+
if (assertion.spaceKey.equals(this._identity.space.key)) {
|
|
62
|
+
// ignore halo space
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!this.dataSpaceManager) {
|
|
66
|
+
log('dataSpaceManager not initialized yet, ignoring space deletion', { details: assertion });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
log('tombstoning space recorded in halo', { details: assertion });
|
|
72
|
+
await this.dataSpaceManager.handleRemoteSpaceDeleted(this._ctx, assertion.spaceKey);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
log.catch(err);
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (assertion['@type'] !== 'dxos.halo.credentials.SpaceMember') {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (assertion.spaceKey.equals(this._identity.space.key)) {
|
|
83
|
+
// ignore halo space
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!this.dataSpaceManager) {
|
|
87
|
+
log('dataSpaceManager not initialized yet, ignoring space admission', { details: assertion });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Do not re-accept a space that has been tombstoned (handles out-of-order credential replay).
|
|
91
|
+
if (this.dataSpaceManager.isSpaceDeleted(assertion.spaceKey)) {
|
|
92
|
+
log('space is deleted, ignoring space admission', { details: assertion });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (this.dataSpaceManager.spaces.has(assertion.spaceKey)) {
|
|
96
|
+
log('space already exists, ignoring space admission', { details: assertion });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
log('accepting space recorded in halo', { details: assertion });
|
|
102
|
+
await this.dataSpaceManager.acceptSpace(this._ctx, {
|
|
103
|
+
spaceKey: assertion.spaceKey,
|
|
104
|
+
genesisFeedKey: assertion.genesisFeedKey,
|
|
105
|
+
tags: assertion.tags,
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
log.catch(err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Creates a dormant {@link CrossDeviceSpaceSynchronizer} bound to the given data space manager.
|
|
115
|
+
*/
|
|
116
|
+
export const createCrossDeviceSpaceSynchronizer = (dataSpaceManager: DataSpaceManager): CrossDeviceSpaceSynchronizer =>
|
|
117
|
+
new CrossDeviceSpaceSynchronizerImpl(dataSpaceManager);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Effect Layer constructing a dormant {@link CrossDeviceSpaceSynchronizer}.
|
|
121
|
+
*/
|
|
122
|
+
export const CrossDeviceSpaceSynchronizerLayer: Layer.Layer<
|
|
123
|
+
CrossDeviceSpaceSynchronizerService,
|
|
124
|
+
never,
|
|
125
|
+
DataSpaceManagerService
|
|
126
|
+
> = Layer.effect(
|
|
127
|
+
CrossDeviceSpaceSynchronizerService,
|
|
128
|
+
Effect.gen(function* () {
|
|
129
|
+
const dataSpaceManager = yield* DataSpaceManagerService;
|
|
130
|
+
return createCrossDeviceSpaceSynchronizer(dataSpaceManager);
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as BrowserWorker from '@effect/platform-browser/BrowserWorker';
|
|
6
|
+
import * as BrowserWorkerRunner from '@effect/platform-browser/BrowserWorkerRunner';
|
|
7
|
+
import * as Rpc from '@effect/rpc/Rpc';
|
|
8
|
+
import * as RpcClient from '@effect/rpc/RpcClient';
|
|
9
|
+
import * as RpcGroup from '@effect/rpc/RpcGroup';
|
|
10
|
+
import * as RpcServer from '@effect/rpc/RpcServer';
|
|
11
|
+
import * as Effect from 'effect/Effect';
|
|
12
|
+
import * as Exit from 'effect/Exit';
|
|
13
|
+
import * as Layer from 'effect/Layer';
|
|
14
|
+
import * as Runtime from 'effect/Runtime';
|
|
15
|
+
import * as Schema from 'effect/Schema';
|
|
16
|
+
import * as Scope from 'effect/Scope';
|
|
17
|
+
import * as Stream from 'effect/Stream';
|
|
18
|
+
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
19
|
+
|
|
20
|
+
import { Trigger, sleep } from '@dxos/async';
|
|
21
|
+
import {
|
|
22
|
+
ClientRpcServer,
|
|
23
|
+
type ClientServicesHandlers,
|
|
24
|
+
type ClientServicesRpc,
|
|
25
|
+
makeClientServicesRpc,
|
|
26
|
+
makeServicesFromRpc,
|
|
27
|
+
} from '@dxos/client-protocol';
|
|
28
|
+
import { Stream as PbStream } from '@dxos/codec-protobuf/stream';
|
|
29
|
+
import { EffectEx } from '@dxos/effect';
|
|
30
|
+
import { PublicKey } from '@dxos/keys';
|
|
31
|
+
import { IdentityNotInitializedError, TimeoutError } from '@dxos/protocols';
|
|
32
|
+
import { type QueryStatusResponse, SpaceState, SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
|
|
33
|
+
import { MembershipPolicy } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
34
|
+
import { SpacesService, SystemService } from '@dxos/protocols/rpc';
|
|
35
|
+
|
|
36
|
+
//
|
|
37
|
+
// Helpers & Schema for test suite 2
|
|
38
|
+
//
|
|
39
|
+
|
|
40
|
+
class User extends Schema.Class<User>('User')({
|
|
41
|
+
id: Schema.String,
|
|
42
|
+
name: Schema.String,
|
|
43
|
+
}) {}
|
|
44
|
+
|
|
45
|
+
class TestGroup extends RpcGroup.make(
|
|
46
|
+
Rpc.make('ping', {
|
|
47
|
+
success: Schema.String,
|
|
48
|
+
payload: {
|
|
49
|
+
message: Schema.String,
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
Rpc.make('getUser', {
|
|
53
|
+
success: User,
|
|
54
|
+
payload: {
|
|
55
|
+
id: Schema.String,
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
) {}
|
|
59
|
+
|
|
60
|
+
const handlers = TestGroup.toLayer(
|
|
61
|
+
Effect.gen(function* () {
|
|
62
|
+
return {
|
|
63
|
+
ping: ({ message }) => Effect.succeed(`pong: ${message}`),
|
|
64
|
+
getUser: ({ id }) => Effect.succeed(new User({ id, name: `User ${id}` })),
|
|
65
|
+
};
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const makeMessageChannel = () =>
|
|
70
|
+
Effect.gen(function* () {
|
|
71
|
+
const channel = new MessageChannel();
|
|
72
|
+
const port1 = channel.port1;
|
|
73
|
+
const port2 = channel.port2;
|
|
74
|
+
|
|
75
|
+
yield* Effect.addFinalizer(() =>
|
|
76
|
+
Effect.sync(() => {
|
|
77
|
+
port1.close();
|
|
78
|
+
port2.close();
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return { port1, port2 };
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
//
|
|
86
|
+
// Helpers for test suite 1
|
|
87
|
+
//
|
|
88
|
+
|
|
89
|
+
const mockService = <T>(partial: Partial<T>): T => partial as T;
|
|
90
|
+
|
|
91
|
+
const setupRpc = async (
|
|
92
|
+
services: () => Partial<ClientServicesHandlers>,
|
|
93
|
+
options?: { onRequest?: () => Promise<void> },
|
|
94
|
+
): Promise<ClientServicesRpc> => {
|
|
95
|
+
const channel = new MessageChannel();
|
|
96
|
+
const proxyPort = channel.port1;
|
|
97
|
+
const serverPort = channel.port2;
|
|
98
|
+
|
|
99
|
+
const server = new ClientRpcServer({ services, port: serverPort, onRequest: options?.onRequest });
|
|
100
|
+
await server.open();
|
|
101
|
+
onTestFinished(() => server.close());
|
|
102
|
+
|
|
103
|
+
const scope = Effect.runSync(Scope.make());
|
|
104
|
+
onTestFinished(() => EffectEx.runPromise(Scope.close(scope, Exit.void)));
|
|
105
|
+
return EffectEx.runPromise(makeClientServicesRpc(proxyPort).pipe(Scope.extend(scope)));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const setup = async (
|
|
109
|
+
services: () => Partial<ClientServicesHandlers>,
|
|
110
|
+
options?: { onRequest?: () => Promise<void> },
|
|
111
|
+
) => {
|
|
112
|
+
const rpc = await setupRpc(services, options);
|
|
113
|
+
return makeServicesFromRpc(rpc, Runtime.defaultRuntime);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
//
|
|
117
|
+
// Test Suites
|
|
118
|
+
//
|
|
119
|
+
|
|
120
|
+
describe('client services effect-rpc', () => {
|
|
121
|
+
test('unary call round trip preserves substituted types', async ({ expect }) => {
|
|
122
|
+
const spaceKey = PublicKey.random();
|
|
123
|
+
const proxy = await setup(() => ({
|
|
124
|
+
SpacesService: mockService<SpacesService.Handlers>({
|
|
125
|
+
['SpacesService.createSpace']: () =>
|
|
126
|
+
Effect.succeed({
|
|
127
|
+
id: 'test-space',
|
|
128
|
+
spaceKey,
|
|
129
|
+
state: SpaceState.SPACE_READY,
|
|
130
|
+
membershipPolicy: MembershipPolicy.INVITE,
|
|
131
|
+
metrics: {},
|
|
132
|
+
}),
|
|
133
|
+
}),
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
const space = await proxy.SpacesService!.createSpace({ membershipPolicy: MembershipPolicy.INVITE });
|
|
137
|
+
expect(space.id).toEqual('test-space');
|
|
138
|
+
expect(space.spaceKey).toBeInstanceOf(PublicKey);
|
|
139
|
+
expect(space.spaceKey.equals(spaceKey)).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('streaming call round trip', async ({ expect }) => {
|
|
143
|
+
const proxy = await setup(() => ({
|
|
144
|
+
SystemService: mockService<SystemService.Handlers>({
|
|
145
|
+
['SystemService.queryStatus']: (): Stream.Stream<QueryStatusResponse, Error> =>
|
|
146
|
+
Stream.fromIterable([{ status: SystemStatus.INACTIVE }, { status: SystemStatus.ACTIVE }]),
|
|
147
|
+
}),
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
const statuses = await PbStream.consumeData(proxy.SystemService!.queryStatus({}));
|
|
151
|
+
expect(statuses.map((update) => update.status)).toEqual([SystemStatus.INACTIVE, SystemStatus.ACTIVE]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('stream errors propagate to the consumer', async ({ expect }) => {
|
|
155
|
+
const proxy = await setup(() => ({
|
|
156
|
+
SystemService: mockService<SystemService.Handlers>({
|
|
157
|
+
['SystemService.queryStatus']: (): Stream.Stream<QueryStatusResponse, Error> =>
|
|
158
|
+
Stream.make({ status: SystemStatus.ACTIVE }).pipe(Stream.concat(Stream.fail(new Error('stream failed')))),
|
|
159
|
+
}),
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
await expect(PbStream.consumeData(proxy.SystemService!.queryStatus({}))).rejects.toThrow('stream failed');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('typed errors keep their identity across the wire', async ({ expect }) => {
|
|
166
|
+
const proxy = await setup(() => ({
|
|
167
|
+
SystemService: mockService<SystemService.Handlers>({
|
|
168
|
+
['SystemService.getConfig']: () => Effect.fail(new IdentityNotInitializedError({ message: 'no identity' })),
|
|
169
|
+
}),
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
const error = await proxy.SystemService!.getConfig().then(
|
|
173
|
+
() => undefined,
|
|
174
|
+
(err) => err,
|
|
175
|
+
);
|
|
176
|
+
expect(error).toBeInstanceOf(IdentityNotInitializedError);
|
|
177
|
+
expect(error.message).toContain('no identity');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('calls fail when the service is not available', async ({ expect }) => {
|
|
181
|
+
const proxy = await setup(() => ({}));
|
|
182
|
+
await expect(proxy.SystemService!.getConfig()).rejects.toThrow(
|
|
183
|
+
'Service handler not available: SystemService.getConfig',
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('onRequest gates dispatch until ready', async ({ expect }) => {
|
|
188
|
+
const ready = new Trigger();
|
|
189
|
+
let called = false;
|
|
190
|
+
const proxy = await setup(
|
|
191
|
+
() => ({
|
|
192
|
+
SystemService: mockService<SystemService.Handlers>({
|
|
193
|
+
['SystemService.getConfig']: () =>
|
|
194
|
+
Effect.sync(() => {
|
|
195
|
+
called = true;
|
|
196
|
+
return {};
|
|
197
|
+
}),
|
|
198
|
+
}),
|
|
199
|
+
}),
|
|
200
|
+
{ onRequest: () => ready.wait() },
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const request = proxy.SystemService!.getConfig();
|
|
204
|
+
await sleep(50);
|
|
205
|
+
expect(called).toBe(false);
|
|
206
|
+
|
|
207
|
+
ready.wake();
|
|
208
|
+
await request;
|
|
209
|
+
expect(called).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('per-call timeout', async ({ expect }) => {
|
|
213
|
+
const proxy = await setup(() => ({
|
|
214
|
+
SystemService: mockService<SystemService.Handlers>({
|
|
215
|
+
['SystemService.getConfig']: () => Effect.never,
|
|
216
|
+
}),
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
await expect(proxy.SystemService!.getConfig(undefined, { timeout: 100 })).rejects.toThrow(TimeoutError);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('effect-native rpc surface: unary and stream', async ({ expect }) => {
|
|
223
|
+
const rpc = await setupRpc(() => ({
|
|
224
|
+
SystemService: mockService<SystemService.Handlers>({
|
|
225
|
+
['SystemService.getConfig']: () =>
|
|
226
|
+
Effect.succeed({ runtime: { client: { remoteSource: 'https://example.com' } } }),
|
|
227
|
+
['SystemService.queryStatus']: (): Stream.Stream<QueryStatusResponse, Error> =>
|
|
228
|
+
Stream.make({ status: SystemStatus.ACTIVE }),
|
|
229
|
+
}),
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
const config = await EffectEx.runPromise(rpc.SystemService.getConfig(undefined));
|
|
233
|
+
expect(config.runtime?.client?.remoteSource).toEqual('https://example.com');
|
|
234
|
+
|
|
235
|
+
const statuses = await EffectEx.runPromise(rpc.SystemService.queryStatus({}).pipe(Stream.runCollect));
|
|
236
|
+
expect([...statuses].map((update) => update.status)).toEqual([SystemStatus.ACTIVE]);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('effect-rpc tests', () => {
|
|
241
|
+
test(
|
|
242
|
+
'1. just regular test with a couple rpcs',
|
|
243
|
+
Effect.fnUntraced(function* () {
|
|
244
|
+
const { port1, port2 } = yield* makeMessageChannel();
|
|
245
|
+
|
|
246
|
+
const serverLayer = Layer.mergeAll(
|
|
247
|
+
handlers,
|
|
248
|
+
RpcServer.layerProtocolWorkerRunner.pipe(Layer.provide(BrowserWorkerRunner.layerMessagePort(port2))),
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
yield* RpcServer.make(TestGroup, { disableTracing: true }).pipe(Effect.provide(serverLayer), Effect.forkScoped);
|
|
252
|
+
|
|
253
|
+
const clientLayer = RpcClient.layerProtocolWorker({ size: 1 }).pipe(
|
|
254
|
+
Layer.provide(BrowserWorker.layerPlatform(() => port1)),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
yield* Effect.gen(function* () {
|
|
258
|
+
const client = yield* RpcClient.make(TestGroup, { disableTracing: true });
|
|
259
|
+
const pongResult = yield* client.ping({ message: 'hello' });
|
|
260
|
+
expect(pongResult).toBe('pong: hello');
|
|
261
|
+
|
|
262
|
+
const userResult = yield* client.getUser({ id: '42' });
|
|
263
|
+
expect(userResult.id).toBe('42');
|
|
264
|
+
expect(userResult.name).toBe('User 42');
|
|
265
|
+
}).pipe(Effect.provide(clientLayer));
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
test(
|
|
270
|
+
'3. two message channels to one server (simulating 2 tabs one worker)',
|
|
271
|
+
Effect.fnUntraced(function* () {
|
|
272
|
+
const channel1 = yield* makeMessageChannel();
|
|
273
|
+
const channel2 = yield* makeMessageChannel();
|
|
274
|
+
|
|
275
|
+
const serverLayer1 = Layer.mergeAll(
|
|
276
|
+
handlers,
|
|
277
|
+
Layer.fresh(RpcServer.layerProtocolWorkerRunner).pipe(
|
|
278
|
+
Layer.provide(BrowserWorkerRunner.layerMessagePort(channel1.port2)),
|
|
279
|
+
),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const serverLayer2 = Layer.mergeAll(
|
|
283
|
+
handlers,
|
|
284
|
+
Layer.fresh(RpcServer.layerProtocolWorkerRunner).pipe(
|
|
285
|
+
Layer.provide(BrowserWorkerRunner.layerMessagePort(channel2.port2)),
|
|
286
|
+
),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
yield* RpcServer.make(TestGroup, { disableTracing: true }).pipe(Effect.provide(serverLayer1), Effect.forkScoped);
|
|
290
|
+
|
|
291
|
+
yield* RpcServer.make(TestGroup, { disableTracing: true }).pipe(Effect.provide(serverLayer2), Effect.forkScoped);
|
|
292
|
+
|
|
293
|
+
const clientLayer1 = RpcClient.layerProtocolWorker({ size: 1 }).pipe(
|
|
294
|
+
Layer.provide(BrowserWorker.layerPlatform(() => channel1.port1)),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
yield* Effect.gen(function* () {
|
|
298
|
+
const client = yield* RpcClient.make(TestGroup, { disableTracing: true });
|
|
299
|
+
const result = yield* client.ping({ message: 'tab1' });
|
|
300
|
+
expect(result).toBe('pong: tab1');
|
|
301
|
+
}).pipe(Effect.provide(clientLayer1));
|
|
302
|
+
|
|
303
|
+
const clientLayer2 = RpcClient.layerProtocolWorker({ size: 1 }).pipe(
|
|
304
|
+
Layer.provide(BrowserWorker.layerPlatform(() => channel2.port1)),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
yield* Effect.gen(function* () {
|
|
308
|
+
const client = yield* RpcClient.make(TestGroup, { disableTracing: true });
|
|
309
|
+
const result = yield* client.ping({ message: 'tab2' });
|
|
310
|
+
expect(result).toBe('pong: tab2');
|
|
311
|
+
}).pipe(Effect.provide(clientLayer2));
|
|
312
|
+
}),
|
|
313
|
+
);
|
|
314
|
+
});
|