@dxos/client-services 0.8.4-main.a4bbb77 → 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.
Files changed (229) hide show
  1. package/dist/lib/browser/{chunk-FZDVFED2.mjs → chunk-KW4WMU5R.mjs} +2698 -4599
  2. package/dist/lib/browser/chunk-KW4WMU5R.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-QCWEHHJW.mjs +24 -0
  4. package/dist/lib/browser/chunk-QCWEHHJW.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-XJRPB3GA.mjs +22 -0
  6. package/dist/lib/browser/chunk-XJRPB3GA.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +490 -228
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +88 -0
  11. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
  12. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
  13. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
  14. package/dist/lib/browser/packlets/locks/browser.mjs +86 -0
  15. package/dist/lib/browser/packlets/locks/browser.mjs.map +7 -0
  16. package/dist/lib/browser/packlets/locks/node.mjs +48 -0
  17. package/dist/lib/browser/packlets/locks/node.mjs.map +7 -0
  18. package/dist/lib/browser/testing/index.mjs +60 -90
  19. package/dist/lib/browser/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/chunk-2DT3MZRL.mjs +22 -0
  21. package/dist/lib/node-esm/chunk-2DT3MZRL.mjs.map +7 -0
  22. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +24 -0
  23. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +7 -0
  24. package/dist/lib/node-esm/{chunk-JDTIU3EP.mjs → chunk-NDMKP2CH.mjs} +2621 -4391
  25. package/dist/lib/node-esm/chunk-NDMKP2CH.mjs.map +7 -0
  26. package/dist/lib/node-esm/index.mjs +490 -228
  27. package/dist/lib/node-esm/index.mjs.map +4 -4
  28. package/dist/lib/node-esm/meta.json +1 -1
  29. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +88 -0
  30. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
  31. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
  32. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
  33. package/dist/lib/node-esm/packlets/locks/browser.mjs +86 -0
  34. package/dist/lib/node-esm/packlets/locks/browser.mjs.map +7 -0
  35. package/dist/lib/node-esm/packlets/locks/node.mjs +48 -0
  36. package/dist/lib/node-esm/packlets/locks/node.mjs.map +7 -0
  37. package/dist/lib/node-esm/testing/index.mjs +60 -90
  38. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  39. package/dist/types/src/index.d.ts +1 -0
  40. package/dist/types/src/index.d.ts.map +1 -1
  41. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +3 -2
  42. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
  43. package/dist/types/src/packlets/agents/edge-agent-service.d.ts +2 -1
  44. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  45. package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
  46. package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
  47. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  48. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  49. package/dist/types/src/packlets/devtools/keys.d.ts.map +1 -1
  50. package/dist/types/src/packlets/devtools/metadata.d.ts.map +1 -1
  51. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  52. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  53. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -1
  54. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  55. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
  56. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +2 -3
  57. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  58. package/dist/types/src/packlets/diagnostics/index.d.ts +1 -1
  59. package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -1
  60. package/dist/types/src/packlets/identity/authenticator.d.ts +2 -2
  61. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  62. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  63. package/dist/types/src/packlets/identity/identity-manager.d.ts +6 -6
  64. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  65. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +8 -7
  66. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  67. package/dist/types/src/packlets/identity/identity-service.d.ts +6 -10
  68. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  69. package/dist/types/src/packlets/identity/identity.d.ts +8 -11
  70. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  71. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +6 -5
  72. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  73. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +1 -1
  74. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  75. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  76. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  77. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +7 -4
  78. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  79. package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -1
  80. package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -1
  81. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -4
  82. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  83. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +3 -3
  84. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  85. package/dist/types/src/packlets/invitations/invitations-service.d.ts +3 -3
  86. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  87. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +4 -3
  88. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  89. package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -1
  90. package/dist/types/src/packlets/locks/browser.d.ts.map +1 -1
  91. package/dist/types/src/packlets/locks/index.d.ts +1 -1
  92. package/dist/types/src/packlets/locks/index.d.ts.map +1 -1
  93. package/dist/types/src/packlets/locks/node.d.ts.map +1 -1
  94. package/dist/types/src/packlets/logging/logging-service.d.ts +4 -0
  95. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  96. package/dist/types/src/packlets/network/network-service.d.ts +5 -4
  97. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  98. package/dist/types/src/packlets/services/client-rpc-server.d.ts +5 -5
  99. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  100. package/dist/types/src/packlets/services/feed-syncer.d.ts +59 -0
  101. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
  102. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  103. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  104. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  105. package/dist/types/src/packlets/services/service-context.d.ts +13 -9
  106. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  107. package/dist/types/src/packlets/services/service-host.d.ts +20 -7
  108. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  109. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  110. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  111. package/dist/types/src/packlets/space-export/archive-format.d.ts +9 -0
  112. package/dist/types/src/packlets/space-export/archive-format.d.ts.map +1 -0
  113. package/dist/types/src/packlets/space-export/index.d.ts +4 -1
  114. package/dist/types/src/packlets/space-export/index.d.ts.map +1 -1
  115. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +23 -0
  116. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -0
  117. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts +36 -0
  118. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -0
  119. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts +9 -1
  120. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts.map +1 -1
  121. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +7 -1
  122. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
  123. package/dist/types/src/packlets/space-export/space-archive.test.d.ts +2 -0
  124. package/dist/types/src/packlets/space-export/space-archive.test.d.ts.map +1 -0
  125. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
  126. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +28 -17
  127. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  128. package/dist/types/src/packlets/spaces/data-space.d.ts +26 -9
  129. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  130. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +2 -2
  131. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  132. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  133. package/dist/types/src/packlets/spaces/genesis.d.ts +2 -1
  134. package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
  135. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -9
  136. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  137. package/dist/types/src/packlets/spaces/spaces-service.d.ts +10 -7
  138. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  139. package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
  140. package/dist/types/src/packlets/storage/profile-archive.d.ts.map +1 -1
  141. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  142. package/dist/types/src/packlets/storage/util.d.ts.map +1 -1
  143. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  144. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  145. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -1
  146. package/dist/types/src/packlets/testing/invitation-utils.d.ts +6 -3
  147. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  148. package/dist/types/src/packlets/testing/test-builder.d.ts +6 -5
  149. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  150. package/dist/types/src/packlets/worker/worker-runtime.d.ts +41 -4
  151. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  152. package/dist/types/src/packlets/worker/worker-session.d.ts +2 -4
  153. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  154. package/dist/types/src/testing/setup.d.ts.map +1 -1
  155. package/dist/types/src/version.d.ts +1 -1
  156. package/dist/types/src/version.d.ts.map +1 -1
  157. package/dist/types/tsconfig.tsbuildinfo +1 -1
  158. package/package.json +70 -55
  159. package/src/index.ts +1 -0
  160. package/src/packlets/agents/edge-agent-manager.ts +8 -5
  161. package/src/packlets/agents/edge-agent-service.ts +15 -3
  162. package/src/packlets/devices/devices-service.test.ts +0 -1
  163. package/src/packlets/devices/devices-service.ts +1 -1
  164. package/src/packlets/devtools/devtools.ts +2 -3
  165. package/src/packlets/diagnostics/diagnostics.ts +1 -2
  166. package/src/packlets/diagnostics/index.ts +1 -1
  167. package/src/packlets/identity/authenticator.ts +2 -2
  168. package/src/packlets/identity/contacts-service.ts +0 -1
  169. package/src/packlets/identity/identity-manager.test.ts +5 -5
  170. package/src/packlets/identity/identity-manager.ts +23 -22
  171. package/src/packlets/identity/identity-recovery-manager.ts +22 -18
  172. package/src/packlets/identity/identity-service.test.ts +6 -27
  173. package/src/packlets/identity/identity-service.ts +13 -81
  174. package/src/packlets/identity/identity.test.ts +6 -6
  175. package/src/packlets/identity/identity.ts +11 -34
  176. package/src/packlets/invitations/device-invitation-protocol.ts +8 -7
  177. package/src/packlets/invitations/edge-invitation-handler.ts +9 -5
  178. package/src/packlets/invitations/invitation-guest-extenstion.ts +6 -4
  179. package/src/packlets/invitations/invitation-host-extension.ts +13 -14
  180. package/src/packlets/invitations/invitation-protocol.ts +7 -4
  181. package/src/packlets/invitations/invitation-state.ts +1 -15
  182. package/src/packlets/invitations/invitations-handler.test.ts +4 -5
  183. package/src/packlets/invitations/invitations-handler.ts +74 -22
  184. package/src/packlets/invitations/invitations-manager.ts +40 -15
  185. package/src/packlets/invitations/invitations-service.ts +9 -9
  186. package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
  187. package/src/packlets/invitations/space-invitation-protocol.ts +11 -16
  188. package/src/packlets/locks/index.ts +1 -1
  189. package/src/packlets/logging/logging-service.ts +20 -16
  190. package/src/packlets/network/network-service.test.ts +0 -1
  191. package/src/packlets/network/network-service.ts +10 -8
  192. package/src/packlets/services/client-rpc-server.ts +19 -16
  193. package/src/packlets/services/feed-syncer.test.ts +340 -0
  194. package/src/packlets/services/feed-syncer.ts +337 -0
  195. package/src/packlets/services/platform.ts +7 -1
  196. package/src/packlets/services/service-context.test.ts +3 -2
  197. package/src/packlets/services/service-context.ts +138 -56
  198. package/src/packlets/services/service-host.test.ts +8 -8
  199. package/src/packlets/services/service-host.ts +70 -40
  200. package/src/packlets/services/service-registry.test.ts +0 -1
  201. package/src/packlets/space-export/archive-format.ts +42 -0
  202. package/src/packlets/space-export/index.ts +4 -1
  203. package/src/packlets/space-export/serialized-space-reader.ts +111 -0
  204. package/src/packlets/space-export/serialized-space-writer.ts +253 -0
  205. package/src/packlets/space-export/space-archive-reader.ts +64 -3
  206. package/src/packlets/space-export/space-archive-writer.ts +41 -3
  207. package/src/packlets/space-export/space-archive.test.ts +461 -0
  208. package/src/packlets/spaces/data-space-manager.test.ts +79 -13
  209. package/src/packlets/spaces/data-space-manager.ts +115 -115
  210. package/src/packlets/spaces/data-space.ts +58 -33
  211. package/src/packlets/spaces/edge-feed-replicator.test.ts +2 -2
  212. package/src/packlets/spaces/edge-feed-replicator.ts +12 -10
  213. package/src/packlets/spaces/epoch-migrations.ts +5 -5
  214. package/src/packlets/spaces/genesis.ts +6 -1
  215. package/src/packlets/spaces/notarization-plugin.test.ts +2 -2
  216. package/src/packlets/spaces/notarization-plugin.ts +10 -9
  217. package/src/packlets/spaces/spaces-service.test.ts +18 -11
  218. package/src/packlets/spaces/spaces-service.ts +123 -24
  219. package/src/packlets/storage/storage.ts +4 -4
  220. package/src/packlets/testing/invitation-utils.ts +10 -6
  221. package/src/packlets/testing/test-builder.ts +36 -10
  222. package/src/packlets/worker/worker-runtime.ts +188 -17
  223. package/src/packlets/worker/worker-session.ts +12 -18
  224. package/src/version.ts +1 -1
  225. package/dist/lib/browser/chunk-FZDVFED2.mjs.map +0 -7
  226. package/dist/lib/node-esm/chunk-JDTIU3EP.mjs.map +0 -7
  227. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
  228. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
  229. package/src/packlets/identity/default-space-state-machine.ts +0 -44
