@dxos/echo-pipeline 0.5.9-main.07b4bad → 0.5.9-main.1c1903d

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 (32) hide show
  1. package/dist/lib/browser/index.mjs +121 -207
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +122 -203
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/automerge/automerge-host.d.ts +1 -8
  8. package/dist/types/src/automerge/automerge-host.d.ts.map +1 -1
  9. package/dist/types/src/automerge/echo-network-adapter.d.ts +6 -0
  10. package/dist/types/src/automerge/echo-network-adapter.d.ts.map +1 -1
  11. package/dist/types/src/automerge/echo-replicator.d.ts +1 -0
  12. package/dist/types/src/automerge/echo-replicator.d.ts.map +1 -1
  13. package/dist/types/src/automerge/index.d.ts +0 -1
  14. package/dist/types/src/automerge/index.d.ts.map +1 -1
  15. package/dist/types/src/automerge/mesh-echo-replicator.d.ts.map +1 -1
  16. package/package.json +33 -33
  17. package/src/automerge/automerge-host.ts +8 -21
  18. package/src/automerge/echo-network-adapter.ts +24 -8
  19. package/src/automerge/echo-replicator.ts +2 -0
  20. package/src/automerge/index.ts +0 -1
  21. package/src/automerge/mesh-echo-replicator.ts +3 -1
  22. package/src/automerge/storage-adapter.test.ts +103 -139
  23. package/src/space/space-protocol.test.ts +2 -0
  24. package/dist/types/src/automerge/automerge-storage-adapter.d.ts +0 -16
  25. package/dist/types/src/automerge/automerge-storage-adapter.d.ts.map +0 -1
  26. package/dist/types/src/automerge/automerge-storage/342/200/223wrapper.d.ts +0 -25
  27. package/dist/types/src/automerge/automerge-storage/342/200/223wrapper.d.ts.map +0 -1
  28. package/dist/types/src/automerge/migrations.d.ts +0 -7
  29. package/dist/types/src/automerge/migrations.d.ts.map +0 -1
  30. package/src/automerge/automerge-storage-adapter.ts +0 -103
  31. package/src/automerge/automerge-storage/342/200/223wrapper.ts +0 -59
  32. package/src/automerge/migrations.ts +0 -42
@@ -4,153 +4,117 @@
4
4
 
5
5
  import { expect } from 'chai';
6
6
 
7
- import { type StorageAdapterInterface } from '@dxos/automerge/automerge-repo';
8
7
  import { randomBytes } from '@dxos/crypto';
9
8
  import { PublicKey } from '@dxos/keys';
10
9
  import { createTestLevel } from '@dxos/kv-store/testing';
11
- import { StorageType, createStorage } from '@dxos/random-access-storage';
12
10
  import { afterTest, describe, test } from '@dxos/test';
13
- import { arrayToBuffer, bufferToArray, type MaybePromise } from '@dxos/util';
11
+ import { arrayToBuffer, bufferToArray } from '@dxos/util';
14
12
 
15
- import { AutomergeStorageAdapter } from './automerge-storage-adapter';
16
13
  import { LevelDBStorageAdapter } from './leveldb-storage-adapter';
17
14
 
