@dxos/client-services 0.8.4-main.fffef41 → 0.9.0
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/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/browser/{chunk-I2RGLVJF.mjs → chunk-37BKOM5O.mjs} +2870 -3767
- package/dist/lib/browser/chunk-37BKOM5O.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/chunk-XJRPB3GA.mjs +22 -0
- package/dist/lib/browser/chunk-XJRPB3GA.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +576 -139
- 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 +88 -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 +86 -0
- package/dist/lib/browser/packlets/locks/browser.mjs.map +7 -0
- package/dist/lib/browser/packlets/locks/node.mjs +48 -0
- package/dist/lib/browser/packlets/locks/node.mjs.map +7 -0
- package/dist/lib/browser/testing/index.mjs +58 -53
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/chunk-2DT3MZRL.mjs +22 -0
- package/dist/lib/node-esm/chunk-2DT3MZRL.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-QTUURCR4.mjs → chunk-2ONS4DLO.mjs} +2810 -3576
- package/dist/lib/node-esm/chunk-2ONS4DLO.mjs.map +7 -0
- 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/index.mjs +576 -139
- 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 +88 -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 +86 -0
- package/dist/lib/node-esm/packlets/locks/browser.mjs.map +7 -0
- package/dist/lib/node-esm/packlets/locks/node.mjs +48 -0
- package/dist/lib/node-esm/packlets/locks/node.mjs.map +7 -0
- package/dist/lib/node-esm/testing/index.mjs +58 -53
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +3 -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 +7 -3
- package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/keys.d.ts +2 -2
- 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/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 +3 -3
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts +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 +10 -10
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +14 -9
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-service.d.ts +7 -11
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +10 -13
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +7 -6
- 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 +7 -4
- 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 +4 -4
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations-manager.d.ts +5 -5
- 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 +6 -5
- 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/index.d.ts +1 -1
- package/dist/types/src/packlets/locks/index.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 +4 -0
- 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 +5 -5
- package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
- package/dist/types/src/packlets/services/feed-syncer.d.ts +75 -0
- package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
- package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
- package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
- package/dist/types/src/packlets/services/index.d.ts +1 -0
- package/dist/types/src/packlets/services/index.d.ts.map +1 -1
- package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +22 -19
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +20 -13
- 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/sqlite-storage.d.ts +27 -0
- package/dist/types/src/packlets/services/sqlite-storage.d.ts.map +1 -0
- 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 +9 -1
- 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 +7 -1
- package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
- package/dist/types/src/packlets/space-export/space-archive.test.d.ts +2 -0
- package/dist/types/src/packlets/space-export/space-archive.test.d.ts.map +1 -0
- 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 +49 -22
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +38 -13
- 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/epoch-migrations.d.ts +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/genesis.d.ts +4 -3
- package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -9
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +10 -7
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/index.d.ts +1 -0
- package/dist/types/src/packlets/storage/index.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-sqlite.d.ts +24 -0
- package/dist/types/src/packlets/storage/profile-archive-sqlite.d.ts.map +1 -0
- package/dist/types/src/packlets/storage/profile-archive-sqlite.test.d.ts +2 -0
- package/dist/types/src/packlets/storage/profile-archive-sqlite.test.d.ts.map +1 -0
- 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 +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 +20 -22
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts +41 -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 -4
- 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/src/version.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +71 -57
- package/src/index.ts +1 -0
- package/src/packlets/agents/edge-agent-manager.ts +8 -5
- package/src/packlets/agents/edge-agent-service.ts +4 -2
- package/src/packlets/devices/devices-service.test.ts +0 -1
- package/src/packlets/devtools/devtools.ts +28 -7
- package/src/packlets/devtools/feeds.ts +1 -1
- package/src/packlets/devtools/keys.ts +2 -2
- package/src/packlets/devtools/spaces.ts +1 -1
- package/src/packlets/diagnostics/diagnostics.ts +1 -2
- package/src/packlets/diagnostics/index.ts +1 -1
- package/src/packlets/identity/authenticator.ts +3 -3
- package/src/packlets/identity/contacts-service.ts +1 -2
- package/src/packlets/identity/identity-manager.test.ts +6 -6
- package/src/packlets/identity/identity-manager.ts +29 -28
- package/src/packlets/identity/identity-recovery-manager.ts +31 -22
- package/src/packlets/identity/identity-service.test.ts +6 -27
- package/src/packlets/identity/identity-service.ts +17 -83
- package/src/packlets/identity/identity.test.ts +3 -3
- package/src/packlets/identity/identity.ts +12 -35
- package/src/packlets/invitations/device-invitation-protocol.ts +10 -9
- package/src/packlets/invitations/edge-invitation-handler.ts +9 -5
- package/src/packlets/invitations/invitation-guest-extenstion.ts +6 -4
- package/src/packlets/invitations/invitation-host-extension.ts +13 -14
- package/src/packlets/invitations/invitation-protocol.ts +7 -4
- package/src/packlets/invitations/invitation-state.ts +1 -15
- package/src/packlets/invitations/invitations-handler.test.ts +4 -5
- package/src/packlets/invitations/invitations-handler.ts +74 -22
- package/src/packlets/invitations/invitations-manager.ts +42 -17
- package/src/packlets/invitations/invitations-service.ts +9 -9
- package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
- package/src/packlets/invitations/space-invitation-protocol.ts +13 -18
- package/src/packlets/locks/index.ts +1 -1
- package/src/packlets/logging/logging-service.ts +19 -15
- package/src/packlets/network/network-service.test.ts +0 -1
- package/src/packlets/network/network-service.ts +10 -8
- package/src/packlets/services/client-rpc-server.ts +19 -16
- package/src/packlets/services/feed-syncer.test.ts +376 -0
- package/src/packlets/services/feed-syncer.ts +536 -0
- package/src/packlets/services/index.ts +1 -0
- package/src/packlets/services/platform.ts +7 -1
- package/src/packlets/services/service-context.test.ts +3 -2
- package/src/packlets/services/service-context.ts +215 -78
- package/src/packlets/services/service-host.test.ts +8 -10
- package/src/packlets/services/service-host.ts +102 -70
- package/src/packlets/services/service-registry.test.ts +0 -1
- package/src/packlets/services/sqlite-storage.ts +390 -0
- 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 +129 -0
- package/src/packlets/space-export/serialized-space-writer.ts +260 -0
- package/src/packlets/space-export/space-archive-reader.ts +64 -3
- package/src/packlets/space-export/space-archive-writer.ts +41 -4
- package/src/packlets/space-export/space-archive.test.ts +482 -0
- package/src/packlets/spaces/data-space-manager.test.ts +169 -14
- package/src/packlets/spaces/data-space-manager.ts +192 -127
- package/src/packlets/spaces/data-space.ts +89 -43
- package/src/packlets/spaces/edge-feed-replicator.test.ts +2 -2
- package/src/packlets/spaces/edge-feed-replicator.ts +11 -9
- package/src/packlets/spaces/epoch-migrations.ts +7 -6
- package/src/packlets/spaces/genesis.ts +9 -4
- package/src/packlets/spaces/notarization-plugin.test.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.ts +10 -9
- package/src/packlets/spaces/spaces-service.test.ts +18 -11
- package/src/packlets/spaces/spaces-service.ts +130 -24
- package/src/packlets/storage/index.ts +1 -0
- package/src/packlets/storage/profile-archive-sqlite.test.ts +79 -0
- package/src/packlets/storage/profile-archive-sqlite.ts +100 -0
- package/src/packlets/storage/profile-archive.ts +3 -0
- package/src/packlets/storage/storage.ts +4 -4
- package/src/packlets/testing/invitation-utils.ts +10 -6
- package/src/packlets/testing/test-builder.ts +59 -40
- package/src/packlets/worker/worker-runtime.ts +173 -17
- package/src/packlets/worker/worker-session.ts +12 -18
- package/src/version.ts +5 -1
- package/dist/lib/browser/chunk-I2RGLVJF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-QTUURCR4.mjs.map +0 -7
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
- package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
- package/src/packlets/identity/default-space-state-machine.ts +0 -44
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as SqlClient from '@effect/sql/SqlClient';
|
|
6
|
+
import type * as SqlError from '@effect/sql/SqlError';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import type { Callback, FileStat, RandomAccessStorage } from 'random-access-storage';
|
|
9
|
+
|
|
10
|
+
import { RuntimeProvider } from '@dxos/effect';
|
|
11
|
+
import { log } from '@dxos/log';
|
|
12
|
+
import { Directory, type File, StorageType, type Storage, wrapFile } from '@dxos/random-access-storage';
|
|
13
|
+
import { SqlTransaction } from '@dxos/sql-sqlite';
|
|
14
|
+
|
|
15
|
+
// SqlTransaction.SqlTransaction is the Tag class exported from the SqlTransaction namespace.
|
|
16
|
+
type SqlTransactionTag = SqlTransaction.SqlTransaction;
|
|
17
|
+
|
|
18
|
+
export type SqliteStorageOptions = {
|
|
19
|
+
runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Minimal cross-platform EventEmitter needed by the RandomAccessStorage contract. */
|
|
23
|
+
class BaseEventEmitter {
|
|
24
|
+
readonly #events: Record<string, ((...args: unknown[]) => void)[]> = Object.create(null);
|
|
25
|
+
|
|
26
|
+
on(event: string, listener: (...args: unknown[]) => void): this {
|
|
27
|
+
(this.#events[event] ??= []).push(listener);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
off(event: string, listener: (...args: unknown[]) => void): this {
|
|
31
|
+
const list = this.#events[event];
|
|
32
|
+
if (list) {
|
|
33
|
+
const idx = list.indexOf(listener);
|
|
34
|
+
if (idx !== -1) {
|
|
35
|
+
list.splice(idx, 1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
emit(event: string, ...args: unknown[]): boolean {
|
|
41
|
+
const list = this.#events[event];
|
|
42
|
+
if (!list?.length) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
for (const fn of [...list]) {
|
|
46
|
+
fn(...args);
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
addListener = this.on;
|
|
51
|
+
removeListener = this.off;
|
|
52
|
+
once(event: string, listener: (...args: unknown[]) => void): this {
|
|
53
|
+
const wrapper = (...args: unknown[]) => {
|
|
54
|
+
this.off(event, wrapper);
|
|
55
|
+
listener(...args);
|
|
56
|
+
};
|
|
57
|
+
return this.on(event, wrapper);
|
|
58
|
+
}
|
|
59
|
+
removeAllListeners(event?: string): this {
|
|
60
|
+
if (event) {
|
|
61
|
+
delete this.#events[event];
|
|
62
|
+
} else {
|
|
63
|
+
for (const key of Object.keys(this.#events)) {
|
|
64
|
+
delete this.#events[key];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
listeners(event: string): ((...args: unknown[]) => void)[] {
|
|
70
|
+
return this.#events[event] ?? [];
|
|
71
|
+
}
|
|
72
|
+
rawListeners(event: string): ((...args: unknown[]) => void)[] {
|
|
73
|
+
return this.#events[event] ?? [];
|
|
74
|
+
}
|
|
75
|
+
listenerCount(event: string): number {
|
|
76
|
+
return this.#events[event]?.length ?? 0;
|
|
77
|
+
}
|
|
78
|
+
prependListener(event: string, listener: (...args: unknown[]) => void): this {
|
|
79
|
+
(this.#events[event] ??= []).unshift(listener);
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
prependOnceListener(event: string, listener: (...args: unknown[]) => void): this {
|
|
83
|
+
const wrapper = (...args: unknown[]) => {
|
|
84
|
+
this.off(event, wrapper);
|
|
85
|
+
listener(...args);
|
|
86
|
+
};
|
|
87
|
+
return this.prependListener(event, wrapper);
|
|
88
|
+
}
|
|
89
|
+
eventNames(): string[] {
|
|
90
|
+
return Object.keys(this.#events);
|
|
91
|
+
}
|
|
92
|
+
getMaxListeners(): number {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
setMaxListeners(): this {
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* SQLite-backed random-access file for hypercore storage.
|
|
102
|
+
* Stores each file as a blob in `hypercore_files`. Read-modify-write for writes.
|
|
103
|
+
*/
|
|
104
|
+
class SqliteRandomAccessFile extends BaseEventEmitter implements RandomAccessStorage {
|
|
105
|
+
readonly opened: boolean = false;
|
|
106
|
+
readonly suspended: boolean = false;
|
|
107
|
+
readonly writing: boolean = false;
|
|
108
|
+
readonly readable: boolean = true;
|
|
109
|
+
readonly writable: boolean = true;
|
|
110
|
+
readonly deletable: boolean = true;
|
|
111
|
+
readonly truncatable: boolean = false;
|
|
112
|
+
// Setting statable=false prevents hypercore's statAndReadAll() from computing a negative
|
|
113
|
+
// read-size (stat.size - offset) for new empty files, which causes an infinite async loop.
|
|
114
|
+
readonly statable: boolean = false;
|
|
115
|
+
|
|
116
|
+
#closed = false;
|
|
117
|
+
#unlinked = false;
|
|
118
|
+
#destroyed = false;
|
|
119
|
+
|
|
120
|
+
get closed(): boolean {
|
|
121
|
+
return this.#closed;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get unlinked(): boolean {
|
|
125
|
+
return this.#unlinked;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
get destroyed(): boolean {
|
|
129
|
+
return this.#destroyed;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Filesystem-like metadata for compatibility with wrapFile.
|
|
133
|
+
readonly directory: string;
|
|
134
|
+
readonly filename: string;
|
|
135
|
+
|
|
136
|
+
#buffer: Buffer = Buffer.alloc(0);
|
|
137
|
+
#loaded = false;
|
|
138
|
+
#loading: Promise<void> | null = null;
|
|
139
|
+
|
|
140
|
+
constructor(
|
|
141
|
+
private readonly filePath: string,
|
|
142
|
+
private readonly runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>,
|
|
143
|
+
) {
|
|
144
|
+
super();
|
|
145
|
+
const parts = filePath.split('/');
|
|
146
|
+
this.filename = parts.pop() ?? '';
|
|
147
|
+
this.directory = parts.join('/');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private _ensureLoaded(): Promise<void> {
|
|
151
|
+
if (this.#loaded) {
|
|
152
|
+
return Promise.resolve();
|
|
153
|
+
}
|
|
154
|
+
// If the file was closed (e.g. the storage was torn down during shutdown) before its initial
|
|
155
|
+
// load completed, don't start the DB read. The underlying SQL connection may already be
|
|
156
|
+
// closing, and the read would reject with "database connection is not open" as an unhandled
|
|
157
|
+
// rejection — a background read racing teardown. #buffer already defaults to empty, so reads
|
|
158
|
+
// resolve against an empty file.
|
|
159
|
+
if (this.#closed) {
|
|
160
|
+
return Promise.resolve();
|
|
161
|
+
}
|
|
162
|
+
if (!this.#loading) {
|
|
163
|
+
this.#loading = this._loadFromDb();
|
|
164
|
+
}
|
|
165
|
+
return this.#loading;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async _loadFromDb(): Promise<void> {
|
|
169
|
+
const filePath = this.filePath;
|
|
170
|
+
const rows = await RuntimeProvider.runPromise(this.runtime)(
|
|
171
|
+
Effect.gen(function* () {
|
|
172
|
+
const sql = yield* SqlClient.SqlClient;
|
|
173
|
+
return yield* sql<{ data: Uint8Array }>`SELECT data FROM hypercore_files WHERE path = ${filePath}`;
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
this.#buffer = rows.length > 0 ? Buffer.from(rows[0].data) : Buffer.alloc(0);
|
|
177
|
+
this.#loaded = true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async _saveToDb(): Promise<void> {
|
|
181
|
+
const filePath = this.filePath;
|
|
182
|
+
const data = this.#buffer;
|
|
183
|
+
await RuntimeProvider.runPromise(this.runtime)(
|
|
184
|
+
Effect.gen(function* () {
|
|
185
|
+
const sql = yield* SqlClient.SqlClient;
|
|
186
|
+
yield* sql`INSERT OR REPLACE INTO hypercore_files (path, data) VALUES (${filePath}, ${data})`;
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
write(offset: number, data: Buffer, cb: Callback<any>): void {
|
|
192
|
+
this._ensureLoaded()
|
|
193
|
+
.then(() => {
|
|
194
|
+
const end = offset + data.length;
|
|
195
|
+
if (end > this.#buffer.length) {
|
|
196
|
+
const enlarged = Buffer.alloc(end);
|
|
197
|
+
this.#buffer.copy(enlarged);
|
|
198
|
+
this.#buffer = enlarged;
|
|
199
|
+
}
|
|
200
|
+
data.copy(this.#buffer, offset);
|
|
201
|
+
return this._saveToDb();
|
|
202
|
+
})
|
|
203
|
+
.then(() => cb(null))
|
|
204
|
+
.catch((err) => cb(err));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
read(offset: number, size: number, cb: Callback<Buffer>): void {
|
|
208
|
+
this._ensureLoaded()
|
|
209
|
+
.then(() => {
|
|
210
|
+
if (size === 0) {
|
|
211
|
+
cb(null, Buffer.alloc(0));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (offset + size > this.#buffer.length) {
|
|
215
|
+
cb(new Error(`Could not satisfy length ${size} at offset ${offset} (file size: ${this.#buffer.length})`));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
cb(null, Buffer.from(this.#buffer.subarray(offset, offset + size)));
|
|
219
|
+
})
|
|
220
|
+
.catch((err) => cb(err));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
del(offset: number, size: number, cb: Callback<any>): void {
|
|
224
|
+
this._ensureLoaded()
|
|
225
|
+
.then(() => {
|
|
226
|
+
const end = Math.min(offset + size, this.#buffer.length);
|
|
227
|
+
if (offset < end) {
|
|
228
|
+
this.#buffer.fill(0, offset, end);
|
|
229
|
+
}
|
|
230
|
+
return this._saveToDb();
|
|
231
|
+
})
|
|
232
|
+
.then(() => cb(null))
|
|
233
|
+
.catch((err) => cb(err));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
stat(cb: Callback<FileStat>): void {
|
|
237
|
+
this._ensureLoaded()
|
|
238
|
+
.then(() => {
|
|
239
|
+
cb(null, { size: this.#buffer.length });
|
|
240
|
+
})
|
|
241
|
+
.catch((err) => cb(err));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
close(cb: Callback<Error>): void {
|
|
245
|
+
this.#closed = true;
|
|
246
|
+
// Drain any in-flight initial load before reporting closed, so a read never races a
|
|
247
|
+
// torn-down SQL connection (avoids "database connection is not open" unhandled rejections).
|
|
248
|
+
(this.#loading ?? Promise.resolve()).then(
|
|
249
|
+
() => cb(null),
|
|
250
|
+
() => cb(null),
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
destroy(cb: Callback<Error>): void {
|
|
255
|
+
const filePath = this.filePath;
|
|
256
|
+
RuntimeProvider.runPromise(this.runtime)(
|
|
257
|
+
Effect.gen(function* () {
|
|
258
|
+
const sql = yield* SqlClient.SqlClient;
|
|
259
|
+
yield* sql`DELETE FROM hypercore_files WHERE path = ${filePath}`;
|
|
260
|
+
}),
|
|
261
|
+
)
|
|
262
|
+
.then(() => {
|
|
263
|
+
this.#buffer = Buffer.alloc(0);
|
|
264
|
+
this.#destroyed = true;
|
|
265
|
+
this.#unlinked = true;
|
|
266
|
+
this.#closed = true;
|
|
267
|
+
cb(null);
|
|
268
|
+
})
|
|
269
|
+
.catch((err) => cb(err));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* SQLite-backed Storage implementation for hypercore feeds.
|
|
275
|
+
* Table: hypercore_files(path TEXT PRIMARY KEY, data BLOB NOT NULL DEFAULT x'').
|
|
276
|
+
*/
|
|
277
|
+
export class SqliteStorage implements Storage {
|
|
278
|
+
readonly path: string;
|
|
279
|
+
readonly type = StorageType.NODE;
|
|
280
|
+
|
|
281
|
+
readonly #runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>;
|
|
282
|
+
readonly #files = new Map<string, File>();
|
|
283
|
+
readonly #nativeFiles = new Map<string, SqliteRandomAccessFile>();
|
|
284
|
+
#closed = false;
|
|
285
|
+
|
|
286
|
+
constructor({ runtime }: SqliteStorageOptions, path = '/sqlite-feeds') {
|
|
287
|
+
this.#runtime = runtime;
|
|
288
|
+
this.path = path;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
readonly migrate: Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient | SqlTransactionTag> = Effect.fn(
|
|
292
|
+
'SqliteStorage.migrate',
|
|
293
|
+
)(() =>
|
|
294
|
+
Effect.gen(function* () {
|
|
295
|
+
const sql = yield* SqlClient.SqlClient;
|
|
296
|
+
yield* sql`CREATE TABLE IF NOT EXISTS hypercore_files (
|
|
297
|
+
path TEXT PRIMARY KEY,
|
|
298
|
+
data BLOB NOT NULL DEFAULT x''
|
|
299
|
+
)`;
|
|
300
|
+
log('hypercore_files table ready');
|
|
301
|
+
}).pipe(Effect.withSpan('SqliteStorage.migrate')),
|
|
302
|
+
)();
|
|
303
|
+
|
|
304
|
+
get size(): number {
|
|
305
|
+
return this.#files.size;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
createDirectory(sub = ''): Directory {
|
|
309
|
+
const dirPath = sub ? `${this.path}/${sub}` : this.path;
|
|
310
|
+
const runtime = this.#runtime;
|
|
311
|
+
const files = this.#files;
|
|
312
|
+
const nativeFiles = this.#nativeFiles;
|
|
313
|
+
|
|
314
|
+
const getOrCreateFile = (path: string, filename: string): File => {
|
|
315
|
+
const fullPath = `${path}/${filename}`;
|
|
316
|
+
const existingNative = nativeFiles.get(fullPath);
|
|
317
|
+
if (existingNative && !existingNative.destroyed) {
|
|
318
|
+
return files.get(fullPath)!;
|
|
319
|
+
}
|
|
320
|
+
const native = new SqliteRandomAccessFile(fullPath, runtime);
|
|
321
|
+
const file = wrapFile(native, StorageType.NODE);
|
|
322
|
+
files.set(fullPath, file);
|
|
323
|
+
nativeFiles.set(fullPath, native);
|
|
324
|
+
return file;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const list = async (path: string): Promise<string[]> => {
|
|
328
|
+
const prefix = path.endsWith('/') ? path : path + '/';
|
|
329
|
+
const rows = await RuntimeProvider.runPromise(runtime)(
|
|
330
|
+
Effect.gen(function* () {
|
|
331
|
+
const sql = yield* SqlClient.SqlClient;
|
|
332
|
+
return yield* sql<{
|
|
333
|
+
path: string;
|
|
334
|
+
}>`SELECT path FROM hypercore_files WHERE path = ${path} OR path LIKE ${prefix + '%'}`;
|
|
335
|
+
}),
|
|
336
|
+
);
|
|
337
|
+
return rows.map((row) => row.path.replace(path + '/', '').replace(path, ''));
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const remove = async (): Promise<void> => {
|
|
341
|
+
const prefix = dirPath.endsWith('/') ? dirPath : dirPath + '/';
|
|
342
|
+
await RuntimeProvider.runPromise(runtime)(
|
|
343
|
+
Effect.gen(function* () {
|
|
344
|
+
const sql = yield* SqlClient.SqlClient;
|
|
345
|
+
yield* sql`DELETE FROM hypercore_files WHERE path = ${dirPath} OR path LIKE ${prefix + '%'}`;
|
|
346
|
+
}),
|
|
347
|
+
);
|
|
348
|
+
// Clean up in-memory file cache.
|
|
349
|
+
for (const [filePath] of files) {
|
|
350
|
+
if (filePath === dirPath || filePath.startsWith(prefix)) {
|
|
351
|
+
files.delete(filePath);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
return new Directory({
|
|
357
|
+
type: StorageType.NODE,
|
|
358
|
+
path: dirPath,
|
|
359
|
+
list,
|
|
360
|
+
getOrCreateFile,
|
|
361
|
+
remove,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async reset(): Promise<void> {
|
|
366
|
+
log('SqliteStorage: clearing all hypercore files');
|
|
367
|
+
await RuntimeProvider.runPromise(this.#runtime)(
|
|
368
|
+
Effect.gen(function* () {
|
|
369
|
+
const sql = yield* SqlClient.SqlClient;
|
|
370
|
+
yield* sql`DELETE FROM hypercore_files`;
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
this.#files.clear();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async close(): Promise<void> {
|
|
377
|
+
if (this.#closed) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
this.#closed = true;
|
|
381
|
+
// Close each native file before the SQL connection is torn down. `close()` marks the file
|
|
382
|
+
// closed (so any subsequent read skips the DB) and drains its in-flight initial load —
|
|
383
|
+
// preventing "database connection is not open" unhandled rejections from background reads
|
|
384
|
+
// that race shutdown (e.g. a test client closing while a background sync read is in flight).
|
|
385
|
+
const natives = [...this.#nativeFiles.values()];
|
|
386
|
+
await Promise.all(natives.map((native) => new Promise<void>((resolve) => native.close(() => resolve()))));
|
|
387
|
+
this.#files.clear();
|
|
388
|
+
this.#nativeFiles.clear();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
|
|
6
|
+
|
|
7
|
+
const JSON_EXTENSIONS = ['.dx.json', '.dx.json.gz', '.json'];
|
|
8
|
+
const BINARY_EXTENSIONS = ['.tar', '.tar.gz'];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect the format of a space archive.
|
|
12
|
+
*
|
|
13
|
+
* Uses filename extension as the primary signal, and falls back to sniffing
|
|
14
|
+
* the first bytes of the archive contents to distinguish JSON from tar.
|
|
15
|
+
*/
|
|
16
|
+
export const detectSpaceArchiveFormat = (archive: Pick<SpaceArchive, 'filename' | 'contents'>): SpaceArchive.Format => {
|
|
17
|
+
const filename = archive.filename?.toLowerCase() ?? '';
|
|
18
|
+
if (JSON_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
|
|
19
|
+
return SpaceArchive.Format.JSON;
|
|
20
|
+
}
|
|
21
|
+
if (BINARY_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
|
|
22
|
+
return SpaceArchive.Format.BINARY;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Fall back to byte sniffing: JSON archives start with '{' (0x7B) or whitespace.
|
|
26
|
+
const bytes = archive.contents;
|
|
27
|
+
if (bytes && bytes.length > 0) {
|
|
28
|
+
for (let i = 0; i < Math.min(bytes.length, 16); i++) {
|
|
29
|
+
const byte = bytes[i];
|
|
30
|
+
// Skip whitespace.
|
|
31
|
+
if (byte === 0x20 || byte === 0x09 || byte === 0x0a || byte === 0x0d) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (byte === 0x7b /* '{' */) {
|
|
35
|
+
return SpaceArchive.Format.JSON;
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return SpaceArchive.Format.BINARY;
|
|
42
|
+
};
|
|
@@ -2,5 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export * from './
|
|
5
|
+
export * from './archive-format';
|
|
6
|
+
export * from './serialized-space-reader';
|
|
7
|
+
export * from './serialized-space-writer';
|
|
6
8
|
export * from './space-archive-reader';
|
|
9
|
+
export * from './space-archive-writer';
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Obj, Type } from '@dxos/echo';
|
|
6
|
+
import { type SerializedSpace } from '@dxos/echo-client';
|
|
7
|
+
import { type DatabaseDirectory, type EntityStructure } from '@dxos/echo-protocol';
|
|
8
|
+
import { assertArgument } from '@dxos/invariant';
|
|
9
|
+
import { URI } from '@dxos/keys';
|
|
10
|
+
import { type SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
|
|
11
|
+
|
|
12
|
+
const ATTR_TYPE = '@type';
|
|
13
|
+
const ATTR_META = '@meta';
|
|
14
|
+
const ATTR_DELETED = '@deleted';
|
|
15
|
+
const ATTR_PARENT = '@parent';
|
|
16
|
+
const ATTR_RELATION_SOURCE = '@relationSource';
|
|
17
|
+
const ATTR_RELATION_TARGET = '@relationTarget';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Type URI of the meta-schema (`TypeSchema` / `Type.Type`). Objects with this
|
|
21
|
+
* `@type` represent persisted ECHO type definitions and must be branded
|
|
22
|
+
* `system.kind = 'type'` so `Filter.type(Type.Type)` and `Type.isType` recognize
|
|
23
|
+
* them after import. Anything else defaults to `'object'` (or `'relation'` when
|
|
24
|
+
* source/target attrs are present).
|
|
25
|
+
*/
|
|
26
|
+
const TYPE_KIND_SCHEMA_URI = Type.getURI(Type.Type).toString();
|
|
27
|
+
|
|
28
|
+
const INTERNAL_KEYS: ReadonlySet<string> = new Set([
|
|
29
|
+
'id',
|
|
30
|
+
ATTR_TYPE,
|
|
31
|
+
ATTR_META,
|
|
32
|
+
ATTR_DELETED,
|
|
33
|
+
ATTR_PARENT,
|
|
34
|
+
ATTR_RELATION_SOURCE,
|
|
35
|
+
ATTR_RELATION_TARGET,
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse a JSON space archive.
|
|
40
|
+
*
|
|
41
|
+
* The archive contents are expected to be the UTF-8 encoding of
|
|
42
|
+
* `JSON.stringify(serializedSpace)`.
|
|
43
|
+
*/
|
|
44
|
+
export const readSerializedSpaceArchive = (archive: SpaceArchive): SerializedSpace => {
|
|
45
|
+
const text = new TextDecoder().decode(archive.contents);
|
|
46
|
+
const parsed = JSON.parse(text) as SerializedSpace;
|
|
47
|
+
assertArgument(typeof parsed === 'object' && parsed !== null, 'archive', 'Invalid JSON archive payload');
|
|
48
|
+
assertArgument(typeof parsed.version === 'number', 'archive', 'Missing SerializedSpace.version');
|
|
49
|
+
assertArgument(Array.isArray(parsed.objects), 'archive', 'Missing SerializedSpace.objects');
|
|
50
|
+
return parsed;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Convert an {@link Obj.JSON} back into an internal {@link EntityStructure} suitable
|
|
55
|
+
* for embedding into a {@link DatabaseDirectory}.
|
|
56
|
+
*/
|
|
57
|
+
export const objJsonToObjectStructure = (obj: Obj.JSON): EntityStructure => {
|
|
58
|
+
const data: Record<string, unknown> = {};
|
|
59
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
60
|
+
if (INTERNAL_KEYS.has(key)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
data[key] = value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const system: NonNullable<EntityStructure['system']> = {};
|
|
67
|
+
|
|
68
|
+
const type = obj[ATTR_TYPE];
|
|
69
|
+
if (type) {
|
|
70
|
+
system.type = { '/': URI.make(type) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const parent = (obj as any)[ATTR_PARENT];
|
|
74
|
+
if (typeof parent === 'string') {
|
|
75
|
+
system.parent = { '/': URI.make(parent) };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const relationSource = (obj as any)[ATTR_RELATION_SOURCE];
|
|
79
|
+
const relationTarget = (obj as any)[ATTR_RELATION_TARGET];
|
|
80
|
+
if (typeof relationSource === 'string' || typeof relationTarget === 'string') {
|
|
81
|
+
system.kind = 'relation';
|
|
82
|
+
if (typeof relationSource === 'string') {
|
|
83
|
+
system.source = { '/': URI.make(relationSource) };
|
|
84
|
+
}
|
|
85
|
+
if (typeof relationTarget === 'string') {
|
|
86
|
+
system.target = { '/': URI.make(relationTarget) };
|
|
87
|
+
}
|
|
88
|
+
// TODO(wittjosiah): This is fragile, will break if the type URI changes.
|
|
89
|
+
} else if (type === TYPE_KIND_SCHEMA_URI) {
|
|
90
|
+
system.kind = 'type';
|
|
91
|
+
} else {
|
|
92
|
+
system.kind = 'object';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if ((obj as any)[ATTR_DELETED]) {
|
|
96
|
+
system.deleted = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const meta = (obj as any)[ATTR_META];
|
|
100
|
+
return {
|
|
101
|
+
system,
|
|
102
|
+
meta: {
|
|
103
|
+
keys: meta?.keys ?? [],
|
|
104
|
+
...(meta?.tags ? { tags: meta.tags } : {}),
|
|
105
|
+
// Preserve registry-provenance fields so persisted `Type.Type` entities
|
|
106
|
+
// round-trip with their typename / semver (see the symmetric write in
|
|
107
|
+
// `objectStructureToObjJson`).
|
|
108
|
+
...(meta?.key !== undefined ? { key: meta.key } : {}),
|
|
109
|
+
...(meta?.version !== undefined ? { version: meta.version } : {}),
|
|
110
|
+
},
|
|
111
|
+
data,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Build a new {@link DatabaseDirectory} containing every object from the archive,
|
|
117
|
+
* keyed by object id. The caller is responsible for stamping the `access.spaceKey`
|
|
118
|
+
* and version fields after the document is created.
|
|
119
|
+
*/
|
|
120
|
+
export const buildDatabaseDirectoryFromObjects = (objects: readonly Obj.JSON[]): DatabaseDirectory => {
|
|
121
|
+
const map: Record<string, EntityStructure> = {};
|
|
122
|
+
for (const obj of objects) {
|
|
123
|
+
map[obj.id] = objJsonToObjectStructure(obj);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
objects: map,
|
|
127
|
+
links: {},
|
|
128
|
+
};
|
|
129
|
+
};
|