@@ -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
+ };
@@ -6,12 +6,26 @@ import type { DocumentId } from '@automerge/automerge-repo';
6
6
 
7
7
  import { assertArgument, failedInvariant, invariant } from '@dxos/invariant';
8
8
  import { log } from '@dxos/log';
9
- import { SpaceArchiveFileStructure, type SpaceArchiveMetadata } from '@dxos/protocols';
9
+ import {
10
+ type FeedArchiveBlock,
11
+ type FeedArchiveMetadata,
12
+ SpaceArchiveFileStructure,
13
+ type SpaceArchiveMetadata,
14
+ } from '@dxos/protocols';
10
15
  import type { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
11
16
 
17
+ /**
18
+ * Extracted feed data from the archive.
19
+ */
20
+ export type ExtractedFeed = {
21
+ metadata: FeedArchiveMetadata;
22
+ blocks: FeedArchiveBlock[];
23
+ };
24
+
12
25
  export type ExtractedSpaceArchive = {
13
26
  metadata: SpaceArchiveMetadata;
14
27
  documents: Record<DocumentId, Uint8Array>;
28
+ feeds: Record<string, ExtractedFeed>;
15
29
  };
16
30
 
17
31
  export const extractSpaceArchive = async (archive: SpaceArchive): Promise<ExtractedSpaceArchive> => {
@@ -29,6 +43,53 @@ export const extractSpaceArchive = async (archive: SpaceArchive): Promise<Extrac
29
43
  documents[documentId] = entry.content ?? failedInvariant();
30
44
  }
31
45
 
32
- log.info('extracted space archive', { metadata, documents });
33
- return { metadata, documents };
46
+ const feeds: Record<string, ExtractedFeed> = {};
47
+ const feedsPrefix = `${SpaceArchiveFileStructure.feeds}/`;
48
+ const feedEntries = entries.filter((entry) => entry.fileName.startsWith(feedsPrefix));
49
+
50
+ const feedMetadataByFeedId = new Map<string, FeedArchiveMetadata>();
51
+ const feedBlocksByFeedId = new Map<string, Map<number, FeedArchiveBlock[]>>();
52
+
53
+ for (const entry of feedEntries) {
54
+ const relativePath = entry.fileName.slice(feedsPrefix.length);
55
+ const pathParts = relativePath.split('/');
56
+ if (pathParts.length !== 2) {
57
+ continue;
58
+ }
59
+
60
+ const [feedId, fileName] = pathParts;
61
+ invariant(feedId, 'Feed ID is required');
62
+ invariant(fileName, 'File name is required');
63
+
64
+ if (fileName === SpaceArchiveFileStructure.feedMetadata) {
65
+ const feedMetadata = JSON.parse(entry.getContentAsText()) as FeedArchiveMetadata;
66
+ feedMetadataByFeedId.set(feedId, feedMetadata);
67
+ } else if (fileName.startsWith(SpaceArchiveFileStructure.feedBlocksPrefix) && fileName.endsWith('.json')) {
68
+ const chunkIndexStr = fileName.slice(SpaceArchiveFileStructure.feedBlocksPrefix.length).replace(/\.json$/, '');
69
+ const chunkIndex = parseInt(chunkIndexStr, 10);
70
+ invariant(!isNaN(chunkIndex), `Invalid chunk index: ${chunkIndexStr}`);
71
+
72
+ const blocks = JSON.parse(entry.getContentAsText()) as FeedArchiveBlock[];
73
+ if (!feedBlocksByFeedId.has(feedId)) {
74
+ feedBlocksByFeedId.set(feedId, new Map());
75
+ }
76
+ feedBlocksByFeedId.get(feedId)!.set(chunkIndex, blocks);
77
+ }
78
+ }
79
+
80
+ for (const [feedId, feedMetadata] of feedMetadataByFeedId) {
81
+ const blockChunks = feedBlocksByFeedId.get(feedId) ?? new Map<number, FeedArchiveBlock[]>();
82
+ const sortedChunkIndices = Array.from(blockChunks.keys()).sort((a, b) => a - b);
83
+ const allBlocks: FeedArchiveBlock[] = [];
84
+ for (const chunkIndex of sortedChunkIndices) {
85
+ allBlocks.push(...blockChunks.get(chunkIndex)!);
86
+ }
87
+ feeds[feedId] = {
88
+ metadata: feedMetadata,
89
+ blocks: allBlocks,
90
+ };
91
+ }
92
+
93
+ log('extracted space archive', { metadata, documents, feedCount: Object.keys(feeds).length });
94
+ return { metadata, documents, feeds };
34
95
  };
