@dxos/client-services 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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-CK3KJB3B.mjs → chunk-KW4WMU5R.mjs} +1310 -3084
- package/dist/lib/browser/chunk-KW4WMU5R.mjs.map +7 -0
- package/dist/lib/browser/{chunk-NQSC7HOE.mjs → chunk-XJRPB3GA.mjs} +1 -1
- package/dist/lib/browser/index.mjs +100 -197
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +1 -6
- package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +2 -2
- package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +1 -1
- package/dist/lib/browser/packlets/locks/browser.mjs +9 -49
- package/dist/lib/browser/packlets/locks/browser.mjs.map +2 -2
- package/dist/lib/browser/packlets/locks/node.mjs +4 -22
- package/dist/lib/browser/packlets/locks/node.mjs.map +2 -2
- package/dist/lib/browser/testing/index.mjs +7 -27
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/node-esm/{chunk-PKEGMOQ4.mjs → chunk-2DT3MZRL.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-WHBWCIEN.mjs → chunk-NDMKP2CH.mjs} +1310 -3084
- package/dist/lib/node-esm/chunk-NDMKP2CH.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +100 -197
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +1 -6
- package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +2 -2
- package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +1 -1
- package/dist/lib/node-esm/packlets/locks/browser.mjs +9 -49
- package/dist/lib/node-esm/packlets/locks/browser.mjs.map +2 -2
- package/dist/lib/node-esm/packlets/locks/node.mjs +4 -22
- package/dist/lib/node-esm/packlets/locks/node.mjs.map +2 -2
- package/dist/lib/node-esm/testing/index.mjs +7 -27
- package/dist/lib/node-esm/testing/index.mjs.map +2 -2
- 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 +2 -1
- package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
- package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/keys.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/metadata.d.ts.map +1 -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/browser-diagnostics-broadcast.d.ts.map +1 -1
- 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 +2 -3
- package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
- 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-service.d.ts +6 -5
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +1 -1
- package/dist/types/src/packlets/invitations/edge-invitation-handler.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 +5 -1
- package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-service.d.ts +3 -3
- package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -1
- package/dist/types/src/packlets/locks/browser.d.ts.map +1 -1
- package/dist/types/src/packlets/locks/node.d.ts.map +1 -1
- package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
- package/dist/types/src/packlets/network/network-service.d.ts +5 -4
- package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/client-rpc-server.d.ts +3 -3
- package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
- package/dist/types/src/packlets/services/feed-syncer.d.ts +1 -1
- package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +1 -2
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +1 -2
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
- package/dist/types/src/packlets/services/util.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/archive-format.d.ts +9 -0
- package/dist/types/src/packlets/space-export/archive-format.d.ts.map +1 -0
- package/dist/types/src/packlets/space-export/index.d.ts +4 -1
- package/dist/types/src/packlets/space-export/index.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +23 -0
- package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -0
- package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts +36 -0
- package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -0
- package/dist/types/src/packlets/space-export/space-archive-reader.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +1 -1
- package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +1 -2
- 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 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +1 -4
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +9 -6
- 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/storage/profile-archive.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/util.d.ts.map +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts +1 -1
- package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/credential-utils.d.ts.map +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.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts +11 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-session.d.ts +0 -2
- package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
- package/dist/types/src/testing/setup.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +39 -46
- package/src/packlets/agents/edge-agent-service.ts +3 -2
- package/src/packlets/diagnostics/diagnostics.ts +1 -2
- package/src/packlets/identity/identity-manager.ts +2 -4
- package/src/packlets/identity/identity-service.ts +12 -9
- package/src/packlets/identity/identity.ts +2 -2
- package/src/packlets/invitations/device-invitation-protocol.ts +3 -1
- package/src/packlets/invitations/edge-invitation-handler.ts +5 -2
- package/src/packlets/invitations/invitation-host-extension.ts +7 -10
- package/src/packlets/invitations/invitation-protocol.ts +5 -1
- package/src/packlets/invitations/invitation-state.ts +1 -15
- package/src/packlets/invitations/invitations-handler.ts +64 -12
- package/src/packlets/invitations/invitations-manager.ts +6 -4
- package/src/packlets/invitations/invitations-service.ts +6 -6
- package/src/packlets/invitations/space-invitation-protocol.ts +3 -3
- package/src/packlets/logging/logging-service.ts +15 -15
- package/src/packlets/network/network-service.ts +9 -8
- package/src/packlets/services/client-rpc-server.ts +15 -12
- package/src/packlets/services/service-context.ts +12 -15
- package/src/packlets/services/service-host.ts +8 -19
- package/src/packlets/space-export/archive-format.ts +42 -0
- package/src/packlets/space-export/index.ts +4 -1
- package/src/packlets/space-export/serialized-space-reader.ts +111 -0
- package/src/packlets/space-export/serialized-space-writer.ts +253 -0
- package/src/packlets/space-export/space-archive-writer.ts +2 -1
- package/src/packlets/space-export/space-archive.test.ts +175 -1
- package/src/packlets/spaces/data-space-manager.ts +17 -19
- package/src/packlets/spaces/data-space.ts +9 -7
- package/src/packlets/spaces/edge-feed-replicator.ts +1 -0
- package/src/packlets/spaces/spaces-service.test.ts +9 -4
- package/src/packlets/spaces/spaces-service.ts +91 -16
- package/src/packlets/worker/worker-runtime.ts +38 -4
- package/src/packlets/worker/worker-session.ts +4 -10
- package/src/version.ts +1 -1
- package/dist/lib/browser/chunk-CK3KJB3B.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-WHBWCIEN.mjs.map +0 -7
- /package/dist/lib/browser/{chunk-NQSC7HOE.mjs.map → chunk-XJRPB3GA.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-PKEGMOQ4.mjs.map → chunk-2DT3MZRL.mjs.map} +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type AutomergeUrl } from '@automerge/automerge-repo';
|
|
6
|
+
|
|
7
|
+
import { Context } from '@dxos/context';
|
|
8
|
+
import { type Obj } from '@dxos/echo';
|
|
9
|
+
import { type SerializedFeed, type SerializedSpace } from '@dxos/echo-db';
|
|
10
|
+
import { type EchoHost } from '@dxos/echo-pipeline';
|
|
11
|
+
import { type DatabaseDirectory, type ObjectStructure } from '@dxos/echo-protocol';
|
|
12
|
+
import { assertState, invariant } from '@dxos/invariant';
|
|
13
|
+
import { DXN, type IdentityDid, type SpaceId } from '@dxos/keys';
|
|
14
|
+
import { log } from '@dxos/log';
|
|
15
|
+
import { FeedProtocol } from '@dxos/protocols';
|
|
16
|
+
import { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
|
|
17
|
+
import { createFilename } from '@dxos/util';
|
|
18
|
+
|
|
19
|
+
import { type DataSpace } from '../spaces/data-space';
|
|
20
|
+
|
|
21
|
+
const SERIALIZED_SPACE_VERSION = 1;
|
|
22
|
+
|
|
23
|
+
const FEED_TYPENAME = 'org.dxos.type.feed';
|
|
24
|
+
|
|
25
|
+
const ATTR_ID = 'id';
|
|
26
|
+
const ATTR_TYPE = '@type';
|
|
27
|
+
const ATTR_META = '@meta';
|
|
28
|
+
const ATTR_DELETED = '@deleted';
|
|
29
|
+
const ATTR_PARENT = '@parent';
|
|
30
|
+
const ATTR_RELATION_SOURCE = '@relationSource';
|
|
31
|
+
const ATTR_RELATION_TARGET = '@relationTarget';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Canonical order of well-known system fields in a serialized object.
|
|
35
|
+
* All remaining `@*` fields follow in the order they appear on the source
|
|
36
|
+
* object, and finally the data fields are appended in their existing order.
|
|
37
|
+
*/
|
|
38
|
+
const SYSTEM_FIELD_ORDER: readonly string[] = [
|
|
39
|
+
ATTR_ID,
|
|
40
|
+
ATTR_TYPE,
|
|
41
|
+
ATTR_META,
|
|
42
|
+
ATTR_DELETED,
|
|
43
|
+
ATTR_PARENT,
|
|
44
|
+
ATTR_RELATION_SOURCE,
|
|
45
|
+
ATTR_RELATION_TARGET,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reorder the keys of an {@link Obj.JSON} so that system fields appear first
|
|
50
|
+
* (`id`, `@type`, `@meta`, then any other `@*` attributes), followed by data
|
|
51
|
+
* fields. The returned object has identical values to the input — only the key
|
|
52
|
+
* iteration order changes.
|
|
53
|
+
*/
|
|
54
|
+
export const orderObjJsonFields = (obj: Obj.JSON): Obj.JSON => {
|
|
55
|
+
const source = obj as Record<string, unknown>;
|
|
56
|
+
const result: Record<string, unknown> = {};
|
|
57
|
+
for (const key of SYSTEM_FIELD_ORDER) {
|
|
58
|
+
if (key in source) {
|
|
59
|
+
result[key] = source[key];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const key of Object.keys(source)) {
|
|
63
|
+
if (key.startsWith('@') && !(key in result)) {
|
|
64
|
+
result[key] = source[key];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const key of Object.keys(source)) {
|
|
68
|
+
if (!(key in result)) {
|
|
69
|
+
result[key] = source[key];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result as Obj.JSON;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type WriteSerializedSpaceArchiveOptions = {
|
|
76
|
+
space: DataSpace;
|
|
77
|
+
echoHost: EchoHost;
|
|
78
|
+
exportedBy?: IdentityDid;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Write a JSON space archive from a live {@link DataSpace}.
|
|
83
|
+
*
|
|
84
|
+
* This runs entirely inside the worker and walks automerge documents directly —
|
|
85
|
+
* it does not require a client-side {@link EchoDatabase}. The output conforms to
|
|
86
|
+
* {@link SerializedSpace} and is compatible with the JSON format consumed by the
|
|
87
|
+
* importer in {@link readSerializedSpaceArchive}.
|
|
88
|
+
*/
|
|
89
|
+
export const writeSerializedSpaceArchive = async (
|
|
90
|
+
options: WriteSerializedSpaceArchiveOptions,
|
|
91
|
+
): Promise<SpaceArchive> => {
|
|
92
|
+
const { space, echoHost, exportedBy } = options;
|
|
93
|
+
|
|
94
|
+
const rootUrl = space.automergeSpaceState.lastEpoch?.subject.assertion.automergeRoot;
|
|
95
|
+
assertState(rootUrl, 'Space does not have a root URL');
|
|
96
|
+
const databaseRoot = space.databaseRoot;
|
|
97
|
+
assertState(databaseRoot, 'Space database root is not ready');
|
|
98
|
+
|
|
99
|
+
const rootDoc = databaseRoot.doc();
|
|
100
|
+
invariant(rootDoc, 'Space database root document is not loaded');
|
|
101
|
+
|
|
102
|
+
// Collect all object structures across the root doc and any linked docs.
|
|
103
|
+
const objects: Obj.JSON[] = [];
|
|
104
|
+
collectObjectsFromDoc(rootDoc, objects);
|
|
105
|
+
|
|
106
|
+
for (const linkedUrl of databaseRoot.getAllLinkedDocuments()) {
|
|
107
|
+
const handle = await echoHost.loadDoc<DatabaseDirectory>(Context.default(), linkedUrl as AutomergeUrl);
|
|
108
|
+
await handle.whenReady();
|
|
109
|
+
const doc = handle.doc();
|
|
110
|
+
if (!doc) {
|
|
111
|
+
log.warn('linked document did not load; skipping', { url: linkedUrl });
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
collectObjectsFromDoc(doc, objects);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Export queue/feed messages for every Feed object in the space.
|
|
118
|
+
const feeds = await exportFeedData(space, echoHost, objects);
|
|
119
|
+
|
|
120
|
+
const serialized: SerializedSpace = {
|
|
121
|
+
version: SERIALIZED_SPACE_VERSION,
|
|
122
|
+
timestamp: new Date().toISOString(),
|
|
123
|
+
originalSpaceId: space.id,
|
|
124
|
+
exportedBy,
|
|
125
|
+
createdAt: Date.now(),
|
|
126
|
+
objects,
|
|
127
|
+
...(feeds.length > 0 ? { feeds } : {}),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const encoded = new TextEncoder().encode(JSON.stringify(serialized));
|
|
131
|
+
return {
|
|
132
|
+
filename: createFilename({ parts: [space.id], ext: 'dx.json' }),
|
|
133
|
+
contents: encoded,
|
|
134
|
+
format: SpaceArchive.Format.JSON,
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const collectObjectsFromDoc = (doc: DatabaseDirectory, out: Obj.JSON[]): void => {
|
|
139
|
+
const docObjects = doc.objects ?? {};
|
|
140
|
+
for (const [objectId, structure] of Object.entries(docObjects)) {
|
|
141
|
+
out.push(objectStructureToObjJson(objectId, structure));
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Convert an internal {@link ObjectStructure} into an {@link Obj.JSON}.
|
|
147
|
+
*
|
|
148
|
+
* Unlike the equivalent helper used for indexing, this preserves the object's
|
|
149
|
+
* `@meta` section so archives produced by this writer can be round-tripped
|
|
150
|
+
* through {@link Obj.fromJSON}.
|
|
151
|
+
*/
|
|
152
|
+
export const objectStructureToObjJson = (objectId: string, structure: ObjectStructure): Obj.JSON => {
|
|
153
|
+
const result: Record<string, unknown> = {
|
|
154
|
+
[ATTR_ID]: objectId,
|
|
155
|
+
[ATTR_TYPE]: (structure.system?.type?.['/'] ?? '') as any,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (structure.meta) {
|
|
159
|
+
result[ATTR_META] = {
|
|
160
|
+
keys: structure.meta.keys ?? [],
|
|
161
|
+
...(structure.meta.tags ? { tags: structure.meta.tags } : {}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (structure.system?.deleted) {
|
|
165
|
+
result[ATTR_DELETED] = true;
|
|
166
|
+
}
|
|
167
|
+
if (structure.system?.parent) {
|
|
168
|
+
result[ATTR_PARENT] = structure.system.parent['/'];
|
|
169
|
+
}
|
|
170
|
+
if (structure.system?.source) {
|
|
171
|
+
result[ATTR_RELATION_SOURCE] = structure.system.source['/'];
|
|
172
|
+
}
|
|
173
|
+
if (structure.system?.target) {
|
|
174
|
+
result[ATTR_RELATION_TARGET] = structure.system.target['/'];
|
|
175
|
+
}
|
|
176
|
+
Object.assign(result, structure.data);
|
|
177
|
+
|
|
178
|
+
return result as Obj.JSON;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const exportFeedData = async (space: DataSpace, echoHost: EchoHost, objects: Obj.JSON[]): Promise<SerializedFeed[]> => {
|
|
182
|
+
const feeds: SerializedFeed[] = [];
|
|
183
|
+
const spaceId: SpaceId = space.id;
|
|
184
|
+
|
|
185
|
+
for (const obj of objects) {
|
|
186
|
+
if (obj[ATTR_TYPE] == null) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const typeDxn = DXN.tryParse(obj[ATTR_TYPE] as string);
|
|
191
|
+
if (typeDxn?.asTypeDXN()?.type !== FEED_TYPENAME) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const namespace = (obj as any).namespace === 'trace' ? 'trace' : 'data';
|
|
196
|
+
const queueDxn = new DXN(DXN.kind.QUEUE, [namespace, spaceId, obj.id]);
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const messages = await collectQueueMessages(echoHost, queueDxn);
|
|
200
|
+
if (messages.length > 0) {
|
|
201
|
+
feeds.push({
|
|
202
|
+
feedObjectId: obj.id,
|
|
203
|
+
namespace,
|
|
204
|
+
messages,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
log.warn('failed to export feed data', { feedObjectId: obj.id, error: err });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return feeds;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const collectQueueMessages = async (echoHost: EchoHost, queueDxn: DXN): Promise<Obj.JSON[]> => {
|
|
216
|
+
const parts = queueDxn.asQueueDXN();
|
|
217
|
+
invariant(parts, 'Expected a queue DXN');
|
|
218
|
+
|
|
219
|
+
const namespace =
|
|
220
|
+
parts.subspaceTag === 'trace' ? FeedProtocol.WellKnownNamespaces.trace : FeedProtocol.WellKnownNamespaces.data;
|
|
221
|
+
|
|
222
|
+
const messages: Obj.JSON[] = [];
|
|
223
|
+
let cursor: string | undefined;
|
|
224
|
+
while (true) {
|
|
225
|
+
const result = await echoHost.queuesService.queryQueue({
|
|
226
|
+
query: {
|
|
227
|
+
spaceId: parts.spaceId,
|
|
228
|
+
queueIds: [parts.queueId],
|
|
229
|
+
queuesNamespace: namespace,
|
|
230
|
+
after: cursor,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
const batch = (result.objects ?? []).flatMap((encoded): Obj.JSON[] => {
|
|
234
|
+
try {
|
|
235
|
+
return [JSON.parse(encoded) as Obj.JSON];
|
|
236
|
+
} catch (err) {
|
|
237
|
+
log.verbose('queue object JSON parse failed; object ignored', { encoded, error: err });
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
if (batch.length === 0) {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
for (const message of batch) {
|
|
245
|
+
messages.push(orderObjJsonFields(message));
|
|
246
|
+
}
|
|
247
|
+
if (!result.nextCursor || result.nextCursor === cursor) {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
cursor = result.nextCursor;
|
|
251
|
+
}
|
|
252
|
+
return messages;
|
|
253
|
+
};
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type SpaceArchiveMetadata,
|
|
16
16
|
SpaceArchiveVersion,
|
|
17
17
|
} from '@dxos/protocols';
|
|
18
|
-
import
|
|
18
|
+
import { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
|
|
19
19
|
import { createFilename } from '@dxos/util';
|
|
20
20
|
|
|
21
21
|
export type SpaceArchiveBeginProps = {
|
|
@@ -115,6 +115,7 @@ export class SpaceArchiveWriter extends Resource {
|
|
|
115
115
|
return {
|
|
116
116
|
filename: createFilename({ parts: [this._meta.spaceId], ext: 'tar' }),
|
|
117
117
|
contents: binary,
|
|
118
|
+
format: SpaceArchive.Format.BINARY,
|
|
118
119
|
};
|
|
119
120
|
}
|
|
120
121
|
}
|
|
@@ -5,14 +5,19 @@
|
|
|
5
5
|
import type { DocumentId } from '@automerge/automerge-repo';
|
|
6
6
|
import { describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { type SerializedSpace } from '@dxos/echo-db';
|
|
9
|
+
import { ObjectId, SpaceId } from '@dxos/keys';
|
|
9
10
|
import {
|
|
10
11
|
FEED_ARCHIVE_BLOCKS_PER_CHUNK,
|
|
11
12
|
type FeedArchiveBlock,
|
|
12
13
|
SpaceArchiveFileStructure,
|
|
13
14
|
SpaceArchiveVersion,
|
|
14
15
|
} from '@dxos/protocols';
|
|
16
|
+
import { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
|
|
15
17
|
|
|
18
|
+
import { detectSpaceArchiveFormat } from './archive-format';
|
|
19
|
+
import { buildDatabaseDirectoryFromObjects, readSerializedSpaceArchive } from './serialized-space-reader';
|
|
20
|
+
import { objectStructureToObjJson, orderObjJsonFields } from './serialized-space-writer';
|
|
16
21
|
import { extractSpaceArchive } from './space-archive-reader';
|
|
17
22
|
import { SpaceArchiveWriter } from './space-archive-writer';
|
|
18
23
|
|
|
@@ -284,4 +289,173 @@ describe('SpaceArchive', () => {
|
|
|
284
289
|
expect(FEED_ARCHIVE_BLOCKS_PER_CHUNK).toBe(100);
|
|
285
290
|
});
|
|
286
291
|
});
|
|
292
|
+
|
|
293
|
+
describe('detectSpaceArchiveFormat', () => {
|
|
294
|
+
test('detects JSON via .dx.json extension', () => {
|
|
295
|
+
const format = detectSpaceArchiveFormat({ filename: 'space.dx.json', contents: new Uint8Array() });
|
|
296
|
+
expect(format).toBe(SpaceArchive.Format.JSON);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('detects JSON via .json extension', () => {
|
|
300
|
+
const format = detectSpaceArchiveFormat({ filename: 'backup.json', contents: new Uint8Array() });
|
|
301
|
+
expect(format).toBe(SpaceArchive.Format.JSON);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('detects BINARY via .tar extension', () => {
|
|
305
|
+
const format = detectSpaceArchiveFormat({ filename: 'space.tar', contents: new Uint8Array() });
|
|
306
|
+
expect(format).toBe(SpaceArchive.Format.BINARY);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('detects BINARY via .tar.gz extension', () => {
|
|
310
|
+
const format = detectSpaceArchiveFormat({ filename: 'space.tar.gz', contents: new Uint8Array() });
|
|
311
|
+
expect(format).toBe(SpaceArchive.Format.BINARY);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('falls back to JSON via leading { byte', () => {
|
|
315
|
+
const contents = new TextEncoder().encode('{"version":1}');
|
|
316
|
+
const format = detectSpaceArchiveFormat({ filename: 'unknown', contents });
|
|
317
|
+
expect(format).toBe(SpaceArchive.Format.JSON);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('skips leading whitespace before sniffing', () => {
|
|
321
|
+
const contents = new TextEncoder().encode(' \n\t{"version":1}');
|
|
322
|
+
const format = detectSpaceArchiveFormat({ filename: 'unknown', contents });
|
|
323
|
+
expect(format).toBe(SpaceArchive.Format.JSON);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('falls back to BINARY on non-JSON bytes', () => {
|
|
327
|
+
const format = detectSpaceArchiveFormat({
|
|
328
|
+
filename: 'unknown',
|
|
329
|
+
contents: new Uint8Array([0x00, 0x01, 0x02]),
|
|
330
|
+
});
|
|
331
|
+
expect(format).toBe(SpaceArchive.Format.BINARY);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('SerializedSpace reader', () => {
|
|
336
|
+
test('parses a minimal JSON archive', () => {
|
|
337
|
+
const serialized: SerializedSpace = {
|
|
338
|
+
version: 1,
|
|
339
|
+
objects: [],
|
|
340
|
+
};
|
|
341
|
+
const contents = new TextEncoder().encode(JSON.stringify(serialized));
|
|
342
|
+
const archive: SpaceArchive = {
|
|
343
|
+
filename: 'test.dx.json',
|
|
344
|
+
contents,
|
|
345
|
+
format: SpaceArchive.Format.JSON,
|
|
346
|
+
};
|
|
347
|
+
const result = readSerializedSpaceArchive(archive);
|
|
348
|
+
expect(result.version).toBe(1);
|
|
349
|
+
expect(result.objects).toEqual([]);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('rejects archives missing required fields', () => {
|
|
353
|
+
const bogus = new TextEncoder().encode(JSON.stringify({ objects: [] }));
|
|
354
|
+
expect(() =>
|
|
355
|
+
readSerializedSpaceArchive({
|
|
356
|
+
filename: 'bad.json',
|
|
357
|
+
contents: bogus,
|
|
358
|
+
format: SpaceArchive.Format.JSON,
|
|
359
|
+
}),
|
|
360
|
+
).toThrow();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test('buildDatabaseDirectoryFromObjects round-trips data and type info', () => {
|
|
364
|
+
const id = ObjectId.random();
|
|
365
|
+
const objects = [
|
|
366
|
+
{
|
|
367
|
+
id,
|
|
368
|
+
'@type': 'dxn:type:example.Thing',
|
|
369
|
+
'@meta': { keys: [] },
|
|
370
|
+
title: 'hello',
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
const directory = buildDatabaseDirectoryFromObjects(objects as any);
|
|
374
|
+
expect(directory.objects).toBeDefined();
|
|
375
|
+
const structure = directory.objects![id];
|
|
376
|
+
expect(structure).toBeDefined();
|
|
377
|
+
expect(structure.data).toEqual({ title: 'hello' });
|
|
378
|
+
expect(structure.system?.type).toEqual({ '/': 'dxn:type:example.Thing' });
|
|
379
|
+
expect(structure.system?.kind).toBe('object');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test('objectStructureToObjJson emits fields in canonical order', () => {
|
|
383
|
+
const id = ObjectId.random();
|
|
384
|
+
const sourceId = ObjectId.random();
|
|
385
|
+
const targetId = ObjectId.random();
|
|
386
|
+
const parentId = ObjectId.random();
|
|
387
|
+
const obj = objectStructureToObjJson(id, {
|
|
388
|
+
data: { title: 'hello', count: 42 },
|
|
389
|
+
meta: { keys: [] },
|
|
390
|
+
system: {
|
|
391
|
+
type: { '/': 'dxn:type:example.Link' },
|
|
392
|
+
kind: 'relation',
|
|
393
|
+
source: { '/': sourceId },
|
|
394
|
+
target: { '/': targetId },
|
|
395
|
+
parent: { '/': parentId },
|
|
396
|
+
deleted: true,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
expect(Object.keys(obj)).toEqual([
|
|
401
|
+
'id',
|
|
402
|
+
'@type',
|
|
403
|
+
'@meta',
|
|
404
|
+
'@deleted',
|
|
405
|
+
'@parent',
|
|
406
|
+
'@relationSource',
|
|
407
|
+
'@relationTarget',
|
|
408
|
+
'title',
|
|
409
|
+
'count',
|
|
410
|
+
]);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('orderObjJsonFields reorders feed queue messages with id/@type/@meta first', () => {
|
|
414
|
+
const id = ObjectId.random();
|
|
415
|
+
const message = {
|
|
416
|
+
payload: { value: 'x' },
|
|
417
|
+
timestamp: 1000,
|
|
418
|
+
id,
|
|
419
|
+
'@meta': { keys: [] },
|
|
420
|
+
'@type': 'dxn:type:example.Message',
|
|
421
|
+
} as any;
|
|
422
|
+
|
|
423
|
+
const ordered = orderObjJsonFields(message);
|
|
424
|
+
expect(Object.keys(ordered)).toEqual(['id', '@type', '@meta', 'payload', 'timestamp']);
|
|
425
|
+
expect(ordered).toEqual(message);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test('orderObjJsonFields preserves unknown @-prefixed fields between system and data', () => {
|
|
429
|
+
const id = ObjectId.random();
|
|
430
|
+
const obj = {
|
|
431
|
+
data: 1,
|
|
432
|
+
'@custom': 'extension',
|
|
433
|
+
'@type': 'dxn:type:example.Thing',
|
|
434
|
+
id,
|
|
435
|
+
} as any;
|
|
436
|
+
|
|
437
|
+
const ordered = orderObjJsonFields(obj);
|
|
438
|
+
expect(Object.keys(ordered)).toEqual(['id', '@type', '@custom', 'data']);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test('buildDatabaseDirectoryFromObjects flags relations', () => {
|
|
442
|
+
const id = ObjectId.random();
|
|
443
|
+
const sourceId = ObjectId.random();
|
|
444
|
+
const targetId = ObjectId.random();
|
|
445
|
+
const objects = [
|
|
446
|
+
{
|
|
447
|
+
id,
|
|
448
|
+
'@type': 'dxn:type:example.Link',
|
|
449
|
+
'@meta': { keys: [] },
|
|
450
|
+
'@relationSource': sourceId,
|
|
451
|
+
'@relationTarget': targetId,
|
|
452
|
+
},
|
|
453
|
+
];
|
|
454
|
+
const directory = buildDatabaseDirectoryFromObjects(objects as any);
|
|
455
|
+
const structure = directory.objects![id];
|
|
456
|
+
expect(structure.system?.kind).toBe('relation');
|
|
457
|
+
expect(structure.system?.source).toEqual({ '/': sourceId });
|
|
458
|
+
expect(structure.system?.target).toEqual({ '/': targetId });
|
|
459
|
+
});
|
|
460
|
+
});
|
|
287
461
|
});
|
|
@@ -6,7 +6,7 @@ import { type Doc } from '@automerge/automerge';
|
|
|
6
6
|
import { type AutomergeUrl, type DocumentId, interpretAsDocumentId } from '@automerge/automerge-repo';
|
|
7
7
|
|
|
8
8
|
import { Event, synchronized, trackLeaks } from '@dxos/async';
|
|
9
|
-
import {
|
|
9
|
+
import { SpaceProperties } from '@dxos/client-protocol';
|
|
10
10
|
import { Context, LifecycleState, Resource, cancelWithContext } from '@dxos/context';
|
|
11
11
|
import {
|
|
12
12
|
type CredentialSigner,
|
|
@@ -37,7 +37,7 @@ import { assertArgument, assertState, failedInvariant, invariant } from '@dxos/i
|
|
|
37
37
|
import { type Keyring } from '@dxos/keyring';
|
|
38
38
|
import { PublicKey, type SpaceId } from '@dxos/keys';
|
|
39
39
|
import { log } from '@dxos/log';
|
|
40
|
-
import { AlreadyJoinedError
|
|
40
|
+
import { AlreadyJoinedError } from '@dxos/protocols';
|
|
41
41
|
import { Invitation, SpaceState } from '@dxos/protocols/proto/dxos/client/services';
|
|
42
42
|
import { type Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
43
43
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
@@ -138,14 +138,12 @@ export type CreateSpaceOptions = {
|
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
@trackLeaks('open', 'close')
|
|
141
|
-
@trace.resource()
|
|
141
|
+
@trace.resource({ lifecycle: true })
|
|
142
142
|
export class DataSpaceManager extends Resource {
|
|
143
143
|
public readonly updated = new Event();
|
|
144
144
|
|
|
145
145
|
private readonly _spaces = new ComplexMap<PublicKey, DataSpace>(PublicKey.hash);
|
|
146
146
|
|
|
147
|
-
private readonly _instanceId = PublicKey.random().toHex();
|
|
148
|
-
|
|
149
147
|
private readonly _spaceManager: SpaceManager;
|
|
150
148
|
private readonly _metadataStore: MetadataStore;
|
|
151
149
|
private readonly _keyring: Keyring;
|
|
@@ -190,10 +188,7 @@ export class DataSpaceManager extends Resource {
|
|
|
190
188
|
await rootHandle?.whenReady();
|
|
191
189
|
const rootDoc = rootHandle?.doc();
|
|
192
190
|
|
|
193
|
-
const properties =
|
|
194
|
-
rootDoc &&
|
|
195
|
-
(findInlineObjectOfType(rootDoc, Type.getTypename(SpaceProperties)) ??
|
|
196
|
-
findInlineObjectOfType(rootDoc, Type.getTypename(LegacySpaceProperties)));
|
|
191
|
+
const properties = rootDoc && findInlineObjectOfType(rootDoc, Type.getTypename(SpaceProperties));
|
|
197
192
|
|
|
198
193
|
return {
|
|
199
194
|
key: space.key.toHex(),
|
|
@@ -221,16 +216,16 @@ export class DataSpaceManager extends Resource {
|
|
|
221
216
|
}
|
|
222
217
|
|
|
223
218
|
@synchronized
|
|
224
|
-
|
|
219
|
+
@trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
|
|
220
|
+
protected override async _open(ctx: Context): Promise<void> {
|
|
225
221
|
log('open');
|
|
226
|
-
log.trace('dxos.echo.data-space-manager.open', Trace.begin({ id: this._instanceId }));
|
|
227
222
|
log('metadata loaded', { spaces: this._metadataStore.spaces.length });
|
|
228
223
|
|
|
229
224
|
const spacesToActivate: DataSpace[] = [];
|
|
230
225
|
await forEachAsync(this._metadataStore.spaces, async (spaceMetadata) => {
|
|
231
226
|
try {
|
|
232
227
|
log('load space', { spaceMetadata });
|
|
233
|
-
const space = await this._constructSpace(
|
|
228
|
+
const space = await this._constructSpace(ctx, spaceMetadata);
|
|
234
229
|
// Track spaces that were previously active for auto-activation (used in dedicated worker mode).
|
|
235
230
|
if (this._runtimeProps?.autoActivateSpaces && spaceMetadata.state === SpaceState.SPACE_ACTIVE) {
|
|
236
231
|
spacesToActivate.push(space);
|
|
@@ -243,14 +238,12 @@ export class DataSpaceManager extends Resource {
|
|
|
243
238
|
// Auto-activate spaces that were previously active (used in dedicated worker mode after leader changeover).
|
|
244
239
|
for (const space of spacesToActivate) {
|
|
245
240
|
log('auto-activating space', { spaceKey: space.key });
|
|
246
|
-
space.activate(
|
|
241
|
+
space.activate(ctx).catch((err) => {
|
|
247
242
|
log.error('Error auto-activating space', { spaceKey: space.key, err });
|
|
248
243
|
});
|
|
249
244
|
}
|
|
250
245
|
|
|
251
246
|
this.updated.emit();
|
|
252
|
-
|
|
253
|
-
log.trace('dxos.echo.data-space-manager.open', Trace.end({ id: this._instanceId }));
|
|
254
247
|
}
|
|
255
248
|
|
|
256
249
|
@synchronized
|
|
@@ -266,7 +259,7 @@ export class DataSpaceManager extends Resource {
|
|
|
266
259
|
* Creates a new space writing the genesis credentials to the control feed.
|
|
267
260
|
*/
|
|
268
261
|
@synchronized
|
|
269
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
262
|
+
@trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
|
|
270
263
|
async createSpace(ctx: Context, options: CreateSpaceOptions = {}): Promise<DataSpace> {
|
|
271
264
|
assertArgument(
|
|
272
265
|
!!options.rootUrl === !!options.documents,
|
|
@@ -372,7 +365,7 @@ export class DataSpaceManager extends Resource {
|
|
|
372
365
|
*/
|
|
373
366
|
// TODO(burdon): Rename join space.
|
|
374
367
|
@synchronized
|
|
375
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
368
|
+
@trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
|
|
376
369
|
async acceptSpace(ctx: Context, opts: AcceptSpaceOptions): Promise<DataSpace> {
|
|
377
370
|
log('accept space', { opts });
|
|
378
371
|
invariant(this._lifecycleState === LifecycleState.OPEN, 'Not open.');
|
|
@@ -390,7 +383,11 @@ export class DataSpaceManager extends Resource {
|
|
|
390
383
|
const space = await this._constructSpace(ctx, metadata);
|
|
391
384
|
await space.open(ctx);
|
|
392
385
|
await this._metadataStore.addSpace(metadata);
|
|
393
|
-
|
|
386
|
+
// Use DSM lifecycle ctx: the invitation accept flow disposes `ctx` as soon as
|
|
387
|
+
// `acceptSpace` returns (guardedState.complete -> ctx.dispose). Detached data-pipeline
|
|
388
|
+
// initialization must outlive the invitation flow, and its span must be parented to a
|
|
389
|
+
// long-lived context.
|
|
390
|
+
space.initializeDataPipelineAsync(this._ctx);
|
|
394
391
|
|
|
395
392
|
this.updated.emit();
|
|
396
393
|
return space;
|
|
@@ -578,7 +575,8 @@ export class DataSpaceManager extends Resource {
|
|
|
578
575
|
dataSpace.postOpen.append(async () => {
|
|
579
576
|
const setting = dataSpace.getEdgeReplicationSetting();
|
|
580
577
|
if (!setting || setting === EdgeReplicationSetting.ENABLED) {
|
|
581
|
-
|
|
578
|
+
// Use lifecycle ctx: the caller ctx from _constructSpace may be disposed by the time postOpen fires.
|
|
579
|
+
await this._echoEdgeReplicator?.connectToSpace(this._ctx, dataSpace.id);
|
|
582
580
|
} else if (this._echoEdgeReplicator) {
|
|
583
581
|
log('not connecting EchoEdgeReplicator because of EdgeReplicationSetting', { spaceId: dataSpace.id });
|
|
584
582
|
}
|
|
@@ -239,7 +239,7 @@ export class DataSpace {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
@synchronized
|
|
242
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
242
|
+
@trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
|
|
243
243
|
async open(ctx: Context): Promise<void> {
|
|
244
244
|
if (this._state === SpaceState.SPACE_CLOSED) {
|
|
245
245
|
await this._open(ctx);
|
|
@@ -273,7 +273,7 @@ export class DataSpace {
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
@synchronized
|
|
276
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
276
|
+
@trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
|
|
277
277
|
async close(ctx: Context): Promise<void> {
|
|
278
278
|
await this._close(ctx);
|
|
279
279
|
}
|
|
@@ -316,12 +316,14 @@ export class DataSpace {
|
|
|
316
316
|
|
|
317
317
|
/**
|
|
318
318
|
* Initialize the data pipeline in a separate task.
|
|
319
|
+
* @param callerCtx - Trace context from the caller (e.g., activate or createSpace) for span parenting.
|
|
319
320
|
*/
|
|
320
|
-
initializeDataPipelineAsync(): void {
|
|
321
|
+
initializeDataPipelineAsync(callerCtx?: Context): void {
|
|
322
|
+
const traceCtx = callerCtx ?? this._ctx;
|
|
321
323
|
scheduleTask(this._ctx, async () => {
|
|
322
324
|
try {
|
|
323
325
|
this.metrics.pipelineInitBegin = new Date();
|
|
324
|
-
await this.initializeDataPipeline(
|
|
326
|
+
await this.initializeDataPipeline(traceCtx);
|
|
325
327
|
} catch (err) {
|
|
326
328
|
if (err instanceof CancelledError || err instanceof ContextDisposedError) {
|
|
327
329
|
log('data pipeline initialization cancelled', err);
|
|
@@ -339,7 +341,7 @@ export class DataSpace {
|
|
|
339
341
|
});
|
|
340
342
|
}
|
|
341
343
|
|
|
342
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
344
|
+
@trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
|
|
343
345
|
async initializeDataPipeline(ctx: Context): Promise<void> {
|
|
344
346
|
if (this._state !== SpaceState.SPACE_CONTROL_ONLY) {
|
|
345
347
|
throw new SystemError({ message: 'Invalid operation' });
|
|
@@ -395,7 +397,7 @@ export class DataSpace {
|
|
|
395
397
|
await this._callbacks.afterReady?.();
|
|
396
398
|
}
|
|
397
399
|
|
|
398
|
-
@trace.span({ showInBrowserTimeline: true })
|
|
400
|
+
@trace.span({ showInBrowserTimeline: true, op: 'lifecycle' })
|
|
399
401
|
private async _initializeAndReadControlPipeline(ctx: Context): Promise<void> {
|
|
400
402
|
await this._inner.controlPipeline.state.waitUntilReachedTargetTimeframe({
|
|
401
403
|
ctx,
|
|
@@ -600,7 +602,7 @@ export class DataSpace {
|
|
|
600
602
|
|
|
601
603
|
await this._metadataStore.setSpaceState(this.key, SpaceState.SPACE_ACTIVE);
|
|
602
604
|
await this._open(ctx);
|
|
603
|
-
this.initializeDataPipelineAsync();
|
|
605
|
+
this.initializeDataPipelineAsync(ctx);
|
|
604
606
|
}
|
|
605
607
|
|
|
606
608
|
@synchronized
|
|
@@ -290,6 +290,7 @@ export class EdgeFeedReplicator extends Resource {
|
|
|
290
290
|
|
|
291
291
|
private _createConnectionContext(): Context {
|
|
292
292
|
const connectionCtx = new Context({
|
|
293
|
+
parent: this._ctx,
|
|
293
294
|
onError: async (err: any) => {
|
|
294
295
|
if (connectionCtx !== this._connectionCtx) {
|
|
295
296
|
return;
|
|
@@ -21,10 +21,15 @@ describe('SpacesService', () => {
|
|
|
21
21
|
beforeEach(async () => {
|
|
22
22
|
serviceContext = await createServiceContext();
|
|
23
23
|
await serviceContext.open(new Context());
|
|
24
|
-
spacesService = new SpacesServiceImpl(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
spacesService = new SpacesServiceImpl(
|
|
25
|
+
serviceContext.identityManager,
|
|
26
|
+
serviceContext.spaceManager,
|
|
27
|
+
serviceContext.echoHost,
|
|
28
|
+
async () => {
|
|
29
|
+
await serviceContext.initialized.wait();
|
|
30
|
+
return serviceContext.dataSpaceManager!;
|
|
31
|
+
},
|
|
32
|
+
);
|
|
28
33
|
});
|
|
29
34
|
|
|
30
35
|
afterEach(async () => {
|