18
- const runTests = (
19
- testNamespace: string,
20
- /** Run per test. Expects automatic clean-up with `afterTest`. */
21
- createAdapter: (root?: string) => MaybePromise<{
22
- adapter: StorageAdapterInterface;
23
- /** Would be called automatically with `afterTest`. Exposed for mid-test clean-up. */
24
- close: () => MaybePromise<void>;
25
- }>,
26
- ) => {
27
- describe(testNamespace, () => {
28
- const chunks = [
29
- { key: ['a', 'b', 'c', '1'], data: PublicKey.random().asUint8Array() },
30
- { key: ['a', 'b', 'c', '2'], data: PublicKey.random().asUint8Array() },
31
- { key: ['a', 'b', 'd', '3'], data: PublicKey.random().asUint8Array() },
32
- { key: ['a', 'b', 'd', '4'], data: PublicKey.random().asUint8Array() },
33
- ];
34
-
35
- test('should store and retrieve data', async () => {
36
- const { adapter } = await createAdapter();
37
-
38
- await adapter.save(chunks[0].key, chunks[0].data);
39
- expect(await adapter.load(chunks[0].key)).to.deep.equal(chunks[0].data);
40
- });
41
-
42
- test('loadRange return inputs with correct prefixes', async () => {
43
- const { adapter } = await createAdapter();
44
-
45
- for (const chunk of chunks) {
46
- await adapter.save(chunk.key, chunk.data);
47
- }
48
-
49
- expect((await adapter.loadRange(['a', 'b'])).length).to.equal(4);
50
- expect((await adapter.loadRange(['a', 'b', 'c']))[0]).to.deep.equal(chunks[0]);
51
- expect((await adapter.loadRange(['a', 'b', 'c'])).length).to.equal(2);
52
- });
53
-
54
- test('deletion works', async () => {
55
- const { adapter } = await createAdapter();
56
-
57
- for (const chunk of chunks) {
58
- await adapter.save(chunk.key, chunk.data);
59
- }
60
-
61
- await adapter.remove(['a', 'b', 'c', '1']);
62
-
63
- expect((await adapter.loadRange(['a', 'b'])).length).to.equal(3);
64
- expect((await adapter.loadRange(['a', 'b', 'c'])).length).to.equal(1);
65
-
66
- await adapter.removeRange(['a', 'b', 'd']);
67
-
68
- expect((await adapter.loadRange(['a', 'b'])).length).to.equal(1);
69
- expect((await adapter.loadRange(['a', 'b']))[0]).to.deep.equal(chunks[1]);
70
- expect(await adapter.load(['a', 'b', 'c', '2'])).to.deep.equal(chunks[1].data);
71
- expect(await adapter.load(['a', 'b', 'd', '3'])).to.be.undefined;
72
- });
73
-
74
- test('loadRange', async () => {
75
- const root = `/tmp/${randomBytes(16).toString('hex')}`;
76
- {
77
- const { adapter, close } = await createAdapter(root);
78
- await adapter.save(['test', '1'], bufferToArray(Buffer.from('one')));
79
- await adapter.save(['test', '2'], bufferToArray(Buffer.from('two')));
80
- await adapter.save(['bar', '1'], bufferToArray(Buffer.from('bar')));
81
- await close();
82
- }
83
-
84
- {
85
- const { adapter } = await createAdapter(root);
86
- const range = await adapter.loadRange(['test']);
87
- expect(range.map((chunk) => arrayToBuffer(chunk.data!).toString())).to.deep.eq(['one', 'two']);
88
- expect(range.map((chunk) => chunk.key)).to.deep.eq([
89
- ['test', '1'],
90
- ['test', '2'],
91
- ]);
92
- }
93
- });
94
-
95
- test('removeRange', async () => {
96
- const root = `/tmp/${randomBytes(16).toString('hex')}`;
97
- {
98
- const { adapter, close } = await createAdapter(root);
99
- await adapter.save(['test', '1'], bufferToArray(Buffer.from('one')));
100
- await adapter.save(['test', '2'], bufferToArray(Buffer.from('two')));
101
- await adapter.save(['bar', '1'], bufferToArray(Buffer.from('bar')));
102
- await close();
103
- }
104
-
105
- {
106
- const { adapter } = await createAdapter(root);
107
- await adapter.removeRange(['test']);
108
- const range = await adapter.loadRange(['test']);
109
- expect(range.map((chunk) => arrayToBuffer(chunk.data!).toString())).to.deep.eq([]);
110
- const range2 = await adapter.loadRange(['bar']);
111
- expect(range2.map((chunk) => arrayToBuffer(chunk.data!).toString())).to.deep.eq(['bar']);
112
- expect(range2.map((chunk) => chunk.key)).to.deep.eq([['bar', '1']]);
113
- }
114
- });
115
- });
116
- };
117
-
118
- /**
119
- * Run tests for AutomergeStorageAdapter.
120
- */
121
- runTests('AutomergeStorageAdapter', (root?: string) => {
122
- const storage = createStorage({ type: root ? StorageType.NODE : StorageType.RAM, root });
123
- const dir = storage.createDirectory('automerge');
124
- const adapter = new AutomergeStorageAdapter(dir);
125
-
126
- const close = async () => {
127
- await adapter.close();
128
- await storage.close();
15
+ describe('LevelDBStorageAdapter', () => {
16
+ const createAdapter = async (root?: string) => {
17
+ const level = createTestLevel(root);
18
+ await level.open();
19
+ const adapter = new LevelDBStorageAdapter({ db: level.sublevel('automerge') });
20
+ await adapter.open?.();
21
+
22
+ const close = async () => {
23
+ await adapter.close();
24
+ await level.close();
25
+ };
26
+ afterTest(close);
27
+ return {
28
+ adapter,
29
+ close,
30
+ };
129
31
  };
130
- afterTest(close);
131
32
 
132
- return {
133
- adapter,
134
- close,
135
- };
136
- });
33
+ const chunks = [
34
+ { key: ['a', 'b', 'c', '1'], data: PublicKey.random().asUint8Array() },
35
+ { key: ['a', 'b', 'c', '2'], data: PublicKey.random().asUint8Array() },
36
+ { key: ['a', 'b', 'd', '3'], data: PublicKey.random().asUint8Array() },
37
+ { key: ['a', 'b', 'd', '4'], data: PublicKey.random().asUint8Array() },
38
+ ];
137
39
 
138
- /**
139
- * Run tests for LevelDBStorageAdapter.
140
- */
141
- runTests('LevelDBStorageAdapter', async (root?: string) => {
142
- const level = createTestLevel(root);
143
- await level.open();
144
- const adapter = new LevelDBStorageAdapter({ db: level.sublevel('automerge') });
145
- await adapter.open?.();
146
-
147
- const close = async () => {
148
- await adapter.close();
149
- await level.close();
150
- };
151
- afterTest(close);
152
- return {
153
- adapter,
154
- close,
155
- };
40
+ test('should store and retrieve data', async () => {
41
+ const { adapter } = await createAdapter();
42
+
43
+ await adapter.save(chunks[0].key, chunks[0].data);
44
+ expect(await adapter.load(chunks[0].key)).to.deep.equal(chunks[0].data);
45
+ });
46
+
47
+ test('loadRange return inputs with correct prefixes', async () => {
48
+ const { adapter } = await createAdapter();
49
+
50
+ for (const chunk of chunks) {
51
+ await adapter.save(chunk.key, chunk.data);
52
+ }
53
+
54
+ expect((await adapter.loadRange(['a', 'b'])).length).to.equal(4);
55
+ expect((await adapter.loadRange(['a', 'b', 'c']))[0]).to.deep.equal(chunks[0]);
56
+ expect((await adapter.loadRange(['a', 'b', 'c'])).length).to.equal(2);
57
+ });
58
+
59
+ test('deletion works', async () => {
60
+ const { adapter } = await createAdapter();
61
+
62
+ for (const chunk of chunks) {
63
+ await adapter.save(chunk.key, chunk.data);
64
+ }
65
+
66
+ await adapter.remove(['a', 'b', 'c', '1']);
67
+
68
+ expect((await adapter.loadRange(['a', 'b'])).length).to.equal(3);
69
+ expect((await adapter.loadRange(['a', 'b', 'c'])).length).to.equal(1);
70
+
71
+ await adapter.removeRange(['a', 'b', 'd']);
72
+
73
+ expect((await adapter.loadRange(['a', 'b'])).length).to.equal(1);
74
+ expect((await adapter.loadRange(['a', 'b']))[0]).to.deep.equal(chunks[1]);
75
+ expect(await adapter.load(['a', 'b', 'c', '2'])).to.deep.equal(chunks[1].data);
76
+ expect(await adapter.load(['a', 'b', 'd', '3'])).to.be.undefined;
77
+ });
78
+
79
+ test('loadRange', async () => {
80
+ const root = `/tmp/${randomBytes(16).toString('hex')}`;
81
+ {
82
+ const { adapter, close } = await createAdapter(root);
83
+ await adapter.save(['test', '1'], bufferToArray(Buffer.from('one')));
84
+ await adapter.save(['test', '2'], bufferToArray(Buffer.from('two')));
85
+ await adapter.save(['bar', '1'], bufferToArray(Buffer.from('bar')));
86
+ await close();
87
+ }
88
+
89
+ {
90
+ const { adapter } = await createAdapter(root);
91
+ const range = await adapter.loadRange(['test']);
92
+ expect(range.map((chunk) => arrayToBuffer(chunk.data!).toString())).to.deep.eq(['one', 'two']);
93
+ expect(range.map((chunk) => chunk.key)).to.deep.eq([
94
+ ['test', '1'],
95
+ ['test', '2'],
96
+ ]);
97
+ }
98
+ });
99
+
100
+ test('removeRange', async () => {
101
+ const root = `/tmp/${randomBytes(16).toString('hex')}`;
102
+ {
103
+ const { adapter, close } = await createAdapter(root);
104
+ await adapter.save(['test', '1'], bufferToArray(Buffer.from('one')));
105
+ await adapter.save(['test', '2'], bufferToArray(Buffer.from('two')));
106
+ await adapter.save(['bar', '1'], bufferToArray(Buffer.from('bar')));
107
+ await close();
108
+ }
109
+
110
+ {
111
+ const { adapter } = await createAdapter(root);
112
+ await adapter.removeRange(['test']);
113
+ const range = await adapter.loadRange(['test']);
114
+ expect(range.map((chunk) => arrayToBuffer(chunk.data!).toString())).to.deep.eq([]);
115
+ const range2 = await adapter.loadRange(['bar']);
116
+ expect(range2.map((chunk) => arrayToBuffer(chunk.data!).toString())).to.deep.eq(['bar']);
117
+ expect(range2.map((chunk) => chunk.key)).to.deep.eq([['bar', '1']]);
118
+ }
119
+ });
156
120
  });
@@ -25,11 +25,13 @@ describe('space/space-protocol', () => {
25
25
  const peer1 = await builder.createPeer();
26
26
  const gossip1 = peer1.createGossip();
27
27
  const presence1 = peer1.createPresence(gossip1);
28
+ await presence1.open();
28
29
  const protocol1 = peer1.createSpaceProtocol(topic, gossip1);
29
30
 
30
31
  const peer2 = await builder.createPeer();
31
32
  const gossip2 = peer2.createGossip();
32
33
  const presence2 = peer2.createPresence(gossip2);
34
+ await presence2.open();
33
35
  const protocol2 = peer2.createSpaceProtocol(topic, gossip2);
34
36
 
35
37
  await protocol1.start();
@@ -1,16 +0,0 @@
1
- import { type Chunk, type StorageKey, type StorageAdapterInterface } from '@dxos/automerge/automerge-repo';
2
- import { type Directory } from '@dxos/random-access-storage';
3
- export declare class AutomergeStorageAdapter implements StorageAdapterInterface {
4
- private readonly _directory;
5
- private _state;
6
- constructor(_directory: Directory);
7
- load(key: StorageKey): Promise<Uint8Array | undefined>;
8
- save(key: StorageKey, data: Uint8Array): Promise<void>;
9
- remove(key: StorageKey): Promise<void>;
10
- loadRange(keyPrefix: StorageKey): Promise<Chunk[]>;
11
- removeRange(keyPrefix: StorageKey): Promise<void>;
12
- close(): Promise<void>;
13
- private _getFilename;
14
- private _getKeyFromFilename;
15
- }
16
- //# sourceMappingURL=automerge-storage-adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"automerge-storage-adapter.d.ts","sourceRoot":"","sources":["../../../../src/automerge/automerge-storage-adapter.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG7D,qBAAa,uBAAwB,YAAW,uBAAuB;IAKzD,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,MAAM,CAAiC;gBAElB,UAAU,EAAE,SAAS;IAE5C,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IActD,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtD,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAUtC,SAAS,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAqBlD,WAAW,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,mBAAmB;CAG5B"}
@@ -1,25 +0,0 @@
1
- import { type StorageKey, type Chunk, type StorageAdapterInterface } from '@dxos/automerge/automerge-repo';
2
- import { type MaybePromise } from '@dxos/util';
3
- export type StorageCallbacks = {
4
- beforeSave?: (path: string[]) => MaybePromise<void>;
5
- afterSave?: (path: string[]) => MaybePromise<void>;
6
- };
7
- export type AutomergeStorageWrapperParams = {
8
- storage: StorageAdapterInterface;
9
- callbacks: StorageCallbacks;
10
- };
11
- /**
12
- * Wrapper for automerge storage that adds callback on save.
13
- */
14
- export declare class AutomergeStorageWrapper implements StorageAdapterInterface {
15
- private readonly _storage;
16
- private readonly _callbacks;
17
- constructor({ storage, callbacks }: AutomergeStorageWrapperParams);
18
- load(key: StorageKey): Promise<Uint8Array | undefined>;
19
- save(key: StorageKey, value: Uint8Array): Promise<void>;
20
- remove(key: StorageKey): Promise<void>;
21
- loadRange(keyPrefix: StorageKey): Promise<Chunk[]>;
22
- removeRange(keyPrefix: StorageKey): Promise<void>;
23
- close(): Promise<void>;
24
- }
25
- //# sourceMappingURL=automerge-storage%E2%80%93wrapper.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"automerge-storage–wrapper.d.ts","sourceRoot":"","sources":["../../../../src/automerge/automerge-storage–wrapper.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;IACpD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,uBAAuB,CAAC;IACjC,SAAS,EAAE,gBAAgB,CAAC;CAC7B,CAAC;AAEF;;GAEG;AACH,qBAAa,uBAAwB,YAAW,uBAAuB;IACrE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IACnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;gBAElC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,6BAA6B;IAK3D,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAItD,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvD,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,SAAS,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAIlD,WAAW,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD,KAAK;CAKZ"}
@@ -1,7 +0,0 @@
1
- import { type SublevelDB } from '@dxos/kv-store';
2
- import { type Directory } from '@dxos/random-access-storage';
3
- export declare const levelMigration: ({ db, directory }: {
4
- db: SublevelDB;
5
- directory: Directory;
6
- }) => Promise<void>;
7
- //# sourceMappingURL=migrations.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../../src/automerge/migrations.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAK1E,eAAO,MAAM,cAAc,sBAA6B;IAAE,EAAE,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,SAAS,CAAA;CAAE,kBA4B/F,CAAC"}
@@ -1,103 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
- //
5
- // Copyright 2023 DXOS.org
6
- //
7
-
8
- import { type Chunk, type StorageKey, type StorageAdapterInterface } from '@dxos/automerge/automerge-repo';
9
- import { type Directory } from '@dxos/random-access-storage';
10
- import { arrayToBuffer, bufferToArray } from '@dxos/util';
11
-
12
- export class AutomergeStorageAdapter implements StorageAdapterInterface {
13
- // TODO(mykola): Hack for restricting automerge Repo to access storage if Host is `closed`.
14
- // Automerge Repo do not have any lifetime management.
15
- private _state: 'opened' | 'closed' = 'opened';
16
-
17
- constructor(private readonly _directory: Directory) {}
18
-
19
- async load(key: StorageKey): Promise<Uint8Array | undefined> {
20
- if (this._state !== 'opened') {
21
- return undefined;
22
- }
23
- const filename = this._getFilename(key);
24
- const file = this._directory.getOrCreateFile(filename);
25
- const { size } = await file.stat();
26
- if (!size || size === 0) {
27
- return undefined;
28
- }
29
- const buffer = await file.read(0, size);
30
- return bufferToArray(buffer);
31
- }
32
-
33
- async save(key: StorageKey, data: Uint8Array): Promise<void> {
34
- if (this._state !== 'opened') {
35
- return undefined;
36
- }
37
- const filename = this._getFilename(key);
38
- const file = this._directory.getOrCreateFile(filename);
39
- await file.write(0, arrayToBuffer(data));
40
- await file.truncate?.(data.length);
41
-
42
- await file.flush?.();
43
- }
44
-
45
- async remove(key: StorageKey): Promise<void> {
46
- if (this._state !== 'opened') {
47
- return undefined;
48
- }
49
- // TODO(dmaretskyi): Better deletion.
50
- const filename = this._getFilename(key);
51
- const file = this._directory.getOrCreateFile(filename);
52
- await file.destroy();
53
- }
54
-
55
- async loadRange(keyPrefix: StorageKey): Promise<Chunk[]> {
56
- if (this._state !== 'opened') {
57
- return [];
58
- }
59
- const filename = this._getFilename(keyPrefix);
60
- const entries = await this._directory.list();
61
- return Promise.all(
62
- entries
63
- .filter((entry) => entry.startsWith(filename))
64
- .map(async (entry): Promise<Chunk> => {
65
- const file = this._directory.getOrCreateFile(entry);
66
- const { size } = await file.stat();
67
- const buffer = await file.read(0, size);
68
- return {
69
- key: this._getKeyFromFilename(entry),
70
- data: bufferToArray(buffer),
71
- };
72
- }),
73
- );
74
- }
75
-
76
- async removeRange(keyPrefix: StorageKey): Promise<void> {
77
- if (this._state !== 'opened') {
78
- return undefined;
79
- }
80
- const filename = this._getFilename(keyPrefix);
81
- const entries = await this._directory.list();
82
- await Promise.all(
83
- entries
84
- .filter((entry) => entry.startsWith(filename))
85
- .map(async (entry): Promise<void> => {
86
- const file = this._directory.getOrCreateFile(entry);
87
- await file.destroy();
88
- }),
89
- );
90
- }
91
-
92
- async close(): Promise<void> {
93
- this._state = 'closed';
94
- }
95
-
96
- private _getFilename(key: StorageKey): string {
97
- return key.map((k) => k.replaceAll('%', '%25').replaceAll('-', '%2D')).join('-');
98
- }
99
-
100
- private _getKeyFromFilename(filename: string): StorageKey {
101
- return filename.split('-').map((k) => k.replaceAll('%2D', '-').replaceAll('%25', '%'));
102
- }
103
- }
@@ -1,59 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type StorageKey, type Chunk, type StorageAdapterInterface } from '@dxos/automerge/automerge-repo';
6
- import { type MaybePromise } from '@dxos/util';
7
-
8
- import { AutomergeStorageAdapter } from './automerge-storage-adapter';
9
-
10
- export type StorageCallbacks = {
11
- beforeSave?: (path: string[]) => MaybePromise<void>;
12
- afterSave?: (path: string[]) => MaybePromise<void>;
13
- };
14
-
15
- export type AutomergeStorageWrapperParams = {
16
- storage: StorageAdapterInterface;
17
- callbacks: StorageCallbacks;
18
- };
19
-
20
- /**
21
- * Wrapper for automerge storage that adds callback on save.
22
- */
23
- export class AutomergeStorageWrapper implements StorageAdapterInterface {
24
- private readonly _storage: StorageAdapterInterface;
25
- private readonly _callbacks: StorageCallbacks;
26
-
27
- constructor({ storage, callbacks }: AutomergeStorageWrapperParams) {
28
- this._storage = storage;
29
- this._callbacks = callbacks;
30
- }
31
-
32
- async load(key: StorageKey): Promise<Uint8Array | undefined> {
33
- return this._storage.load(key);
34
- }
35
-
36
- async save(key: StorageKey, value: Uint8Array): Promise<void> {
37
- await this._callbacks.beforeSave?.(key);
38
- await this._storage.save(key, value);
39
- await this._callbacks.afterSave?.(key);
40
- }
41
-
42
- async remove(key: StorageKey): Promise<void> {
43
- return this._storage.remove(key);
44
- }
45
-
46
- async loadRange(keyPrefix: StorageKey): Promise<Chunk[]> {
47
- return this._storage.loadRange(keyPrefix);
48
- }
49
-
50
- async removeRange(keyPrefix: StorageKey): Promise<void> {
51
- return this._storage.removeRange(keyPrefix);
52
- }
53
-
54
- async close() {
55
- if (this._storage instanceof AutomergeStorageAdapter) {
56
- return this._storage.close();
57
- }
58
- }
59
- }
@@ -1,42 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type StorageKey } from '@dxos/automerge/automerge-repo';
6
- import { IndexedDBStorageAdapter } from '@dxos/automerge/automerge-repo-storage-indexeddb';
7
- import { type SublevelDB } from '@dxos/kv-store';
8
- import { log } from '@dxos/log';
9
- import { StorageType, type Directory } from '@dxos/random-access-storage';
10
-
11
- import { AutomergeStorageAdapter } from './automerge-storage-adapter';
12
- import { encodingOptions } from './leveldb-storage-adapter';
13
-
14
- export const levelMigration = async ({ db, directory }: { db: SublevelDB; directory: Directory }) => {
15
- // Note: Make auto-migration from previous storage to leveldb here.
16
- const isNewLevel = !(await db
17
- .iterator<StorageKey, Uint8Array>({
18
- ...encodingOptions,
19
- })
20
- .next());
21
-
22
- if (!isNewLevel) {
23
- return;
24
- }
25
-
26
- const oldStorageAdapter =
27
- directory.type === StorageType.IDB
28
- ? new IndexedDBStorageAdapter(directory.path, 'data')
29
- : new AutomergeStorageAdapter(directory);
30
-
31
- const chunks = await oldStorageAdapter.loadRange([]);
32
- if (chunks.length === 0) {
33
- return;
34
- }
35
-
36
- const batch = db.batch();
37
- log.info('found chunks on old storage adapter', { chunks: chunks.length });
38
- for (const { key, data } of await oldStorageAdapter.loadRange([])) {
39
- data && batch.put<StorageKey, Uint8Array>(key, data, { ...encodingOptions });
40
- }
41
- await batch.write();
42
- };