Skip to content
This repository was archived by the owner on Nov 23, 2022. It is now read-only.

Commit a38c875

Browse files
authored
feat(relay, lib): add stack to relay and store/shard improvements (#86)
* feat(relay, lib): add stack to relay and store/shard improvements #26; #64 * chore(relay): move wrtc to dependencies
1 parent 0271aa5 commit a38c875

File tree

13 files changed

+311
-37
lines changed

13 files changed

+311
-37
lines changed

packages/explorer/src/main.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ export const App: React.FC = () => {
2020
let stop = () => {}
2121

2222
const run = async () => {
23-
const stack = await Stack.create({ namespace })
23+
const stack = await Stack.create({
24+
namespace,
25+
relay: localStorage['relay']
26+
})
2427
window.stack = stack
2528
window.Shard = Shard
2629
window.ShardKind = ShardKind

packages/ipfs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
"license": "GPL-3.0",
1010
"name": "@dstack-js/ipfs",
1111
"repository": "https://github.com/dstack-js/dstack.git",
12-
"type": "module",
12+
"type": "commonjs",
1313
"version": "0.2.47"
1414
}

packages/ipfs/src/bootstrap.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ export const bootstrap = async (
99
const query = gql`
1010
query Bootstrap($protocol: Protocol!, $hostname: String!, $port: Int!) {
1111
listen(protocol: $protocol, hostname: $hostname, port: $port)
12-
peers(randomize: true)
12+
peers(
13+
randomize: true
14+
protocol: $protocol
15+
hostname: $hostname
16+
port: $port
17+
)
1318
}
1419
`
1520

packages/ipfs/src/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { bootstrap } from './bootstrap'
33
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
44
// @ts-ignore
55
import WebRTCStar from '@dstack-js/transport'
6+
import type { IPFSOptions } from 'ipfs-core/src/components/storage'
67

78
export interface Options {
89
namespace: string;
910
relay?: string;
1011
wrtc?: any;
1112
privateKey?: string;
13+
repo?: IPFSOptions['repo'];
1214
}
1315

1416
export { CID, PeerId }
@@ -17,12 +19,14 @@ export const create = async ({
1719
namespace,
1820
relay,
1921
wrtc,
20-
privateKey
22+
privateKey,
23+
repo
2124
}: Options): Promise<IPFS> => {
2225
const { listen, peers } = await bootstrap(namespace, relay)
2326

2427
return IPFSCreate({
2528
init: { privateKey },
29+
repo,
2630
config: {
2731
Discovery: {
2832
webRTCStar: { Enabled: true }
@@ -57,7 +61,8 @@ export const create = async ({
5761
relay: {
5862
enabled: true,
5963
hop: {
60-
enabled: true
64+
enabled: true,
65+
active: true
6166
}
6267
}
6368
})

packages/ipfs/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"extends": "../../tsconfig.base.json",
33
"compilerOptions": {
4-
"module": "ESNext",
4+
"module": "CommonJS",
55
"forceConsistentCasingInFileNames": true,
66
"strict": true,
77
"noImplicitOverride": true,

packages/lib/src/stack.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import all from 'it-all'
44
import type { CID } from 'ipfs-core'
55
import { PeerUnreachableError, Store } from '.'
66
import { PubSub } from './pubsub'
7-
import { Storage } from './storage'
8-
import { create } from '@dstack-js/ipfs'
7+
import { InMemoryStorage, Storage } from './storage'
8+
import { create, Options } from '@dstack-js/ipfs'
99

1010
export interface Peer {
1111
id: string;
@@ -33,19 +33,29 @@ export interface StackOptions {
3333
*
3434
* No need to provide it unless you want to use DStack in non browser environment
3535
*/
36-
wrtc?: any;
36+
wrtc?: Options['wrtc'];
3737
/**
3838
* Relay GraphQL Endpoint
3939
*
4040
* Defaults to DStack Cloud
4141
*/
42-
relay?: string;
42+
relay?: Options['relay'];
4343
/**
4444
* Storage implementation
4545
*
4646
* No need to provide it unless you want custom storage implementation to be used
4747
*/
4848
storage?: Storage;
49+
/**
50+
* A path to store IPFS repo
51+
*
52+
* No need to provide it unless you to create a more than one Stack instance
53+
*/
54+
repo?: Options['repo'];
55+
/**
56+
* Preload shard on store replication
57+
*/
58+
loadOnReplicate?: boolean;
4959
}
5060

5161
export class Stack {
@@ -55,9 +65,14 @@ export class Stack {
5565
private announceInterval?: ReturnType<typeof setTimeout>
5666
public announce = true
5767

58-
constructor(public namespace: CID, public ipfs: IPFS, storage: Storage) {
68+
private constructor(
69+
public namespace: CID,
70+
public ipfs: IPFS,
71+
storage: Storage,
72+
loadOnReplicate?: boolean
73+
) {
5974
this.pubsub = new PubSub(ipfs, namespace.toString())
60-
this.store = new Store(this, storage)
75+
this.store = new Store(this, storage, loadOnReplicate)
6176
}
6277

6378
/**
@@ -100,16 +115,18 @@ export class Stack {
100115
ipfs,
101116
storage,
102117
relay,
103-
wrtc
118+
wrtc,
119+
repo,
120+
loadOnReplicate
104121
}: StackOptions) {
105122
if (!ipfs) {
106-
ipfs = await create({ namespace, relay, wrtc })
123+
ipfs = await create({ namespace, relay, wrtc, repo })
107124
}
108125

109126
const cid = await ipfs.dag.put({ namespace })
110-
storage = storage || new Storage(namespace)
127+
storage = storage || new InMemoryStorage(namespace)
111128

112-
const stack = new Stack(cid, ipfs, storage)
129+
const stack = new Stack(cid, ipfs, storage, loadOnReplicate)
113130
await stack.start()
114131

115132
return stack

packages/lib/src/storage.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
export class Storage<T = { value: string; date: Date }> {
1+
/* eslint-disable @typescript-eslint/no-misused-new */
2+
export interface Storage<T = { value: string; date: Date }> {
3+
namespace: string;
4+
5+
keys(): Promise<string[]>;
6+
get(key: string): Promise<T | null>;
7+
set(key: string, value: T): Promise<void>;
8+
}
9+
10+
export class InMemoryStorage<T = { value: string; date: Date }>
11+
implements Storage<T> {
212
private data: { [key: string]: T } = {}
313

414
constructor(public namespace: string) {}

packages/lib/src/store.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ export type StackMessage = ReplicateMessage | ReplicateRequestMessage;
1818

1919
export class Store {
2020
private pubsub: PubSub<StackMessage>
21-
private storage: Storage
2221

23-
constructor(private stack: Stack, storage: Storage) {
22+
constructor(
23+
private stack: Stack,
24+
private storage: Storage,
25+
private loadOnReplicate: boolean = false
26+
) {
2427
this.pubsub = stack.pubsub.create('$$store')
25-
this.storage = storage
2628
}
2729

2830
private async onReplicate(msg: Message<StackMessage>): Promise<void> {
@@ -37,10 +39,22 @@ export class Store {
3739
value: msg.data.value,
3840
date
3941
})
42+
43+
if (this.loadOnReplicate && msg.data.value.startsWith('/shard/')) {
44+
Shard.from(this.stack, msg.data.value)
45+
.then((shard) => {
46+
console.debug('shard replicated', shard)
47+
})
48+
.catch((err) => {
49+
console.warn('Failed to load on replicate', msg.data, err)
50+
})
51+
}
4052
}
4153

4254
private watchShard(key: string, watch: Shard) {
4355
watch.on('update', async (shard): Promise<void> => {
56+
console.log(shard.toString())
57+
4458
this.storage.set(key, {
4559
value: shard.toString(),
4660
date: new Date()
@@ -93,7 +107,7 @@ export class Store {
93107
await this.pubsub.subscribe('replicateRequest', async (msg) => {
94108
if (msg.data.kind !== 'replicateRequest') return
95109
const keys = await this.storage.keys()
96-
await Promise.all(keys.map(this.replicate))
110+
await Promise.all(keys.map((key) => this.replicate(key)))
97111
})
98112

99113
await this.pubsub.publish('replicateRequest', { kind: 'replicateRequest' })

packages/relay/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33
"dstack-relay": "src/index.js"
44
},
55
"dependencies": {
6+
"@dstack-js/lib": "latest",
7+
"@dstack-js/wrtc": "^0.4.8",
68
"@libp2p/webrtc-star-protocol": "1.0.1",
79
"fastify": "3.27.4",
810
"fastify-cors": "6.0.3",
911
"fastify-socket.io": "3.0.0",
1012
"graphql": "16.3.0",
1113
"graphql-playground-html": "1.6.30",
14+
"lru-cache": "^7.7.1",
1215
"mercurius": "9.3.6",
1316
"nexus": "1.3.0",
1417
"prom-client": "14.0.1",
1518
"redis": "4.0.4",
1619
"socket.io": "4.4.1"
1720
},
1821
"devDependencies": {
22+
"@types/lru-cache": "^7.6.0",
1923
"ts-node": "10.7.0",
2024
"tsconfig-paths": "3.14.1"
2125
},

packages/relay/src/services/signaling/index.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
joinsFailureTotal,
1616
joinsTotal
1717
} from '../metrics'
18+
import { getStack } from '../stack'
1819

1920
const handle = (socket: WebRTCStarSocket, cachePrefix: string) => {
2021
console.log('signaling', 'handle', socket.id)
@@ -104,9 +105,13 @@ export const setSocket = async (server: FastifyInstance) => {
104105

105106
await server.ready()
106107
server.io.on('connection', (socket) => {
107-
const cachePrefix = socket.handshake.auth['namespace']
108-
? socket.handshake.auth['namespace']
109-
: ''
108+
let cachePrefix = ''
109+
110+
if (socket.handshake.auth['namespace']) {
111+
cachePrefix = socket.handshake.auth['namespace']
112+
113+
getStack(socket.handshake.auth['namespace']).catch()
114+
}
110115

111116
// @ts-expect-error: incompatible types
112117
return handle(socket, cachePrefix)

0 commit comments

Comments
 (0)