@optimystic/db-p2p 0.1.1 → 0.1.3

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 (64) hide show
  1. package/{readme.md → README.md} +7 -0
  2. package/dist/index.min.js +31 -30
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/cluster/cluster-repo.d.ts +27 -0
  5. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  6. package/dist/src/cluster/cluster-repo.js +139 -18
  7. package/dist/src/cluster/cluster-repo.js.map +1 -1
  8. package/dist/src/cluster/service.d.ts +13 -2
  9. package/dist/src/cluster/service.d.ts.map +1 -1
  10. package/dist/src/cluster/service.js +17 -7
  11. package/dist/src/cluster/service.js.map +1 -1
  12. package/dist/src/index.d.ts +1 -1
  13. package/dist/src/index.d.ts.map +1 -1
  14. package/dist/src/index.js +1 -1
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/libp2p-node.d.ts +13 -2
  17. package/dist/src/libp2p-node.d.ts.map +1 -1
  18. package/dist/src/libp2p-node.js +35 -16
  19. package/dist/src/libp2p-node.js.map +1 -1
  20. package/dist/src/protocol-client.d.ts.map +1 -1
  21. package/dist/src/protocol-client.js +8 -7
  22. package/dist/src/protocol-client.js.map +1 -1
  23. package/dist/src/repo/cluster-coordinator.d.ts +7 -2
  24. package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
  25. package/dist/src/repo/cluster-coordinator.js +18 -3
  26. package/dist/src/repo/cluster-coordinator.js.map +1 -1
  27. package/dist/src/repo/coordinator-repo.d.ts +26 -3
  28. package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
  29. package/dist/src/repo/coordinator-repo.js +117 -22
  30. package/dist/src/repo/coordinator-repo.js.map +1 -1
  31. package/dist/src/repo/service.d.ts +13 -2
  32. package/dist/src/repo/service.d.ts.map +1 -1
  33. package/dist/src/repo/service.js +25 -12
  34. package/dist/src/repo/service.js.map +1 -1
  35. package/dist/src/storage/memory-storage.d.ts +15 -0
  36. package/dist/src/storage/memory-storage.d.ts.map +1 -1
  37. package/dist/src/storage/memory-storage.js +23 -4
  38. package/dist/src/storage/memory-storage.js.map +1 -1
  39. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  40. package/dist/src/storage/storage-repo.js.map +1 -1
  41. package/dist/src/sync/service.d.ts.map +1 -1
  42. package/dist/src/sync/service.js +7 -2
  43. package/dist/src/sync/service.js.map +1 -1
  44. package/package.json +27 -21
  45. package/src/cluster/cluster-repo.ts +836 -711
  46. package/src/cluster/service.ts +44 -31
  47. package/src/index.ts +1 -1
  48. package/src/libp2p-key-network.ts +334 -334
  49. package/src/libp2p-node.ts +371 -339
  50. package/src/network/network-manager-service.ts +334 -334
  51. package/src/protocol-client.ts +53 -54
  52. package/src/repo/client.ts +112 -112
  53. package/src/repo/cluster-coordinator.ts +613 -592
  54. package/src/repo/coordinator-repo.ts +269 -137
  55. package/src/repo/service.ts +237 -219
  56. package/src/storage/block-storage.ts +182 -182
  57. package/src/storage/memory-storage.ts +24 -5
  58. package/src/storage/storage-repo.ts +321 -320
  59. package/src/sync/service.ts +7 -6
  60. package/dist/src/storage/file-storage.d.ts +0 -30
  61. package/dist/src/storage/file-storage.d.ts.map +0 -1
  62. package/dist/src/storage/file-storage.js +0 -127
  63. package/dist/src/storage/file-storage.js.map +0 -1
  64. package/src/storage/file-storage.ts +0 -163
