@dxos/feed-store 2.33.5-dev.22471d71 → 2.33.5-dev.33d2877e

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.
@@ -20,7 +20,7 @@ describe('FeedDescriptor', () => {
20
20
  beforeEach(async () => {
21
21
  const { publicKey, secretKey } = createKeyPair();
22
22
  fd = new FeedDescriptor({
23
- storage: createStorage('', StorageType.RAM),
23
+ directory: createStorage('', StorageType.RAM).directory('feed'),
24
24
  key: PublicKey.from(publicKey),
25
25
  secretKey,
26
26
  hypercore: defaultHypercore
@@ -41,7 +41,7 @@ describe('FeedDescriptor', () => {
41
41
  // When this behaviour was changed, suddenly `protocol-plugin-replicator` tests started hanging forever on network generation.
42
42
  const { publicKey } = createKeyPair();
43
43
  const key = PublicKey.from(publicKey);
44
- const fd = new FeedDescriptor({ key, storage: createStorage('', StorageType.NODE), hypercore: defaultHypercore });
44
+ const fd = new FeedDescriptor({ key, directory: createStorage('', StorageType.NODE).directory('feed'), hypercore: defaultHypercore });
45
45
  expect(fd.key).toEqual(key);
46
46
  expect(fd.secretKey).toBeUndefined();
47
47
  });
@@ -50,7 +50,7 @@ describe('FeedDescriptor', () => {
50
50
  const { publicKey, secretKey } = createKeyPair();
51
51
 
52
52
  const fd = new FeedDescriptor({
53
- storage: createStorage('', StorageType.RAM),
53
+ directory: createStorage('', StorageType.RAM).directory('feed'),
54
54
  key: PublicKey.from(publicKey),
55
55
  secretKey,
56
56
  valueEncoding: 'json',
@@ -92,7 +92,7 @@ describe('FeedDescriptor', () => {
92
92
  // If we try to close a feed that is opening should wait for the open result.
93
93
  const { publicKey, secretKey } = createKeyPair();
94
94
  const fd2 = new FeedDescriptor({
95
- storage: createStorage('', StorageType.RAM),
95
+ directory: createStorage('', StorageType.RAM).directory('feed'),
96
96
  key: PublicKey.from(publicKey),
97
97
  secretKey,
98
98
  hypercore: defaultHypercore
@@ -108,7 +108,7 @@ describe('FeedDescriptor', () => {
108
108
 
109
109
  const { publicKey, secretKey } = createKeyPair();
110
110
  const fd = new FeedDescriptor({
111
- storage: createStorage(root, StorageType.NODE),
111
+ directory: createStorage(root, StorageType.NODE).directory('feed'),
112
112
  key: PublicKey.from(publicKey),
113
113
  secretKey,
114
114
  valueEncoding: 'utf-8',
@@ -135,7 +135,7 @@ describe('FeedDescriptor', () => {
135
135
  test('on open error should unlock the resource', async () => {
136
136
  const { publicKey, secretKey } = createKeyPair();
137
137
  const fd = new FeedDescriptor({
138
- storage: createStorage('', StorageType.RAM),
138
+ directory: createStorage('', StorageType.RAM).directory('feed'),
139
139
  key: PublicKey.from(publicKey),
140
140
  secretKey,
141
141
  hypercore: () => {
@@ -149,16 +149,16 @@ describe('FeedDescriptor', () => {
149
149
  test('on close error should unlock the resource', async () => {
150
150
  const { publicKey, secretKey } = createKeyPair();
151
151
  const fd = new FeedDescriptor({
152
- storage: createStorage('', StorageType.RAM),
152
+ directory: createStorage('', StorageType.RAM).directory('feed'),
153
153
  key: PublicKey.from(publicKey),
154
154
  secretKey,
155
155
  hypercore: () => ({
156
156
  opened: true,
157
- on () {},
158
- ready (cb: () => void) {
157
+ on: () => {},
158
+ ready: (cb: () => void) => {
159
159
  cb();
160
160
  },
161
- close () {
161
+ close: () => {
162
162
  throw new Error('close error');
163
163
  }
164
164
  } as any)
@@ -5,16 +5,17 @@
5
5
  import assert from 'assert';
6
6
  import defaultHypercore from 'hypercore';
7
7
  import pify from 'pify';
8
+ import { callbackify } from 'util';
8
9
 
9
10
  import { Lock } from '@dxos/async';
10
11
  import { PublicKey } from '@dxos/crypto';
11
- import type { File, Storage } from '@dxos/random-access-multi-storage';
12
+ import type { Directory } from '@dxos/random-access-multi-storage';
12
13
 
13
14
  import type { HypercoreFeed, Hypercore } from './hypercore-types';
14
15
  import type { ValueEncoding } from './types';
15
16
 
16
17
  interface FeedDescriptorOptions {
17
- storage: Storage,
18
+ directory: Directory,
18
19
  key: PublicKey,
19
20
  hypercore: Hypercore,
20
21
  secretKey?: Buffer,
@@ -28,7 +29,7 @@ interface FeedDescriptorOptions {
28
29
  * Abstract handler for an Hypercore instance.
29
30
  */
30
31
  export class FeedDescriptor {
31
- private readonly _storage: Storage;
32
+ private readonly _directory: Directory;
32
33
  private readonly _key: PublicKey;
33
34
  private readonly _secretKey?: Buffer;
34
35
  private readonly _valueEncoding?: ValueEncoding;
@@ -40,7 +41,7 @@ export class FeedDescriptor {
40
41
 
41
42
  constructor (options: FeedDescriptorOptions) {
42
43
  const {
43
- storage,
44
+ directory,
44
45
  key,
45
46
  secretKey,
46
47
  valueEncoding,
@@ -48,7 +49,7 @@ export class FeedDescriptor {
48
49
  disableSigning = false
49
50
  } = options;
50
51
 
51
- this._storage = storage;
52
+ this._directory = directory;
52
53
  this._valueEncoding = valueEncoding;
53
54
  this._hypercore = hypercore;
54
55
  this._key = key;
@@ -119,9 +120,19 @@ export class FeedDescriptor {
119
120
  * Defines the real path where the Hypercore is going
120
121
  * to work with the RandomAccessStorage specified.
121
122
  */
122
- private _createStorage (dir = ''): (name: string) => File {
123
+
124
+ private _createStorage (dir = ''): (name: string) => HypercoreFile {
123
125
  return (name) => {
124
- return this._storage.createOrOpen(`${dir}/${name}`);
126
+ const file = this._directory.createOrOpen(`${dir}/${name}`);
127
+ // Separation between our internal File API and Hypercore's.
128
+ return {
129
+ read: callbackify(file.read.bind(file)),
130
+ write: callbackify(file.write.bind(file)),
131
+ del: callbackify(file.truncate.bind(file)),
132
+ stat: callbackify(file.stat.bind(file)),
133
+ close: callbackify(file.close.bind(file)),
134
+ destroy: callbackify(file.delete.bind(file))
135
+ } as HypercoreFile;
125
136
  };
126
137
  }
127
138
 
@@ -155,3 +166,15 @@ const MOCK_CRYPTO = {
155
166
  cb(null, true);
156
167
  }
157
168
  };
169
+
170
+ /**
171
+ * File API that hypercore uses to read/write from storage.
172
+ */
173
+ interface HypercoreFile {
174
+ read (offset: number, size: number, cb?: (err: Error | null, data?: Buffer) => void): void;
175
+ write (offset: number, data: Buffer, cb?: (err: Error | null) => void): void;
176
+ del (offset: number, size: number, cb?: (err: Error | null) => void): void;
177
+ stat (cb: (err: Error | null, data?: {size: number}) => void): void;
178
+ close (cb?: (err: Error | null) => void): void;
179
+ destroy (cb?: (err: Error | null) => void): void;
180
+ }
@@ -6,7 +6,6 @@
6
6
 
7
7
  import assert from 'assert';
8
8
  import hypercore from 'hypercore';
9
- import hypertrie from 'hypertrie';
10
9
  import pify from 'pify';
11
10
  import tempy from 'tempy';
12
11
 
@@ -26,71 +25,43 @@ interface KeyPair {
26
25
  const feedNames = ['booksFeed', 'usersFeed', 'groupsFeed'];
27
26
 
28
27
  const createFeedStore = (storage: Storage, options = {}) => {
29
- const feedStore = new FeedStore(storage, options);
28
+ const feedStore = new FeedStore(storage.directory('feed'), options);
30
29
  return feedStore;
31
30
  };
32
31
 
33
- async function createDefault () {
32
+ const createDefault = async () => {
34
33
  const directory = tempy.directory();
35
34
 
36
35
  return {
37
36
  directory,
38
37
  feedStore: createFeedStore(createStorage(directory, StorageType.NODE), { valueEncoding: 'utf-8' })
39
38
  };
40
- }
39
+ };
41
40
 
42
- async function defaultFeeds (feedStore: FeedStore, keys: Record<string, KeyPair>) : Promise<Record<string, FeedDescriptor>> {
43
- return Object.fromEntries(await Promise.all(Object.entries<KeyPair>(keys).map(async ([feed, keyPair]) =>
44
- [feed, await feedStore.openReadWriteFeed(keyPair.key, keyPair.secretKey)]
45
- )));
46
- }
41
+ const defaultFeeds = async (feedStore: FeedStore, keys: Record<string, KeyPair>): Promise<Record<string, FeedDescriptor>> => Object.fromEntries(await Promise.all(Object.entries<KeyPair>(keys).map(async ([feed, keyPair]) =>
42
+ [feed, await feedStore.openReadWriteFeed(keyPair.key, keyPair.secretKey)]
43
+ )));
47
44
 
48
- function append (feed: HypercoreFeed, message: any) {
49
- return pify(feed.append.bind(feed))(message);
50
- }
45
+ const append = (feed: HypercoreFeed, message: any) => pify(feed.append.bind(feed))(message);
51
46
 
52
- function head (feed: HypercoreFeed) {
53
- return pify(feed.head.bind(feed))();
54
- }
47
+ const head = (feed: HypercoreFeed) => pify(feed.head.bind(feed))();
55
48
 
56
- const createKeyPairs = () => {
57
- return Object.fromEntries<KeyPair>(feedNames.map(feed => {
58
- const { publicKey, secretKey } = createKeyPair();
59
- return [feed, { key: PublicKey.from(publicKey), secretKey }];
60
- }));
61
- };
49
+ const createKeyPairs = () => Object.fromEntries<KeyPair>(feedNames.map(feed => {
50
+ const { publicKey, secretKey } = createKeyPair();
51
+ return [feed, { key: PublicKey.from(publicKey), secretKey }];
52
+ }));
62
53
 
63
54
  describe('FeedStore', () => {
64
55
  const keys = createKeyPairs();
65
56
 
66
57
  test('Config default', async () => {
67
- const feedStore = await createFeedStore(createStorage('feed', StorageType.RAM));
58
+ const feedStore = await createFeedStore(createStorage('', StorageType.RAM));
68
59
  expect(feedStore).toBeInstanceOf(FeedStore);
69
60
 
70
- const feedStore2 = new FeedStore(createStorage('feed', StorageType.RAM));
61
+ const feedStore2 = new FeedStore(createStorage('', StorageType.RAM).directory('feed'));
71
62
  expect(feedStore2).toBeInstanceOf(FeedStore);
72
63
  });
73
64
 
74
- test('Config default + custom database + custom hypercore', async () => {
75
- const customHypercore = jest.fn((...args) => {
76
- return hypercore(args[0], args[1], args[2]);
77
- });
78
-
79
- const storage = createStorage('', StorageType.RAM);
80
- const database = hypertrie(storage.createOrOpen.bind(storage), { valueEncoding: 'json' });
81
- database.list = jest.fn((_, cb) => cb(null, []));
82
-
83
- const feedStore = createFeedStore(createStorage('feed', StorageType.RAM), {
84
- hypercore: customHypercore
85
- });
86
-
87
- expect(feedStore).toBeInstanceOf(FeedStore);
88
-
89
- await feedStore.openReadOnlyFeed(PublicKey.random());
90
-
91
- expect(customHypercore.mock.calls.length).toBe(1);
92
- });
93
-
94
65
  test('Create feed', async () => {
95
66
  const { feedStore } = await createDefault();
96
67
  const { booksFeed: { feed: booksFeed } } = await defaultFeeds(feedStore, keys);
@@ -161,7 +132,7 @@ describe('FeedStore', () => {
161
132
  });
162
133
 
163
134
  test('Default codec: binary', async () => {
164
- const feedStore = createFeedStore(createStorage('feed', StorageType.RAM));
135
+ const feedStore = createFeedStore(createStorage('', StorageType.RAM));
165
136
  expect(feedStore).toBeInstanceOf(FeedStore);
166
137
 
167
138
  const { publicKey, secretKey } = createKeyPair();
@@ -172,14 +143,14 @@ describe('FeedStore', () => {
172
143
  });
173
144
 
174
145
  test('on close error should unlock the descriptor', async () => {
175
- const feedStore = createFeedStore(createStorage('feed', StorageType.RAM), {
146
+ const feedStore = createFeedStore(createStorage('', StorageType.RAM), {
176
147
  hypercore: () => ({
177
148
  opened: true,
178
- ready (cb: () => void) {
149
+ ready: (cb: () => void) => {
179
150
  cb();
180
151
  },
181
- on () {},
182
- close () {
152
+ on: () => {},
153
+ close: () => {
183
154
  throw new Error('close error');
184
155
  }
185
156
  })
package/src/feed-store.ts CHANGED
@@ -7,7 +7,7 @@ import defaultHypercore from 'hypercore';
7
7
 
8
8
  import { synchronized, Event } from '@dxos/async';
9
9
  import { PublicKey } from '@dxos/crypto';
10
- import { Storage } from '@dxos/random-access-multi-storage';
10
+ import { Directory } from '@dxos/random-access-multi-storage';
11
11
 
12
12
  import FeedDescriptor from './feed-descriptor';
13
13
  import type { Hypercore } from './hypercore-types';
@@ -45,7 +45,7 @@ export interface FeedStoreOptions {
45
45
  * into a persist repository storage.
46
46
  */
47
47
  export class FeedStore {
48
- private _storage: Storage;
48
+ private _directory: Directory;
49
49
  private _valueEncoding: ValueEncoding | undefined;
50
50
  private _hypercore: Hypercore;
51
51
  private _descriptors: Map<string, FeedDescriptor>;
@@ -56,13 +56,13 @@ export class FeedStore {
56
56
  readonly feedOpenedEvent = new Event<FeedDescriptor>();
57
57
 
58
58
  /**
59
- * @param storage RandomAccessStorage to use by default by the feeds.
59
+ * @param directory RandomAccessStorage to use by default by the feeds.
60
60
  * @param options Feedstore options.
61
61
  */
62
- constructor (storage: Storage, options: FeedStoreOptions = {}) {
63
- assert(storage, 'The storage is required.');
62
+ constructor (directory: Directory, options: FeedStoreOptions = {}) {
63
+ assert(directory, 'The storage is required.');
64
64
 
65
- this._storage = storage;
65
+ this._directory = directory;
66
66
 
67
67
  const {
68
68
  valueEncoding,
@@ -79,7 +79,7 @@ export class FeedStore {
79
79
  * @type {RandomAccessStorage}
80
80
  */
81
81
  get storage () {
82
- return this._storage;
82
+ return this._directory;
83
83
  }
84
84
 
85
85
  @synchronized
@@ -114,7 +114,7 @@ export class FeedStore {
114
114
  const { key, secretKey } = options;
115
115
 
116
116
  const descriptor = new FeedDescriptor({
117
- storage: this._storage,
117
+ directory: this._directory,
118
118
  key,
119
119
  secretKey,
120
120
  valueEncoding: this._valueEncoding,
@@ -133,7 +133,7 @@ export class FeedStore {
133
133
  }
134
134
  }
135
135
 
136
- function patchBufferCodec (encoding: ValueEncoding): ValueEncoding {
136
+ const patchBufferCodec = (encoding: ValueEncoding): ValueEncoding => {
137
137
  if (typeof encoding === 'string') {
138
138
  return encoding;
139
139
  }
@@ -141,4 +141,4 @@ function patchBufferCodec (encoding: ValueEncoding): ValueEncoding {
141
141
  encode: (x: any) => Buffer.from(encoding.encode(x)),
142
142
  decode: encoding.decode.bind(encoding)
143
143
  };
144
- }
144
+ };
package/src/stream.ts CHANGED
@@ -20,62 +20,54 @@ const error = debug('dxos:stream:error');
20
20
  * @returns {NodeJS.WritableStream}
21
21
  */
22
22
  // TODO(burdon): Move to @dxos/codec.
23
- export function createWritableFeedStream (feed: HypercoreFeed) {
24
- return new Writable({
25
- objectMode: true,
26
- write (message, _, callback) {
27
- feed.append(message, callback);
28
- }
29
- });
30
- }
23
+ export const createWritableFeedStream = (feed: HypercoreFeed) => new Writable({
24
+ objectMode: true,
25
+ write: (message, _, callback) => {
26
+ feed.append(message, callback);
27
+ }
28
+ });
31
29
 
32
30
  /**
33
31
  * Creates a readStream stream that can be used as a buffer into which messages can be pushed.
34
32
  */
35
- export function createReadable (): Readable {
36
- return new Readable({
37
- objectMode: true,
38
- read () {}
39
- });
40
- }
33
+ export const createReadable = (): Readable => new Readable({
34
+ objectMode: true,
35
+ read: () => {}
36
+ });
41
37
 
42
38
  /**
43
39
  * Creates a writeStream object stream.
44
40
  * @param callback
45
41
  */
46
- export function createWritable<T> (callback: (message: T) => Promise<void>): NodeJS.WritableStream {
47
- return new Writable({
48
- objectMode: true,
49
- write: async (message: T, _, next) => {
50
- try {
51
- await callback(message);
52
- next();
53
- } catch (err: any) {
54
- error(err);
55
- next(err);
56
- }
42
+ export const createWritable = <T>(callback: (message: T) => Promise<void>): NodeJS.WritableStream => new Writable({
43
+ objectMode: true,
44
+ write: async (message: T, _, next) => {
45
+ try {
46
+ await callback(message);
47
+ next();
48
+ } catch (err: any) {
49
+ error(err);
50
+ next(err);
57
51
  }
58
- });
59
- }
52
+ }
53
+ });
60
54
 
61
55
  /**
62
56
  * Creates a transform object stream.
63
57
  * @param callback
64
58
  */
65
- export function createTransform<R, W> (callback: (message: R) => Promise<W | undefined>): Transform {
66
- return new Transform({
67
- objectMode: true,
68
- transform: async (message: R, _, next) => {
69
- try {
70
- const response = await callback(message);
71
- next(null, response);
72
- } catch (err: any) {
73
- error(err);
74
- next(err);
75
- }
59
+ export const createTransform = <R, W>(callback: (message: R) => Promise<W | undefined>): Transform => new Transform({
60
+ objectMode: true,
61
+ transform: async (message: R, _, next) => {
62
+ try {
63
+ const response = await callback(message);
64
+ next(null, response);
65
+ } catch (err: any) {
66
+ error(err);
67
+ next(err);
76
68
  }
77
- });
78
- }
69
+ }
70
+ });
79
71
 
80
72
  /**
81
73
  * Wriable stream that collects objects (e.g., for testing).