@dxos/client-services 0.8.4-main.e8ec1fe → 0.8.4-main.ef1bc66f44
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/browser/chunk-NQSC7HOE.mjs +22 -0
- package/dist/lib/browser/chunk-NQSC7HOE.mjs.map +7 -0
- package/dist/lib/browser/{chunk-CZSLDMSL.mjs → chunk-NTZOEM4P.mjs} +1197 -1338
- package/dist/lib/browser/chunk-NTZOEM4P.mjs.map +7 -0
- package/dist/lib/browser/chunk-QCWEHHJW.mjs +24 -0
- package/dist/lib/browser/chunk-QCWEHHJW.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +441 -66
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +93 -0
- package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
- package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
- package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
- package/dist/lib/browser/packlets/locks/browser.mjs +126 -0
- package/dist/lib/browser/packlets/locks/browser.mjs.map +7 -0
- package/dist/lib/browser/packlets/locks/node.mjs +66 -0
- package/dist/lib/browser/packlets/locks/node.mjs.map +7 -0
- package/dist/lib/browser/testing/index.mjs +24 -12
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +24 -0
- package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-EUNILIU5.mjs → chunk-DQWA5ILA.mjs} +689 -699
- package/dist/lib/node-esm/chunk-DQWA5ILA.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-PKEGMOQ4.mjs +22 -0
- package/dist/lib/node-esm/chunk-PKEGMOQ4.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +441 -66
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +93 -0
- package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
- package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
- package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
- package/dist/lib/node-esm/packlets/locks/browser.mjs +126 -0
- package/dist/lib/node-esm/packlets/locks/browser.mjs.map +7 -0
- package/dist/lib/node-esm/packlets/locks/node.mjs +66 -0
- package/dist/lib/node-esm/packlets/locks/node.mjs.map +7 -0
- package/dist/lib/node-esm/testing/index.mjs +24 -12
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
- package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
- package/dist/types/src/packlets/diagnostics/index.d.ts +1 -1
- package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts +2 -2
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +2 -2
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +4 -4
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +2 -2
- package/dist/types/src/packlets/identity/identity-recovery-manager.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/device-invitation-protocol.d.ts +4 -4
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +2 -3
- package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -4
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -2
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/locks/index.d.ts +1 -1
- package/dist/types/src/packlets/locks/index.d.ts.map +1 -1
- package/dist/types/src/packlets/logging/logging-service.d.ts +4 -0
- package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/client-rpc-server.d.ts +2 -2
- package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
- package/dist/types/src/packlets/services/feed-syncer.d.ts +41 -0
- package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
- package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +13 -7
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +19 -5
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +10 -5
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +2 -2
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +2 -2
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -6
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts +6 -3
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +6 -5
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts +31 -4
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-session.d.ts +2 -2
- package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +70 -48
- package/src/packlets/devtools/devtools.ts +2 -2
- package/src/packlets/diagnostics/index.ts +1 -1
- package/src/packlets/identity/authenticator.ts +2 -2
- package/src/packlets/identity/default-space-state-machine.ts +2 -2
- package/src/packlets/identity/identity-manager.ts +6 -6
- package/src/packlets/identity/identity-recovery-manager.ts +2 -2
- package/src/packlets/identity/identity.ts +2 -2
- package/src/packlets/invitations/device-invitation-protocol.ts +5 -5
- package/src/packlets/invitations/invitation-guest-extenstion.ts +6 -4
- package/src/packlets/invitations/invitation-host-extension.ts +6 -4
- package/src/packlets/invitations/invitation-protocol.ts +2 -3
- package/src/packlets/invitations/invitations-handler.ts +7 -7
- package/src/packlets/invitations/space-invitation-protocol.ts +7 -13
- package/src/packlets/locks/index.ts +1 -1
- package/src/packlets/logging/logging-service.ts +4 -0
- package/src/packlets/services/client-rpc-server.ts +4 -4
- package/src/packlets/services/feed-syncer.ts +227 -0
- package/src/packlets/services/platform.ts +7 -1
- package/src/packlets/services/service-context.ts +47 -21
- package/src/packlets/services/service-host.ts +56 -16
- package/src/packlets/space-export/space-archive-reader.ts +1 -1
- package/src/packlets/space-export/space-archive-writer.ts +3 -2
- package/src/packlets/spaces/data-space-manager.ts +43 -20
- package/src/packlets/spaces/data-space.ts +10 -6
- package/src/packlets/spaces/edge-feed-replicator.ts +2 -2
- package/src/packlets/spaces/epoch-migrations.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.test.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.ts +8 -8
- package/src/packlets/spaces/spaces-service.ts +10 -7
- package/src/packlets/storage/storage.ts +4 -4
- package/src/packlets/testing/invitation-utils.ts +7 -4
- package/src/packlets/testing/test-builder.ts +36 -10
- package/src/packlets/worker/worker-runtime.ts +149 -11
- package/src/packlets/worker/worker-session.ts +4 -4
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-CZSLDMSL.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-EUNILIU5.mjs.map +0 -7
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type * as SqlClient from '@effect/sql/SqlClient';
|
|
6
|
+
import { Encoder, decode as cborXdecode } from 'cbor-x';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
9
|
+
|
|
10
|
+
import { AsyncTask, scheduleTask } from '@dxos/async';
|
|
11
|
+
import { Resource } from '@dxos/context';
|
|
12
|
+
import { type EdgeConnection, MessageSchema } from '@dxos/edge-client';
|
|
13
|
+
import { RuntimeProvider } from '@dxos/effect';
|
|
14
|
+
import { type FeedStore, SyncClient } from '@dxos/feed';
|
|
15
|
+
import { type SpaceId } from '@dxos/keys';
|
|
16
|
+
import { FeedProtocol } from '@dxos/protocols';
|
|
17
|
+
import { EdgeService } from '@dxos/protocols';
|
|
18
|
+
import { createBuf } from '@dxos/protocols/buf';
|
|
19
|
+
import { type Message as RouterMessage } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
|
|
20
|
+
import type { SqlTransaction } from '@dxos/sql-sqlite';
|
|
21
|
+
import { bufferToArray } from '@dxos/util';
|
|
22
|
+
|
|
23
|
+
const encoder = new Encoder({ tagUint8Array: false, useRecords: false });
|
|
24
|
+
|
|
25
|
+
const DEFAULT_MESSAGE_BLOCKS_LIMIT = 50;
|
|
26
|
+
const DEFAULT_SYNC_CONCURRENCY = 5;
|
|
27
|
+
const DEFAULT_POLLING_INTERVAL = 10_000;
|
|
28
|
+
|
|
29
|
+
interface FeedSyncerOptions {
|
|
30
|
+
runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
|
|
31
|
+
feedStore: FeedStore;
|
|
32
|
+
edgeClient: EdgeConnection;
|
|
33
|
+
peerId: string;
|
|
34
|
+
getSpaceIds: () => SpaceId[];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Namespace to sync.
|
|
38
|
+
*/
|
|
39
|
+
// TODO(dmaretskyi): Syncing only one namespace is supported.
|
|
40
|
+
syncNamespace: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Maximum number of blocks to sync in a single message.
|
|
44
|
+
* @default 50
|
|
45
|
+
*/
|
|
46
|
+
messageBlocksLimit?: number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Maximum number of spaces to sync concurrently.
|
|
50
|
+
* @default 5
|
|
51
|
+
*/
|
|
52
|
+
syncConcurrency?: number;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Interval between full polls.
|
|
56
|
+
* @default 10 seconds
|
|
57
|
+
*/
|
|
58
|
+
pollingInterval?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class FeedSyncer extends Resource {
|
|
62
|
+
readonly #syncNamespace: string;
|
|
63
|
+
readonly #messageBlocksLimit: number;
|
|
64
|
+
readonly #syncConcurrency: number;
|
|
65
|
+
readonly #pollingInterval: number;
|
|
66
|
+
|
|
67
|
+
readonly #runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
|
|
68
|
+
readonly #feedStore: FeedStore;
|
|
69
|
+
readonly #edgeClient: EdgeConnection;
|
|
70
|
+
readonly #syncClient: SyncClient;
|
|
71
|
+
readonly #getSpaceIds: () => SpaceId[];
|
|
72
|
+
|
|
73
|
+
#spacesToPoll = new Set<SpaceId>();
|
|
74
|
+
/** Last time full poll was completed. */
|
|
75
|
+
#lastFullPoll: number | null = null;
|
|
76
|
+
|
|
77
|
+
constructor(options: FeedSyncerOptions) {
|
|
78
|
+
super();
|
|
79
|
+
this.#runtime = options.runtime;
|
|
80
|
+
this.#feedStore = options.feedStore;
|
|
81
|
+
this.#edgeClient = options.edgeClient;
|
|
82
|
+
this.#syncClient = new SyncClient({
|
|
83
|
+
peerId: options.peerId,
|
|
84
|
+
feedStore: options.feedStore,
|
|
85
|
+
sendMessage: this.#sendMessage.bind(this),
|
|
86
|
+
});
|
|
87
|
+
this.#getSpaceIds = options.getSpaceIds;
|
|
88
|
+
this.#syncNamespace = options.syncNamespace;
|
|
89
|
+
this.#messageBlocksLimit = options.messageBlocksLimit ?? DEFAULT_MESSAGE_BLOCKS_LIMIT;
|
|
90
|
+
this.#syncConcurrency = options.syncConcurrency ?? DEFAULT_SYNC_CONCURRENCY;
|
|
91
|
+
this.#pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
protected override async _open(): Promise<void> {
|
|
95
|
+
this._ctx.onDispose(
|
|
96
|
+
this.#edgeClient.onMessage((msg: RouterMessage) => {
|
|
97
|
+
if (!msg.serviceId) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const service = msg.serviceId.split(':')[0];
|
|
101
|
+
if (service !== EdgeService.QUEUE_REPLICATOR) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const handleMessageEffect = Effect.gen(this, function* () {
|
|
105
|
+
const decoded = yield* Effect.try({
|
|
106
|
+
try: () => cborXdecode(msg.payload!.value),
|
|
107
|
+
catch: (error) => new Error(`Failed to decode feed sync message: ${error}`),
|
|
108
|
+
});
|
|
109
|
+
const payload = yield* Schema.decodeUnknown(FeedProtocol.ProtocolMessage)(decoded);
|
|
110
|
+
yield* this.#syncClient.handleMessage(payload);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
void RuntimeProvider.runPromise(this.#runtime)(handleMessageEffect);
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
this._ctx.onDispose(
|
|
118
|
+
// NOTE: This will fire immediately if the connection is already open.
|
|
119
|
+
this.#edgeClient.onReconnected(async () => {}),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
this.#feedStore.onNewBlocks.on(this._ctx, () => {
|
|
123
|
+
this.#pushTask.schedule();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await this.#pollTask.open();
|
|
127
|
+
await this.#pushTask.open();
|
|
128
|
+
|
|
129
|
+
this.#resetSpacesToPoll();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
protected override async _close(): Promise<void> {
|
|
133
|
+
await this.#pollTask.close();
|
|
134
|
+
await this.#pushTask.close();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#resetSpacesToPoll(): void {
|
|
138
|
+
this.#spacesToPoll.clear();
|
|
139
|
+
this.#getSpaceIds().forEach((spaceId) => {
|
|
140
|
+
this.#spacesToPoll.add(spaceId);
|
|
141
|
+
});
|
|
142
|
+
this.#lastFullPoll = Date.now();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#sendMessage(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): Effect.Effect<void, unknown, never> {
|
|
146
|
+
return Effect.gen(this, function* () {
|
|
147
|
+
const encoded = encoder.encode(message);
|
|
148
|
+
yield* Effect.tryPromise(async () =>
|
|
149
|
+
this.#edgeClient.send(
|
|
150
|
+
createBuf(MessageSchema, {
|
|
151
|
+
source: {
|
|
152
|
+
identityKey: this.#edgeClient.identityKey,
|
|
153
|
+
peerKey: this.#edgeClient.peerKey,
|
|
154
|
+
},
|
|
155
|
+
serviceId: this.#getTargetServiceId(message),
|
|
156
|
+
payload: { value: bufferToArray(encoded) },
|
|
157
|
+
}),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#getTargetServiceId(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): string {
|
|
164
|
+
// TODO(dmaretskyi): Perhaps in the future we will want to include the queue namespace here as well.
|
|
165
|
+
// This would require putting it at the top level of the message.
|
|
166
|
+
// For now, we let the edge router handle it.
|
|
167
|
+
return FeedProtocol.encodeServiceId(message.feedNamespace, message.spaceId as SpaceId);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
readonly #pollTask = new AsyncTask(async () =>
|
|
171
|
+
Effect.gen(this, function* () {
|
|
172
|
+
yield* Effect.forEach(
|
|
173
|
+
this.#spacesToPoll,
|
|
174
|
+
(spaceId) =>
|
|
175
|
+
Effect.gen(this, function* () {
|
|
176
|
+
const { done } = yield* this.#syncClient.pull({
|
|
177
|
+
spaceId,
|
|
178
|
+
feedNamespace: this.#syncNamespace,
|
|
179
|
+
limit: this.#messageBlocksLimit,
|
|
180
|
+
});
|
|
181
|
+
if (done) {
|
|
182
|
+
this.#spacesToPoll.delete(spaceId);
|
|
183
|
+
}
|
|
184
|
+
}),
|
|
185
|
+
{ concurrency: this.#syncConcurrency },
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// If its time to do a full poll, reset the spaces to poll and schedule the next poll immediately.
|
|
189
|
+
if (this.#lastFullPoll == null || Date.now() - this.#lastFullPoll > this.#pollingInterval) {
|
|
190
|
+
this.#resetSpacesToPoll();
|
|
191
|
+
this.#pollTask.schedule();
|
|
192
|
+
} else if (this.#spacesToPoll.size > 0) {
|
|
193
|
+
// If there are some spaces still syncing, poll them immediately.
|
|
194
|
+
this.#pollTask.schedule();
|
|
195
|
+
} else {
|
|
196
|
+
// All spaces sync, and there's time before the next full poll, schedule it later.
|
|
197
|
+
this.#resetSpacesToPoll();
|
|
198
|
+
scheduleTask(
|
|
199
|
+
this._ctx,
|
|
200
|
+
() => this.#pollTask.schedule(),
|
|
201
|
+
Math.max(this.#pollingInterval - (Date.now() - (this.#lastFullPoll ?? 0)), 0),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}).pipe(RuntimeProvider.runPromise(this.#runtime)),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
readonly #pushTask = new AsyncTask(async () =>
|
|
208
|
+
Effect.gen(this, function* () {
|
|
209
|
+
yield* Effect.forEach(
|
|
210
|
+
this.#getSpaceIds(),
|
|
211
|
+
(spaceId) =>
|
|
212
|
+
Effect.gen(this, function* () {
|
|
213
|
+
const { done } = yield* this.#syncClient.push({
|
|
214
|
+
spaceId,
|
|
215
|
+
feedNamespace: this.#syncNamespace,
|
|
216
|
+
limit: this.#messageBlocksLimit,
|
|
217
|
+
});
|
|
218
|
+
if (!done) {
|
|
219
|
+
// Keep pushing until all blocks are pushed.
|
|
220
|
+
this.#pushTask.schedule();
|
|
221
|
+
}
|
|
222
|
+
}),
|
|
223
|
+
{ concurrency: this.#syncConcurrency },
|
|
224
|
+
);
|
|
225
|
+
}).pipe(RuntimeProvider.runPromise(this.#runtime)),
|
|
226
|
+
);
|
|
227
|
+
}
|
|
@@ -14,12 +14,18 @@ export const getPlatform = (): Platform => {
|
|
|
14
14
|
userAgent,
|
|
15
15
|
uptime: Math.floor((Date.now() - window.performance.timeOrigin) / 1_000),
|
|
16
16
|
};
|
|
17
|
-
} else {
|
|
17
|
+
} else if (typeof SharedWorkerGlobalScope !== 'undefined') {
|
|
18
18
|
// Shared worker.
|
|
19
19
|
return {
|
|
20
20
|
type: Platform.PLATFORM_TYPE.SHARED_WORKER,
|
|
21
21
|
uptime: Math.floor((Date.now() - performance.timeOrigin) / 1_000),
|
|
22
22
|
};
|
|
23
|
+
} else {
|
|
24
|
+
// Dedicated worker.
|
|
25
|
+
return {
|
|
26
|
+
type: Platform.PLATFORM_TYPE.DEDICATED_WORKER,
|
|
27
|
+
uptime: Math.floor((Date.now() - performance.timeOrigin) / 1_000),
|
|
28
|
+
};
|
|
23
29
|
}
|
|
24
30
|
} else {
|
|
25
31
|
// Node.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import type * as SqlClient from '@effect/sql/SqlClient';
|
|
6
|
+
|
|
5
7
|
import { Mutex, Trigger } from '@dxos/async';
|
|
6
8
|
import { Context, Resource } from '@dxos/context';
|
|
7
9
|
import { type CredentialProcessor, getCredentialAssertion } from '@dxos/credentials';
|
|
@@ -16,6 +18,7 @@ import {
|
|
|
16
18
|
} from '@dxos/echo-pipeline';
|
|
17
19
|
import { createChainEdgeIdentity, createEphemeralEdgeIdentity } from '@dxos/edge-client';
|
|
18
20
|
import type { EdgeConnection, EdgeHttpClient, EdgeIdentity } from '@dxos/edge-client';
|
|
21
|
+
import { type RuntimeProvider } from '@dxos/effect';
|
|
19
22
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
20
23
|
import { invariant } from '@dxos/invariant';
|
|
21
24
|
import { Keyring } from '@dxos/keyring';
|
|
@@ -25,11 +28,13 @@ import { log } from '@dxos/log';
|
|
|
25
28
|
import { type SignalManager } from '@dxos/messaging';
|
|
26
29
|
import { type SwarmNetworkManager } from '@dxos/network-manager';
|
|
27
30
|
import { InvalidStorageVersionError, STORAGE_VERSION, trace } from '@dxos/protocols';
|
|
31
|
+
import { FeedProtocol } from '@dxos/protocols';
|
|
28
32
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
29
33
|
import { type Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
30
34
|
import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
31
35
|
import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
32
36
|
import { type Storage } from '@dxos/random-access-storage';
|
|
37
|
+
import type * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
|
|
33
38
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
34
39
|
import { trace as Trace } from '@dxos/tracing';
|
|
35
40
|
import { safeInstanceof } from '@dxos/util';
|
|
@@ -38,28 +43,31 @@ import { EdgeAgentManager } from '../agents';
|
|
|
38
43
|
import {
|
|
39
44
|
type CreateIdentityOptions,
|
|
40
45
|
IdentityManager,
|
|
41
|
-
type
|
|
42
|
-
type
|
|
46
|
+
type IdentityManagerProps,
|
|
47
|
+
type JoinIdentityProps,
|
|
43
48
|
} from '../identity';
|
|
44
49
|
import { EdgeIdentityRecoveryManager } from '../identity/identity-recovery-manager';
|
|
45
50
|
import {
|
|
46
51
|
DeviceInvitationProtocol,
|
|
47
|
-
type
|
|
52
|
+
type InvitationConnectionProps,
|
|
48
53
|
type InvitationProtocol,
|
|
49
54
|
InvitationsHandler,
|
|
50
55
|
InvitationsManager,
|
|
51
56
|
SpaceInvitationProtocol,
|
|
52
57
|
} from '../invitations';
|
|
53
|
-
import { DataSpaceManager, type
|
|
58
|
+
import { DataSpaceManager, type DataSpaceManagerRuntimeProps, type SigningContext } from '../spaces';
|
|
59
|
+
|
|
60
|
+
import { FeedSyncer } from './feed-syncer';
|
|
54
61
|
|
|
55
|
-
export type
|
|
56
|
-
|
|
62
|
+
export type ServiceContextRuntimeProps = Pick<
|
|
63
|
+
IdentityManagerProps,
|
|
57
64
|
'devicePresenceOfflineTimeout' | 'devicePresenceAnnounceInterval'
|
|
58
65
|
> &
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
DataSpaceManagerRuntimeProps & {
|
|
67
|
+
invitationConnectionDefaultProps?: InvitationConnectionProps;
|
|
61
68
|
disableP2pReplication?: boolean;
|
|
62
69
|
enableVectorIndexing?: boolean;
|
|
70
|
+
enableLocalQueues?: boolean;
|
|
63
71
|
};
|
|
64
72
|
/**
|
|
65
73
|
* Shared backend for all client services.
|
|
@@ -84,6 +92,7 @@ export class ServiceContext extends Resource {
|
|
|
84
92
|
public readonly echoHost: EchoHost;
|
|
85
93
|
private readonly _meshReplicator?: MeshEchoReplicator = undefined;
|
|
86
94
|
private readonly _echoEdgeReplicator?: EchoEdgeReplicator = undefined;
|
|
95
|
+
private readonly _feedSyncer?: FeedSyncer = undefined;
|
|
87
96
|
|
|
88
97
|
// Initialized after identity is initialized.
|
|
89
98
|
public dataSpaceManager?: DataSpaceManager;
|
|
@@ -105,11 +114,15 @@ export class ServiceContext extends Resource {
|
|
|
105
114
|
public readonly signalManager: SignalManager,
|
|
106
115
|
private readonly _edgeConnection: EdgeConnection | undefined,
|
|
107
116
|
private readonly _edgeHttpClient: EdgeHttpClient | undefined,
|
|
108
|
-
|
|
117
|
+
private readonly _runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>,
|
|
118
|
+
public readonly _runtimeProps?: ServiceContextRuntimeProps,
|
|
109
119
|
private readonly _edgeFeatures?: Runtime.Client.EdgeFeatures,
|
|
110
120
|
) {
|
|
111
121
|
super();
|
|
112
122
|
|
|
123
|
+
log('runtimeProps', this._runtimeProps);
|
|
124
|
+
log('edgeFeatures', this._edgeFeatures);
|
|
125
|
+
|
|
113
126
|
// TODO(burdon): Move strings to constants.
|
|
114
127
|
this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
|
|
115
128
|
this.blobStore = new BlobStore(storage.createDirectory('blobs'));
|
|
@@ -131,7 +144,7 @@ export class ServiceContext extends Resource {
|
|
|
131
144
|
networkManager: this.networkManager,
|
|
132
145
|
blobStore: this.blobStore,
|
|
133
146
|
metadataStore: this.metadataStore,
|
|
134
|
-
disableP2pReplication: this.
|
|
147
|
+
disableP2pReplication: this._runtimeProps?.disableP2pReplication,
|
|
135
148
|
});
|
|
136
149
|
|
|
137
150
|
this.identityManager = new IdentityManager({
|
|
@@ -139,8 +152,8 @@ export class ServiceContext extends Resource {
|
|
|
139
152
|
keyring: this.keyring,
|
|
140
153
|
feedStore: this.feedStore,
|
|
141
154
|
spaceManager: this.spaceManager,
|
|
142
|
-
devicePresenceOfflineTimeout: this.
|
|
143
|
-
devicePresenceAnnounceInterval: this.
|
|
155
|
+
devicePresenceOfflineTimeout: this._runtimeProps?.devicePresenceOfflineTimeout,
|
|
156
|
+
devicePresenceAnnounceInterval: this._runtimeProps?.devicePresenceAnnounceInterval,
|
|
144
157
|
edgeConnection: this._edgeConnection,
|
|
145
158
|
edgeFeatures: this._edgeFeatures,
|
|
146
159
|
});
|
|
@@ -156,17 +169,14 @@ export class ServiceContext extends Resource {
|
|
|
156
169
|
kv: this.level,
|
|
157
170
|
peerIdProvider: () => this.identityManager.identity?.deviceKey?.toHex(),
|
|
158
171
|
getSpaceKeyByRootDocumentId: (documentId) => this.spaceManager.findSpaceByRootDocumentId(documentId)?.key,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
},
|
|
172
|
+
runtime: this._runtime,
|
|
173
|
+
localQueues: this._runtimeProps?.enableLocalQueues,
|
|
162
174
|
});
|
|
163
175
|
|
|
164
|
-
this._meshReplicator = new MeshEchoReplicator();
|
|
165
|
-
|
|
166
176
|
this.invitations = new InvitationsHandler(
|
|
167
177
|
this.networkManager, //
|
|
168
178
|
this._edgeHttpClient,
|
|
169
|
-
|
|
179
|
+
_runtimeProps?.invitationConnectionDefaultProps,
|
|
170
180
|
);
|
|
171
181
|
this.invitationsManager = new InvitationsManager(
|
|
172
182
|
this.invitations,
|
|
@@ -186,7 +196,7 @@ export class ServiceContext extends Resource {
|
|
|
186
196
|
),
|
|
187
197
|
);
|
|
188
198
|
|
|
189
|
-
if (!this.
|
|
199
|
+
if (!this._runtimeProps?.disableP2pReplication) {
|
|
190
200
|
this._meshReplicator = new MeshEchoReplicator();
|
|
191
201
|
}
|
|
192
202
|
if (this._edgeConnection && this._edgeFeatures?.echoReplicator && this._edgeHttpClient) {
|
|
@@ -195,6 +205,17 @@ export class ServiceContext extends Resource {
|
|
|
195
205
|
edgeHttpClient: this._edgeHttpClient,
|
|
196
206
|
});
|
|
197
207
|
}
|
|
208
|
+
|
|
209
|
+
if (this.echoHost.feedStore && this._edgeConnection) {
|
|
210
|
+
this._feedSyncer = new FeedSyncer({
|
|
211
|
+
runtime: this._runtime,
|
|
212
|
+
feedStore: this.echoHost.feedStore,
|
|
213
|
+
edgeClient: this._edgeConnection,
|
|
214
|
+
peerId: this.identityManager.identity?.deviceKey?.toHex() ?? '',
|
|
215
|
+
getSpaceIds: () => this.echoHost!.spaceIds,
|
|
216
|
+
syncNamespace: FeedProtocol.WellKnownNamespaces.data,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
198
219
|
}
|
|
199
220
|
|
|
200
221
|
@Trace.span()
|
|
@@ -229,6 +250,8 @@ export class ServiceContext extends Resource {
|
|
|
229
250
|
await this._initialize(ctx);
|
|
230
251
|
}
|
|
231
252
|
|
|
253
|
+
await this._feedSyncer?.open();
|
|
254
|
+
|
|
232
255
|
const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
|
|
233
256
|
log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
|
|
234
257
|
|
|
@@ -238,6 +261,9 @@ export class ServiceContext extends Resource {
|
|
|
238
261
|
|
|
239
262
|
protected override async _close(ctx: Context): Promise<void> {
|
|
240
263
|
log('closing...');
|
|
264
|
+
|
|
265
|
+
await this._feedSyncer?.close();
|
|
266
|
+
|
|
241
267
|
if (this._deviceSpaceSync && this.identityManager.identity) {
|
|
242
268
|
await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
|
|
243
269
|
}
|
|
@@ -283,7 +309,7 @@ export class ServiceContext extends Resource {
|
|
|
283
309
|
}
|
|
284
310
|
}
|
|
285
311
|
|
|
286
|
-
private async _acceptIdentity(params:
|
|
312
|
+
private async _acceptIdentity(params: JoinIdentityProps) {
|
|
287
313
|
const { identity, identityRecord } = await this.identityManager.prepareIdentity(params);
|
|
288
314
|
await this._setNetworkIdentity({ deviceCredential: params.authorizedDeviceCredential! });
|
|
289
315
|
await identity.joinNetwork();
|
|
@@ -327,7 +353,7 @@ export class ServiceContext extends Resource {
|
|
|
327
353
|
edgeHttpClient: this._edgeHttpClient,
|
|
328
354
|
echoEdgeReplicator: this._echoEdgeReplicator,
|
|
329
355
|
meshReplicator: this._meshReplicator,
|
|
330
|
-
|
|
356
|
+
runtimeProps: this._runtimeProps as DataSpaceManagerRuntimeProps,
|
|
331
357
|
edgeFeatures: this._edgeFeatures,
|
|
332
358
|
});
|
|
333
359
|
await this.dataSpaceManager.open();
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import * as SqlClient from '@effect/sql/SqlClient';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
|
|
5
8
|
import { Event, synchronized } from '@dxos/async';
|
|
6
9
|
import { type ClientServices, clientServiceBundle } from '@dxos/client-protocol';
|
|
7
10
|
import { type Config } from '@dxos/config';
|
|
8
11
|
import { Context } from '@dxos/context';
|
|
9
12
|
import { EdgeClient, type EdgeConnection, EdgeHttpClient, createStubEdgeIdentity } from '@dxos/edge-client';
|
|
13
|
+
import { RuntimeProvider } from '@dxos/effect';
|
|
10
14
|
import { invariant } from '@dxos/invariant';
|
|
11
15
|
import { PublicKey } from '@dxos/keys';
|
|
12
16
|
import { type LevelDB } from '@dxos/kv-store';
|
|
@@ -21,6 +25,8 @@ import {
|
|
|
21
25
|
import { trace } from '@dxos/protocols';
|
|
22
26
|
import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
|
|
23
27
|
import { type Storage } from '@dxos/random-access-storage';
|
|
28
|
+
import * as SqlExport from '@dxos/sql-sqlite/SqlExport';
|
|
29
|
+
import type * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
|
|
24
30
|
import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
|
|
25
31
|
import { WebsocketRpcClient } from '@dxos/websocket-rpc';
|
|
26
32
|
|
|
@@ -42,10 +48,10 @@ import { SpacesServiceImpl } from '../spaces';
|
|
|
42
48
|
import { createLevel, createStorageObjects } from '../storage';
|
|
43
49
|
import { SystemServiceImpl } from '../system';
|
|
44
50
|
|
|
45
|
-
import { ServiceContext, type
|
|
51
|
+
import { ServiceContext, type ServiceContextRuntimeProps } from './service-context';
|
|
46
52
|
import { ServiceRegistry } from './service-registry';
|
|
47
53
|
|
|
48
|
-
export type
|
|
54
|
+
export type ClientServicesHostProps = {
|
|
49
55
|
/**
|
|
50
56
|
* Can be omitted if `initialize` is later called.
|
|
51
57
|
*/
|
|
@@ -57,7 +63,8 @@ export type ClientServicesHostParams = {
|
|
|
57
63
|
level?: LevelDB;
|
|
58
64
|
lockKey?: string;
|
|
59
65
|
callbacks?: ClientServicesHostCallbacks;
|
|
60
|
-
|
|
66
|
+
runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlExport.SqlExport | SqlTransaction.SqlTransaction>;
|
|
67
|
+
runtimeProps?: ServiceContextRuntimeProps;
|
|
61
68
|
};
|
|
62
69
|
|
|
63
70
|
export type ClientServicesHostCallbacks = {
|
|
@@ -95,7 +102,10 @@ export class ClientServicesHost {
|
|
|
95
102
|
private _edgeHttpClient?: EdgeHttpClient = undefined;
|
|
96
103
|
|
|
97
104
|
private _serviceContext!: ServiceContext;
|
|
98
|
-
private readonly
|
|
105
|
+
private readonly _runtime: RuntimeProvider.RuntimeProvider<
|
|
106
|
+
SqlClient.SqlClient | SqlExport.SqlExport | SqlTransaction.SqlTransaction
|
|
107
|
+
>;
|
|
108
|
+
private readonly _runtimeProps: ServiceContextRuntimeProps;
|
|
99
109
|
private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
|
|
100
110
|
|
|
101
111
|
@Trace.info()
|
|
@@ -116,20 +126,14 @@ export class ClientServicesHost {
|
|
|
116
126
|
// TODO(wittjosiah): Turn this on by default.
|
|
117
127
|
lockKey,
|
|
118
128
|
callbacks,
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
runtime,
|
|
130
|
+
runtimeProps,
|
|
131
|
+
}: ClientServicesHostProps) {
|
|
121
132
|
this._storage = storage;
|
|
122
133
|
this._level = level;
|
|
123
134
|
this._callbacks = callbacks;
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
if (this._runtimeParams.disableP2pReplication === undefined) {
|
|
127
|
-
this._runtimeParams.disableP2pReplication = config?.get('runtime.client.disableP2pReplication', false);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (this._runtimeParams.enableVectorIndexing === undefined) {
|
|
131
|
-
this._runtimeParams.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
|
|
132
|
-
}
|
|
135
|
+
this._runtime = runtime;
|
|
136
|
+
this._runtimeProps = runtimeProps ?? {};
|
|
133
137
|
|
|
134
138
|
if (config) {
|
|
135
139
|
this.initialize({ config, transportFactory, signalManager });
|
|
@@ -200,6 +204,30 @@ export class ClientServicesHost {
|
|
|
200
204
|
return this._serviceRegistry.services;
|
|
201
205
|
}
|
|
202
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Debugging util.
|
|
209
|
+
*/
|
|
210
|
+
async exportSqliteDatabase(): Promise<Uint8Array> {
|
|
211
|
+
return await RuntimeProvider.runPromise(this._runtime)(
|
|
212
|
+
Effect.gen(function* () {
|
|
213
|
+
const sql = yield* SqlExport.SqlExport;
|
|
214
|
+
return yield* sql.export;
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Debugging util.
|
|
221
|
+
*/
|
|
222
|
+
async runSqliteQuery(query: string, params?: any[]): Promise<readonly Record<string, unknown>[]> {
|
|
223
|
+
return await RuntimeProvider.runPromise(this._runtime)(
|
|
224
|
+
Effect.gen(function* () {
|
|
225
|
+
const sql = yield* SqlClient.SqlClient;
|
|
226
|
+
return yield* sql`${sql.unsafe(query, params)}`;
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
203
231
|
/**
|
|
204
232
|
* Initialize the service host with the config.
|
|
205
233
|
* Config can also be provided in the constructor.
|
|
@@ -210,6 +238,16 @@ export class ClientServicesHost {
|
|
|
210
238
|
log('initializing...');
|
|
211
239
|
|
|
212
240
|
if (config) {
|
|
241
|
+
if (this._runtimeProps.disableP2pReplication === undefined) {
|
|
242
|
+
this._runtimeProps.disableP2pReplication = config?.get('runtime.client.disableP2pReplication', false);
|
|
243
|
+
}
|
|
244
|
+
if (this._runtimeProps.enableVectorIndexing === undefined) {
|
|
245
|
+
this._runtimeProps.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
|
|
246
|
+
}
|
|
247
|
+
if (this._runtimeProps.enableLocalQueues === undefined) {
|
|
248
|
+
this._runtimeProps.enableLocalQueues = config?.get('runtime.client.enableLocalQueues', false);
|
|
249
|
+
}
|
|
250
|
+
|
|
213
251
|
invariant(!this._config, 'config already set');
|
|
214
252
|
this._config = config;
|
|
215
253
|
if (!this._storage) {
|
|
@@ -291,7 +329,8 @@ export class ClientServicesHost {
|
|
|
291
329
|
this._signalManager,
|
|
292
330
|
this._edgeConnection,
|
|
293
331
|
this._edgeHttpClient,
|
|
294
|
-
this.
|
|
332
|
+
this._runtime,
|
|
333
|
+
this._runtimeProps,
|
|
295
334
|
this._config.get('runtime.client.edgeFeatures'),
|
|
296
335
|
);
|
|
297
336
|
|
|
@@ -335,6 +374,7 @@ export class ClientServicesHost {
|
|
|
335
374
|
|
|
336
375
|
DataService: this._serviceContext.echoHost.dataService,
|
|
337
376
|
QueryService: this._serviceContext.echoHost.queryService,
|
|
377
|
+
QueueService: this._serviceContext.echoHost.queuesService,
|
|
338
378
|
|
|
339
379
|
NetworkService: new NetworkServiceImpl(
|
|
340
380
|
this._serviceContext.networkManager,
|
|
@@ -29,6 +29,6 @@ export const extractSpaceArchive = async (archive: SpaceArchive): Promise<Extrac
|
|
|
29
29
|
documents[documentId] = entry.content ?? failedInvariant();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
log
|
|
32
|
+
log('extracted space archive', { metadata, documents });
|
|
33
33
|
return { metadata, documents };
|
|
34
34
|
};
|
|
@@ -9,6 +9,7 @@ import { assertArgument, assertState } from '@dxos/invariant';
|
|
|
9
9
|
import type { IdentityDid, SpaceId } from '@dxos/keys';
|
|
10
10
|
import { SpaceArchiveFileStructure, type SpaceArchiveMetadata, SpaceArchiveVersion } from '@dxos/protocols';
|
|
11
11
|
import type { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
|
|
12
|
+
import { createFilename } from '@dxos/util';
|
|
12
13
|
|
|
13
14
|
export type SpaceArchiveBeginProps = {
|
|
14
15
|
spaceId?: SpaceId;
|
|
@@ -59,6 +60,7 @@ export class SpaceArchiveWriter extends Resource {
|
|
|
59
60
|
async finish(): Promise<SpaceArchive> {
|
|
60
61
|
assertState(this._archive, 'Not open');
|
|
61
62
|
assertState(this._meta, 'Not started');
|
|
63
|
+
assertState(this._meta.spaceId, 'No space ID set');
|
|
62
64
|
assertState(this._currentRootUrl, 'No root URL set');
|
|
63
65
|
|
|
64
66
|
const metadata: SpaceArchiveMetadata = {
|
|
@@ -76,8 +78,7 @@ export class SpaceArchiveWriter extends Resource {
|
|
|
76
78
|
const binary = this._archive.toUint8Array();
|
|
77
79
|
|
|
78
80
|
return {
|
|
79
|
-
|
|
80
|
-
filename: `${new Date().toISOString()}-${this._meta.spaceId}.tar`,
|
|
81
|
+
filename: createFilename({ parts: [this._meta.spaceId], ext: 'tar' }),
|
|
81
82
|
contents: binary,
|
|
82
83
|
};
|
|
83
84
|
}
|