@@ -7,8 +7,16 @@ import type * as tar from '@obsidize/tar-browserify';
7
7
  import { type Context, Resource } from '@dxos/context';
8
8
  import { assertArgument, assertState } from '@dxos/invariant';
9
9
  import type { IdentityDid, SpaceId } from '@dxos/keys';
10
- import { SpaceArchiveFileStructure, type SpaceArchiveMetadata, SpaceArchiveVersion } from '@dxos/protocols';
11
- import type { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
10
+ import {
11
+ FEED_ARCHIVE_BLOCKS_PER_CHUNK,
12
+ type FeedArchiveBlock,
13
+ type FeedArchiveMetadata,
14
+ SpaceArchiveFileStructure,
15
+ type SpaceArchiveMetadata,
16
+ SpaceArchiveVersion,
17
+ } from '@dxos/protocols';
18
+ import { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
19
+ import { createFilename } from '@dxos/util';
12
20
 
13
21
  export type SpaceArchiveBeginProps = {
14
22
  spaceId?: SpaceId;
@@ -56,9 +64,38 @@ export class SpaceArchiveWriter extends Resource {
56
64
  this._archive.addBinaryFile(`${SpaceArchiveFileStructure.documents}/${documentId}.bin`, data);
57
65
  }
58
66
 
67
+ /**
68
+ * Writes a feed with its metadata and blocks to the archive.
69
+ * Blocks are written in chunks of {@link FEED_ARCHIVE_BLOCKS_PER_CHUNK}.
70
+ */
71
+ async writeFeed(feedId: string, namespace: string, blocks: FeedArchiveBlock[]): Promise<void> {
72
+ assertArgument(feedId, 'feedId', 'Feed ID is required');
73
+ assertArgument(namespace, 'namespace', 'Namespace is required');
74
+ assertState(this._archive, 'Not open');
75
+
76
+ const feedPath = `${SpaceArchiveFileStructure.feeds}/${feedId}`;
77
+
78
+ const metadata: FeedArchiveMetadata = {
79
+ id: feedId,
80
+ namespace,
81
+ };
82
+ this._archive.addTextFile(`${feedPath}/${SpaceArchiveFileStructure.feedMetadata}`, JSON.stringify(metadata));
83
+
84
+ for (let chunkIndex = 0; chunkIndex * FEED_ARCHIVE_BLOCKS_PER_CHUNK < blocks.length; chunkIndex++) {
85
+ const start = chunkIndex * FEED_ARCHIVE_BLOCKS_PER_CHUNK;
86
+ const end = Math.min(start + FEED_ARCHIVE_BLOCKS_PER_CHUNK, blocks.length);
87
+ const chunk = blocks.slice(start, end);
88
+ this._archive.addTextFile(
89
+ `${feedPath}/${SpaceArchiveFileStructure.feedBlocksPrefix}${chunkIndex}.json`,
90
+ JSON.stringify(chunk),
91
+ );
92
+ }
93
+ }
94
+
59
95
  async finish(): Promise<SpaceArchive> {
60
96
  assertState(this._archive, 'Not open');
61
97
  assertState(this._meta, 'Not started');
98
+ assertState(this._meta.spaceId, 'No space ID set');
62
99
  assertState(this._currentRootUrl, 'No root URL set');
63
100
 
64
101
  const metadata: SpaceArchiveMetadata = {
@@ -76,8 +113,9 @@ export class SpaceArchiveWriter extends Resource {
76
113
  const binary = this._archive.toUint8Array();
77
114
 
78
115
  return {
79
- filename: `${this._meta.spaceId}.tar`,
116
+ filename: createFilename({ parts: [this._meta.spaceId], ext: 'tar' }),
80
117
  contents: binary,
118
+ format: SpaceArchive.Format.BINARY,
81
119
  };
82
120
  }
83
121
  }