@@ -1,54 +1,53 @@
1
- import { pipe } from 'it-pipe';
2
- import { encode as lpEncode, decode as lpDecode } from 'it-length-prefixed';
3
- import { pushable } from 'it-pushable';
4
- import type { PeerId } from '@libp2p/interface';
5
- import type { IPeerNetwork } from '@optimystic/db-core';
6
- import { first } from './it-utility.js';
7
-
8
- /** Base class for clients that communicate via a libp2p protocol */
9
- export class ProtocolClient {
10
- constructor(
11
- protected readonly peerId: PeerId,
12
- protected readonly peerNetwork: IPeerNetwork,
13
- ) { }
14
-
15
- protected async processMessage<T>(
16
- message: unknown,
17
- protocol: string,
18
- options?: { signal?: AbortSignal }
19
- ): Promise<T> {
20
- const stream = await this.peerNetwork.connect(
21
- this.peerId,
22
- protocol,
23
- { signal: options?.signal }
24
- );
25
-
26
- try {
27
- const source = pipe(
28
- stream.source,
29
- lpDecode,
30
- async function* (source) {
31
- for await (const data of source) {
32
- const decoded = new TextDecoder().decode(data.subarray());
33
- const parsed = JSON.parse(decoded);
34
- yield parsed;
35
- }
36
- }
37
- ) as AsyncIterable<T>;
38
-
39
- const sink = pushable();
40
- void pipe(
41
- sink,
42
- lpEncode,
43
- stream.sink
44
- );
45
-
46
- sink.push(new TextEncoder().encode(JSON.stringify(message)));
47
- sink.end();
48
-
49
- return await first(() => source, () => { throw new Error('No response received') });
50
- } finally {
51
- stream.close();
52
- }
53
- }
54
- }
1
+ import { pipe } from 'it-pipe';
2
+ import { encode as lpEncode, decode as lpDecode } from 'it-length-prefixed';
3
+ import type { PeerId } from '@libp2p/interface';
4
+ import type { IPeerNetwork } from '@optimystic/db-core';
5
+ import { first } from './it-utility.js';
6
+
7
+ /** Base class for clients that communicate via a libp2p protocol */
8
+ export class ProtocolClient {
9
+ constructor(
10
+ protected readonly peerId: PeerId,
11
+ protected readonly peerNetwork: IPeerNetwork,
12
+ ) { }
13
+
14
+ protected async processMessage<T>(
15
+ message: unknown,
16
+ protocol: string,
17
+ options?: { signal?: AbortSignal }
18
+ ): Promise<T> {
19
+ const stream = await this.peerNetwork.connect(
20
+ this.peerId,
21
+ protocol,
22
+ { signal: options?.signal }
23
+ );
24
+
25
+ try {
26
+ // Send the request using length-prefixed encoding
27
+ const encoded = pipe(
28
+ [new TextEncoder().encode(JSON.stringify(message))],
29
+ lpEncode
30
+ );
31
+ for await (const chunk of encoded) {
32
+ stream.send(chunk);
33
+ }
34
+
35
+ // Read the response from the stream (which is now directly AsyncIterable)
36
+ const source = pipe(
37
+ stream,
38
+ lpDecode,
39
+ async function* (source) {
40
+ for await (const data of source) {
41
+ const decoded = new TextDecoder().decode(data.subarray());
42
+ const parsed = JSON.parse(decoded);
43
+ yield parsed;
44
+ }
45
+ }
46
+ ) as AsyncIterable<T>;
47
+
48
+ return await first(() => source, () => { throw new Error('No response received') });
49
+ } finally {
50
+ await stream.close();
51
+ }
52
+ }
53
+ }
@@ -1,112 +1,112 @@
1
- import type {
2
- IRepo, GetBlockResults, PendSuccess, StaleFailure, ActionBlocks, MessageOptions, CommitResult,
3
- PendRequest, CommitRequest, BlockGets, IPeerNetwork
4
- } from "@optimystic/db-core";
5
- import type { RepoMessage } from "@optimystic/db-core";
6
- import type { PeerId } from "@libp2p/interface";
7
- import { ProtocolClient } from "../protocol-client.js";
8
- import { peerIdFromString } from "@libp2p/peer-id";
9
-
10
- export class RepoClient extends ProtocolClient implements IRepo {
11
- private constructor(peerId: PeerId, peerNetwork: IPeerNetwork, readonly protocolPrefix?: string) {
12
- super(peerId, peerNetwork);
13
- }
14
-
15
- /** Create a new client instance */
16
- public static create(peerId: PeerId, peerNetwork: IPeerNetwork, protocolPrefix?: string): RepoClient {
17
- return new RepoClient(peerId, peerNetwork, protocolPrefix);
18
- }
19
-
20
- async get(blockGets: BlockGets, options: MessageOptions): Promise<GetBlockResults> {
21
- return this.processRepoMessage<GetBlockResults>(
22
- [{ get: blockGets }],
23
- options
24
- );
25
- }
26
-
27
- async pend(request: PendRequest, options: MessageOptions): Promise<PendSuccess | StaleFailure> {
28
- return this.processRepoMessage<PendSuccess | StaleFailure>(
29
- [{ pend: request }],
30
- options
31
- );
32
- }
33
-
34
- async cancel(actionRef: ActionBlocks, options: MessageOptions): Promise<void> {
35
- return this.processRepoMessage<void>(
36
- [{ cancel: { actionRef } }],
37
- options
38
- );
39
- }
40
-
41
- async commit(request: CommitRequest, options: MessageOptions): Promise<CommitResult> {
42
- return this.processRepoMessage<CommitResult>(
43
- [{ commit: request }],
44
- options
45
- );
46
- }
47
-
48
- private async processRepoMessage<T>(
49
- operations: RepoMessage['operations'],
50
- options: MessageOptions,
51
- hop: number = 0
52
- ): Promise<T> {
53
- const message: RepoMessage = {
54
- operations,
55
- expiration: options.expiration,
56
- };
57
- const deadline = options.expiration ?? (Date.now() + 30_000)
58
- const msLeft = Math.max(1, deadline - Date.now())
59
- const withTimeout = async <U>(fn: () => Promise<U>): Promise<U> => {
60
- return await Promise.race<U>([
61
- fn(),
62
- new Promise<never>((_, reject) => setTimeout(() => reject(new Error('RepoClient timeout')), msLeft))
63
- ])
64
- }
65
- let response: any
66
- const preferred = (this.protocolPrefix ?? '/db-p2p') + '/repo/1.0.0'
67
- response = await withTimeout(() => super.processMessage<any>(message, preferred, { signal: options?.signal }))
68
-
69
- if (response?.redirect?.peers?.length) {
70
- if (hop >= 2) {
71
- throw new Error('Redirect loop detected in RepoClient (max hops reached)')
72
- }
73
- const currentIdStr = this.peerId.toString()
74
- const next = response.redirect.peers.find((p: any) => p.id !== currentIdStr) ?? response.redirect.peers[0]
75
- const nextId = peerIdFromString(next.id)
76
- if (next.id === currentIdStr) {
77
- throw new Error('Redirect loop detected in RepoClient (same peer)')
78
- }
79
- // cache hint
80
- this.recordCoordinatorForOpsIfSupported(operations, nextId)
81
- // single-hop retry against target peer using repo protocol
82
- const nextClient = RepoClient.create(nextId, this.peerNetwork, this.protocolPrefix)
83
- return await nextClient.processRepoMessage<T>(operations, options, hop + 1)
84
- }
85
- return response as T;
86
- }
87
-
88
- private extractKeyFromOperations(ops: RepoMessage['operations']): Uint8Array | undefined {
89
- const op = ops[0];
90
- if ('get' in op) {
91
- const id = op.get.blockIds[0];
92
- return id ? new TextEncoder().encode(id) : undefined;
93
- }
94
- if ('pend' in op) {
95
- const id = Object.keys(op.pend.transforms)[0];
96
- return id ? new TextEncoder().encode(id) : undefined;
97
- }
98
- if ('commit' in op) {
99
- return new TextEncoder().encode(op.commit.tailId);
100
- }
101
- return undefined;
102
- }
103
-
104
- private recordCoordinatorForOpsIfSupported(ops: RepoMessage['operations'], peerId: PeerId): void {
105
- const keyBytes = this.extractKeyFromOperations(ops)
106
- const pn: any = this.peerNetwork as any
107
- if (keyBytes != null && typeof pn?.recordCoordinator === 'function') {
108
- pn.recordCoordinator(keyBytes, peerId)
109
- }
110
- }
111
-
112
- }
1
+ import type {
2
+ IRepo, GetBlockResults, PendSuccess, StaleFailure, ActionBlocks, MessageOptions, CommitResult,
3
+ PendRequest, CommitRequest, BlockGets, IPeerNetwork
4
+ } from "@optimystic/db-core";
5
+ import type { RepoMessage } from "@optimystic/db-core";
6
+ import type { PeerId } from "@libp2p/interface";
7
+ import { ProtocolClient } from "../protocol-client.js";
8
+ import { peerIdFromString } from "@libp2p/peer-id";
9
+
10
+ export class RepoClient extends ProtocolClient implements IRepo {
11
+ private constructor(peerId: PeerId, peerNetwork: IPeerNetwork, readonly protocolPrefix?: string) {
12
+ super(peerId, peerNetwork);
13
+ }
14
+
15
+ /** Create a new client instance */
16
+ public static create(peerId: PeerId, peerNetwork: IPeerNetwork, protocolPrefix?: string): RepoClient {
17
+ return new RepoClient(peerId, peerNetwork, protocolPrefix);
18
+ }
19
+
20
+ async get(blockGets: BlockGets, options: MessageOptions): Promise<GetBlockResults> {
21
+ return this.processRepoMessage<GetBlockResults>(
22
+ [{ get: blockGets }],
23
+ options
24
+ );
25
+ }
26
+
27
+ async pend(request: PendRequest, options: MessageOptions): Promise<PendSuccess | StaleFailure> {
28
+ return this.processRepoMessage<PendSuccess | StaleFailure>(
29
+ [{ pend: request }],
30
+ options
31
+ );
32
+ }
33
+
34
+ async cancel(actionRef: ActionBlocks, options: MessageOptions): Promise<void> {
35
+ return this.processRepoMessage<void>(
36
+ [{ cancel: { actionRef } }],
37
+ options
38
+ );
39
+ }
40
+
41
+ async commit(request: CommitRequest, options: MessageOptions): Promise<CommitResult> {
42
+ return this.processRepoMessage<CommitResult>(
43
+ [{ commit: request }],
44
+ options
45
+ );
46
+ }
47
+
48
+ private async processRepoMessage<T>(
49
+ operations: RepoMessage['operations'],
50
+ options: MessageOptions,
51
+ hop: number = 0
52
+ ): Promise<T> {
53
+ const message: RepoMessage = {
54
+ operations,
55
+ expiration: options.expiration,
56
+ };
57
+ const deadline = options.expiration ?? (Date.now() + 30_000)
58
+ const msLeft = Math.max(1, deadline - Date.now())
59
+ const withTimeout = async <U>(fn: () => Promise<U>): Promise<U> => {
60
+ return await Promise.race<U>([
61
+ fn(),
62
+ new Promise<never>((_, reject) => setTimeout(() => reject(new Error('RepoClient timeout')), msLeft))
63
+ ])
64
+ }
65
+ let response: any
66
+ const preferred = (this.protocolPrefix ?? '/db-p2p') + '/repo/1.0.0'
67
+ response = await withTimeout(() => super.processMessage<any>(message, preferred, { signal: options?.signal }))
68
+
69
+ if (response?.redirect?.peers?.length) {
70
+ if (hop >= 2) {
71
+ throw new Error('Redirect loop detected in RepoClient (max hops reached)')
72
+ }
73
+ const currentIdStr = this.peerId.toString()
74
+ const next = response.redirect.peers.find((p: any) => p.id !== currentIdStr) ?? response.redirect.peers[0]
75
+ const nextId = peerIdFromString(next.id)
76
+ if (next.id === currentIdStr) {
77
+ throw new Error('Redirect loop detected in RepoClient (same peer)')
78
+ }
79
+ // cache hint
80
+ this.recordCoordinatorForOpsIfSupported(operations, nextId)
81
+ // single-hop retry against target peer using repo protocol
82
+ const nextClient = RepoClient.create(nextId, this.peerNetwork, this.protocolPrefix)
83
+ return await nextClient.processRepoMessage<T>(operations, options, hop + 1)
84
+ }
85
+ return response as T;
86
+ }
87
+
88
+ private extractKeyFromOperations(ops: RepoMessage['operations']): Uint8Array | undefined {
89
+ const op = ops[0];
90
+ if ('get' in op) {
91
+ const id = op.get.blockIds[0];
92
+ return id ? new TextEncoder().encode(id) : undefined;
93
+ }
94
+ if ('pend' in op) {
95
+ const id = Object.keys(op.pend.transforms)[0];
96
+ return id ? new TextEncoder().encode(id) : undefined;
97
+ }
98
+ if ('commit' in op) {
99
+ return new TextEncoder().encode(op.commit.tailId);
100
+ }
101
+ return undefined;
102
+ }
103
+
104
+ private recordCoordinatorForOpsIfSupported(ops: RepoMessage['operations'], peerId: PeerId): void {
105
+ const keyBytes = this.extractKeyFromOperations(ops)
106
+ const pn: any = this.peerNetwork as any
107
+ if (keyBytes != null && typeof pn?.recordCoordinator === 'function') {
108
+ pn.recordCoordinator(keyBytes, peerId)
109
+ }
110
+ }
111
+
112
+ }