@xyo-network/bridge-http-express 3.14.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.
@@ -0,0 +1,237 @@
1
+ import { Server } from 'node:http'
2
+
3
+ import { assertEx } from '@xylabs/assert'
4
+ import { exists } from '@xylabs/exists'
5
+ import {
6
+ asyncHandler,
7
+ customPoweredByHeader,
8
+ disableCaseSensitiveRouting,
9
+ disableExpressDefaultPoweredByHeader,
10
+ jsonBodyParser,
11
+ responseProfiler,
12
+ useRequestCounters,
13
+ } from '@xylabs/express'
14
+ import { Address } from '@xylabs/hex'
15
+ import { toJsonString } from '@xylabs/object'
16
+ import { isQueryBoundWitness, QueryBoundWitness } from '@xyo-network/boundwitness-model'
17
+ import { HttpBridge, HttpBridgeConfig } from '@xyo-network/bridge-http'
18
+ import {
19
+ BridgeExposeOptions, BridgeParams, BridgeUnexposeOptions,
20
+ } from '@xyo-network/bridge-model'
21
+ // import { standardResponses } from '@xyo-network/express-node-middleware'
22
+ import {
23
+ AnyConfigSchema, creatableModule, ModuleInstance, ModuleQueryResult, resolveAddressToInstanceUp,
24
+ } from '@xyo-network/module-model'
25
+ import { Payload, Schema } from '@xyo-network/payload-model'
26
+ import express, {
27
+ Application, Request, Response,
28
+ } from 'express'
29
+ import { StatusCodes } from 'http-status-codes'
30
+
31
+ /**
32
+ * The type of the path parameters for the address path.
33
+ */
34
+ type AddressPathParams = {
35
+ address: Address
36
+ }
37
+
38
+ /**
39
+ * The type of the request body for the address path.
40
+ */
41
+ type PostAddressRequestBody = [QueryBoundWitness, undefined | Payload[]]
42
+
43
+ // TODO: This does not match the error response shape of the legacy bridge BUT it its the
44
+ // shape this bridge is currently returning. Massage this into the standard
45
+ // error shape constructed via middleware.
46
+ /* type ErrorResponseBody = {
47
+ error: string
48
+ } */
49
+
50
+ export const HttpBridgeExpressConfigSchema = 'network.xyo.bridge.http.express.config' as const
51
+ export type HttpBridgeExpressConfigSchema = typeof HttpBridgeExpressConfigSchema
52
+
53
+ export interface HttpBridgeExpressConfig extends HttpBridgeConfig<{}, HttpBridgeExpressConfigSchema> {}
54
+
55
+ export interface HttpBridgeExpressParams extends BridgeParams<AnyConfigSchema<HttpBridgeExpressConfig>> {}
56
+
57
+ @creatableModule()
58
+ export class HttpBridgeExpress<TParams extends HttpBridgeExpressParams> extends HttpBridge<TParams> {
59
+ static override readonly configSchemas: Schema[] = [...super.configSchemas, HttpBridgeExpressConfigSchema]
60
+ protected _app?: Application
61
+ protected _exposedModules: WeakRef<ModuleInstance>[] = []
62
+ protected _server?: Server
63
+
64
+ protected get app() {
65
+ if (!this._app) this._app = this.initializeApp()
66
+ return assertEx(this._app, () => 'App not initialized')
67
+ }
68
+
69
+ async exposeChild(mod: ModuleInstance, options?: BridgeExposeOptions | undefined): Promise<ModuleInstance[]> {
70
+ const { maxDepth = 5 } = options ?? {}
71
+ assertEx(this.config.host, () => 'Not configured as a host')
72
+ this._exposedModules.push(new WeakRef(mod))
73
+ const children = maxDepth > 0 ? ((await mod.publicChildren?.()) ?? []) : []
74
+ this.logger.log(`childrenToExpose [${mod.id}][${mod.address}]: ${toJsonString(children.map(child => child.id))}`)
75
+ const exposedChildren = (await Promise.all(children.map(child => this.exposeChild(child, { maxDepth: maxDepth - 1, required: false }))))
76
+ .flat()
77
+ .filter(exists)
78
+ const allExposed = [mod, ...exposedChildren]
79
+
80
+ for (const exposedMod of allExposed) this.logger?.log(`exposed: ${exposedMod.address} [${mod.id}]`)
81
+
82
+ return allExposed
83
+ }
84
+
85
+ override async exposeHandler(address: Address, options?: BridgeExposeOptions | undefined): Promise<ModuleInstance[]> {
86
+ const { required = true } = options ?? {}
87
+ const mod = await resolveAddressToInstanceUp(this, address)
88
+ if (required && !mod) {
89
+ throw new Error(`Unable to find required module: ${address}`)
90
+ }
91
+ if (mod) {
92
+ return this.exposeChild(mod, options)
93
+ }
94
+ return []
95
+ }
96
+
97
+ override exposedHandler(): Address[] {
98
+ return this._exposedModules.map(ref => ref.deref()?.address).filter(exists)
99
+ }
100
+
101
+ override async startHandler(): Promise<boolean> {
102
+ return (await super.startHandler()) && (await this.startHttpServer())
103
+ }
104
+
105
+ override async stopHandler(_timeout?: number | undefined): Promise<boolean> {
106
+ return (await super.stopHandler()) && (await this.stopHttpServer())
107
+ }
108
+
109
+ override async unexposeHandler(address: Address, options?: BridgeUnexposeOptions | undefined): Promise<ModuleInstance[]> {
110
+ const { maxDepth = 2, required = true } = options ?? {}
111
+ assertEx(this.config.host, () => 'Not configured as a host')
112
+ const mod = this._exposedModules.find(ref => ref.deref()?.address === address)?.deref()
113
+ assertEx(!required || mod, () => `Module not exposed: ${address}`)
114
+ this._exposedModules = this._exposedModules.filter(ref => ref.deref()?.address !== address)
115
+ if (mod) {
116
+ const children = maxDepth > 0 ? ((await mod.publicChildren?.()) ?? []) : []
117
+ const exposedChildren = (
118
+ await Promise.all(children.map(child => this.unexposeHandler(child.address, { maxDepth: maxDepth - 1, required: false })))
119
+ )
120
+ .flat()
121
+ .filter(exists)
122
+ return [mod, ...exposedChildren]
123
+ }
124
+ return []
125
+ }
126
+
127
+ protected async callLocalModule(address: Address, query: QueryBoundWitness, payloads: Payload[]): Promise<ModuleQueryResult | null> {
128
+ const mod = this._exposedModules.find(ref => ref.deref()?.address === address)?.deref()
129
+ return mod ? await mod.query(query, payloads) : null
130
+ }
131
+
132
+ protected async handleGet(req: Request<AddressPathParams, ModuleQueryResult, PostAddressRequestBody>, res: Response) {
133
+ const { address } = req.params
134
+ try {
135
+ if (address == this.address) {
136
+ res.json(await this.stateQuery(this.account))
137
+ } else {
138
+ const mod = this._exposedModules.find(ref => ref.deref()?.address === address)?.deref()
139
+ // TODO: Use standard errors middleware
140
+ if (mod) {
141
+ res.json(await mod.stateQuery(this.account))
142
+ } else {
143
+ res.status(StatusCodes.NOT_FOUND).json({ error: 'Module not found' })
144
+ }
145
+ }
146
+ } catch (ex) {
147
+ // TODO: Sanitize message
148
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: (ex as Error).message })
149
+ }
150
+ }
151
+
152
+ protected async handlePost(req: Request<AddressPathParams, ModuleQueryResult, PostAddressRequestBody>, res: Response) {
153
+ const { address } = req.params
154
+ const [bw, payloads = []] = Array.isArray(req.body) ? req.body : []
155
+ const query = isQueryBoundWitness(bw) ? bw : undefined
156
+ if (!query) {
157
+ // TODO: Use standard errors middleware
158
+ res.status(StatusCodes.BAD_REQUEST).json({ error: 'No query provided' })
159
+ return
160
+ }
161
+ try {
162
+ if (address == this.address) {
163
+ const result = await this.query(query, payloads)
164
+ return res.json(result)
165
+ } else {
166
+ const result = await this.callLocalModule(address, query, payloads)
167
+ // TODO: Use standard errors middleware
168
+ if (result === null) {
169
+ res.status(StatusCodes.NOT_FOUND).json({ error: 'Module not found' })
170
+ } else {
171
+ res.json(result)
172
+ }
173
+ }
174
+ } catch (ex) {
175
+ // TODO: Sanitize message
176
+ res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: (ex as Error).message })
177
+ }
178
+ }
179
+
180
+ protected initializeApp() {
181
+ // Create the express app
182
+ const app = express()
183
+
184
+ // Add middleware
185
+ app.use(responseProfiler)
186
+ app.use(jsonBodyParser)
187
+ // removed for now since this causes a cycle
188
+ // app.use(standardResponses)
189
+ disableExpressDefaultPoweredByHeader(app)
190
+ app.use(customPoweredByHeader)
191
+ disableCaseSensitiveRouting(app)
192
+ useRequestCounters(app)
193
+
194
+ // Add routes
195
+ // Redirect all requests to the root to this module's address
196
+ app.get('/', (_req, res) => res.redirect(StatusCodes.MOVED_TEMPORARILY, `/${this.address}`))
197
+ app.post('/', (_req, res) => res.redirect(StatusCodes.TEMPORARY_REDIRECT, `/${this.address}`))
198
+
199
+ app.get<AddressPathParams, ModuleQueryResult>(
200
+ '/:address',
201
+
202
+ asyncHandler(async (req, res) => await this.handleGet(req, res)),
203
+ )
204
+ app.post<AddressPathParams, ModuleQueryResult, PostAddressRequestBody>(
205
+ '/:address',
206
+
207
+ asyncHandler(async (req, res) => { await this.handlePost(req, res) }),
208
+ )
209
+ return app
210
+ }
211
+
212
+ protected startHttpServer(): Promise<boolean> {
213
+ if (this.config.host) {
214
+ assertEx(!this._server, () => 'Server already started')
215
+ this._server = this.app.listen(this.config.host?.port ?? 3030)
216
+ }
217
+ return Promise.resolve(true)
218
+ }
219
+
220
+ protected stopHttpServer(): Promise<boolean> {
221
+ if (this.config.host) {
222
+ return new Promise((resolve, reject) => {
223
+ if (this._server) {
224
+ this._server.close((err) => {
225
+ if (err) {
226
+ reject(err)
227
+ } else {
228
+ this._server = undefined
229
+ resolve(true)
230
+ }
231
+ })
232
+ }
233
+ })
234
+ }
235
+ return Promise.resolve(true)
236
+ }
237
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './HttpBridge.ts'
@@ -0,0 +1,42 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import { MemoryNode } from '@xyo-network/node-memory'
4
+ import {
5
+ describe, expect, it,
6
+ } from 'vitest'
7
+
8
+ import { HttpBridgeExpress, HttpBridgeExpressConfigSchema } from '../HttpBridge.ts'
9
+
10
+ /**
11
+ * @group module
12
+ * @group bridge
13
+ */
14
+
15
+ describe('HttpBridge', () => {
16
+ const baseUrl = 'https://sfjhskjdsfhdsk.com'
17
+
18
+ console.log(`HttpBridge:baseUrl ${baseUrl}`)
19
+ const cases = [
20
+ ['/', `${baseUrl}`],
21
+ /* ['/node', `${baseUrl}/node`], */
22
+ ]
23
+
24
+ it.each(cases)('HttpBridge: %s', async (_, nodeUrl) => {
25
+ const memNode = await MemoryNode.create({ account: 'random' })
26
+
27
+ const bridge = await HttpBridgeExpress.create({
28
+ account: 'random',
29
+ config: {
30
+ nodeUrl, schema: HttpBridgeExpressConfigSchema, security: { allowAnonymous: true },
31
+ },
32
+ })
33
+
34
+ await bridge?.start?.()
35
+
36
+ await memNode.register(bridge)
37
+ await memNode.attach(bridge.address, true)
38
+
39
+ const modules = await memNode.resolve('*')
40
+ expect(modules.length).toBe(2)
41
+ })
42
+ })
@@ -0,0 +1,61 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import {
4
+ beforeEach,
5
+ describe, expect, it,
6
+ } from 'vitest'
7
+
8
+ import type { HttpBridgeExpressParams } from '../HttpBridge.ts'
9
+ import { HttpBridgeExpress, HttpBridgeExpressConfigSchema } from '../HttpBridge.ts'
10
+
11
+ describe('HttpBridgeExpress', () => {
12
+ let httpBridge: HttpBridgeExpress<HttpBridgeExpressParams>
13
+
14
+ beforeEach(async () => {
15
+ httpBridge = await HttpBridgeExpress.create({
16
+ account: 'random',
17
+ config: {
18
+ name: 'TestBridge', nodeUrl: 'http://localhost:8080', schema: HttpBridgeExpressConfigSchema, security: { allowAnonymous: true },
19
+ },
20
+ })
21
+ })
22
+
23
+ it('should create an instance of HttpBridgeExpress', () => {
24
+ expect(httpBridge).toBeInstanceOf(HttpBridgeExpress)
25
+ })
26
+
27
+ it('should have axios instance', () => {
28
+ expect(httpBridge.axios).toBeDefined()
29
+ })
30
+
31
+ it('should have nodeUrl defined', () => {
32
+ expect(httpBridge.clientUrl).toBe('http://localhost:8080')
33
+ })
34
+
35
+ it('should have resolver defined', () => {
36
+ expect(httpBridge.resolver).toBeDefined()
37
+ })
38
+
39
+ it('should return correct moduleUrl', () => {
40
+ const address = '0x1234'
41
+ expect(httpBridge.moduleUrl(address).toString()).toBe('http://localhost:8080/0x1234')
42
+ })
43
+
44
+ it('should throw error on call to exposeHandler', async () => {
45
+ try {
46
+ await httpBridge.exposeHandler('test')
47
+ expect('').toBe('exposeHandler should have thrown an error')
48
+ } catch (error) {
49
+ expect(error).toBeInstanceOf(Error)
50
+ }
51
+ })
52
+
53
+ it('should throw error on call to unexposeHandler', async () => {
54
+ try {
55
+ await httpBridge.unexposeHandler('test')
56
+ expect('').toBe('unexposeHandler should have thrown an error')
57
+ } catch (error) {
58
+ expect(error).toBeInstanceOf(Error)
59
+ }
60
+ })
61
+ })
@@ -0,0 +1,86 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import { HttpBridge, HttpBridgeConfigSchema } from '@xyo-network/bridge-http'
4
+ import type {
5
+ AsyncQueryBusIntersectConfig,
6
+ PubSubBridgeConfig,
7
+ PubSubBridgeParams,
8
+ } from '@xyo-network/bridge-pub-sub'
9
+ import {
10
+ PubSubBridge,
11
+ PubSubBridgeConfigSchema,
12
+ } from '@xyo-network/bridge-pub-sub'
13
+ import { MemoryNode } from '@xyo-network/node-memory'
14
+ import { NodeConfigSchema } from '@xyo-network/node-model'
15
+ import {
16
+ describe, expect, it,
17
+ } from 'vitest'
18
+
19
+ /**
20
+ * @group module
21
+ * @group bridge
22
+ */
23
+
24
+ describe.skip('HttpBridge with PubSubBridge', () => {
25
+ const httpBaseUrl = process.env.XNS_API_DOMAIN ?? 'https://beta.xns.xyo.network' ?? 'http://localhost:80'
26
+
27
+ console.log(`HttpBridge:baseUrl ${httpBaseUrl}`)
28
+ const cases = [
29
+ ['/', `${httpBaseUrl}`],
30
+ /* ['/node', `${baseUrl}/node`], */
31
+ ]
32
+
33
+ it.each(cases)('HttpBridge: %s', async (_, nodeUrl) => {
34
+ const memNode = await MemoryNode.create({ account: 'random', config: { name: 'MemNodeArie', schema: NodeConfigSchema } })
35
+ const intersect: AsyncQueryBusIntersectConfig = {
36
+ queries: { archivist: 'MemNodeArie:XNS:Intersect:QueryArchivist', boundWitnessDiviner: 'MemNodeArie:XNS:Intersect:QueryBoundWitnessDiviner' },
37
+ responses: {
38
+ archivist: 'MemNodeArie:XNS:Intersect:ResponseArchivist',
39
+ boundWitnessDiviner: 'MemNodeArie:XNS:Intersect:ResponseBoundWitnessDiviner',
40
+ },
41
+ }
42
+
43
+ const bridge = await HttpBridge.create({
44
+ account: 'random',
45
+ config: {
46
+ discoverRoots: 'start', name: 'TestBridge', nodeUrl, schema: HttpBridgeConfigSchema, security: { allowAnonymous: true },
47
+ },
48
+ })
49
+
50
+ await bridge?.start?.()
51
+ await memNode.register(bridge)
52
+ await memNode.attach(bridge?.address, true)
53
+
54
+ const config: PubSubBridgeConfig = {
55
+ client: { intersect }, host: { intersect }, name: 'PubSubBridgeArie', schema: PubSubBridgeConfigSchema,
56
+ }
57
+ const psParams: PubSubBridgeParams = { account: 'random', config }
58
+ const psBridge = await PubSubBridge.create(psParams)
59
+ await memNode.register(psBridge)
60
+ await memNode.attach(psBridge?.address, true)
61
+
62
+ await psBridge.start()
63
+ console.log(`Exposing: ${memNode.address}`)
64
+ await bridge.expose(memNode.address)
65
+
66
+ const subNodeInstance = await memNode?.resolve('PubSubBridgeArie')
67
+ expect(subNodeInstance).toBeDefined()
68
+
69
+ const testBridgeInstance = await memNode?.resolve('PubSubBridgeArie:TestBridge')
70
+ expect(testBridgeInstance).toBeDefined()
71
+
72
+ const xns1 = await testBridgeInstance?.resolve('XNS')
73
+ expect(xns1).toBeDefined()
74
+
75
+ const xns = await memNode?.resolve('PubSubBridgeArie:TestBridge:XNS')
76
+ expect(xns).toBeDefined()
77
+
78
+ const intersectNode = await memNode?.resolve('PubSubBridgeArie:TestBridge:XNS:Intersect')
79
+ expect(intersectNode).toBeDefined()
80
+
81
+ const queryArchivist = await memNode?.resolve('XNS:Intersect:QueryArchivist')
82
+ expect(queryArchivist?.id).toBe('QueryArchivist')
83
+
84
+ await psBridge.unexpose(memNode.address)
85
+ })
86
+ })
@@ -0,0 +1,156 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import type { HttpBridgeConfig, HttpBridgeParams } from '@xyo-network/bridge-http'
4
+ import { HttpBridge, HttpBridgeConfigSchema } from '@xyo-network/bridge-http'
5
+ import type { ModuleDescriptionPayload } from '@xyo-network/module-model'
6
+ import { ModuleDescriptionSchema } from '@xyo-network/module-model'
7
+ import { MemoryNode } from '@xyo-network/node-memory'
8
+ import { asAttachableNodeInstance } from '@xyo-network/node-model'
9
+ import { isPayloadOfSchemaType } from '@xyo-network/payload-model'
10
+ import { getPort } from 'get-port-please'
11
+ import {
12
+ beforeAll,
13
+ beforeEach,
14
+ describe, expect, it,
15
+ } from 'vitest'
16
+
17
+ import type { HttpBridgeExpressConfig, HttpBridgeExpressParams } from '../HttpBridge.ts'
18
+ import { HttpBridgeExpress, HttpBridgeExpressConfigSchema } from '../HttpBridge.ts'
19
+
20
+ const account = 'random'
21
+ const hostSchema = HttpBridgeExpressConfigSchema
22
+ const clientSchema = HttpBridgeConfigSchema
23
+ const security = { allowAnonymous: true }
24
+
25
+ /**
26
+ * @group module
27
+ * @group bridge
28
+ */
29
+ describe('HttpBridgeExpress', () => {
30
+ let port: number
31
+ let url: string
32
+ let hostBridge: HttpBridgeExpress<HttpBridgeExpressParams>
33
+ let clientBridge: HttpBridge<HttpBridgeParams>
34
+ let hostNode: MemoryNode
35
+ let clientNode: MemoryNode
36
+ let hostSibling: MemoryNode
37
+ let clientSibling: MemoryNode
38
+ let hostDescendent: MemoryNode
39
+ let clientDescendent: MemoryNode
40
+
41
+ beforeAll(async () => {
42
+ // Create Host/Client Nodes
43
+ hostNode = await MemoryNode.create({ account })
44
+ clientNode = await MemoryNode.create({ account })
45
+
46
+ // Create Host/Client Bridges
47
+ port = await getPort()
48
+ url = `http://localhost:${port}`
49
+
50
+ const host: HttpBridgeExpressConfig['host'] = { port }
51
+ const client: HttpBridgeConfig['client'] = { discoverRoots: 'start', url }
52
+ hostBridge = await HttpBridgeExpress.create({
53
+ account,
54
+ config: {
55
+ host, name: 'TestBridgeHost', schema: hostSchema, security,
56
+ },
57
+ })
58
+ clientBridge = await HttpBridge.create({
59
+ account,
60
+ config: {
61
+ client, name: 'TestBridgeClient', schema: clientSchema, security,
62
+ },
63
+ })
64
+
65
+ // Register Host/Client Bridges
66
+ await hostNode.register(hostBridge)
67
+ await hostNode.attach(hostBridge.address, true)
68
+ await clientNode.register(clientBridge)
69
+ await clientNode.attach(clientBridge.address, true)
70
+
71
+ // Create Host/Client Sibling Nodes
72
+ hostSibling = await MemoryNode.create({ account })
73
+ clientSibling = await MemoryNode.create({ account })
74
+
75
+ // Register Host/Client Siblings
76
+ await hostNode.register(hostSibling)
77
+ await hostNode.attach(hostSibling.address, true)
78
+ await clientNode.register(clientSibling)
79
+ await clientNode.attach(clientSibling.address, true)
80
+
81
+ // Create Host/Client Descendent Nodes
82
+ hostDescendent = await MemoryNode.create({ account })
83
+ clientDescendent = await MemoryNode.create({ account })
84
+
85
+ // Register Host/Client Siblings
86
+ await hostSibling.register(hostDescendent)
87
+ await hostSibling.attach(hostDescendent.address, true)
88
+ await clientSibling.register(clientDescendent)
89
+ await clientSibling.attach(clientDescendent.address, true)
90
+ })
91
+
92
+ describe('exposed module behavior', () => {
93
+ const cases: [string, () => MemoryNode][] = [
94
+ ['parent', () => hostNode],
95
+ ['sibling', () => hostSibling],
96
+ ['descendent', () => hostDescendent],
97
+ ]
98
+ describe.each(cases)('with %s module', (_, getSutModule) => {
99
+ let exposedMod: MemoryNode
100
+ beforeEach(() => {
101
+ exposedMod = getSutModule()
102
+ })
103
+ describe('before expose', () => {
104
+ it('should not be exposed', async () => {
105
+ expect(await hostBridge.exposed()).toBeEmpty()
106
+ })
107
+ it('should not be resolvable', async () => {
108
+ expect(await clientBridge.resolve(exposedMod.address)).toBeUndefined()
109
+ })
110
+ })
111
+ describe('after expose', () => {
112
+ beforeEach(async () => {
113
+ await hostBridge.expose(exposedMod.address)
114
+ })
115
+ it('should be exposed on host', async () => {
116
+ expect((await hostBridge.exposed()).includes(exposedMod.address)).toBeTrue()
117
+ })
118
+ it.skip('should be resolvable from client', async () => {
119
+ // TODO: Implement .connect on HttpBridgeExpress and call here before resolving
120
+ const result = await clientBridge.resolve(exposedMod.address)
121
+ expect(result).toBeDefined()
122
+ expect(asAttachableNodeInstance(result, () => `Failed to resolve correct object type [${result?.constructor.name}]`)).toBeDefined()
123
+ })
124
+ it.skip('should be queryable from client', async () => {
125
+ const bridgedHostedModule = await clientBridge.resolve(exposedMod.address)
126
+ expect(bridgedHostedModule).toBeDefined()
127
+
128
+ const bridgedHostedNode = asAttachableNodeInstance(
129
+ bridgedHostedModule,
130
+ () => `Failed to resolve correct object type [${bridgedHostedModule?.constructor.name}]`,
131
+ )
132
+
133
+ if (bridgedHostedNode) {
134
+ const state = await bridgedHostedNode.state()
135
+ const description = state.find(isPayloadOfSchemaType<ModuleDescriptionPayload>(ModuleDescriptionSchema))
136
+ expect(description?.children).toBeArray()
137
+ expect(description?.queries).toBeArray()
138
+ expect(description?.queries?.length).toBeGreaterThan(0)
139
+ }
140
+ })
141
+ })
142
+ describe('after unexpose', () => {
143
+ beforeEach(async () => {
144
+ await hostBridge.expose(exposedMod.address)
145
+ await hostBridge.unexpose(exposedMod.address)
146
+ })
147
+ it('should not be exposed', async () => {
148
+ expect(await hostBridge.exposed()).toBeEmpty()
149
+ })
150
+ it('should not be resolvable', async () => {
151
+ expect(await clientBridge.resolve(exposedMod.address)).toBeUndefined()
152
+ })
153
+ })
154
+ })
155
+ })
156
+ })
@@ -0,0 +1,55 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import { MemoryNode } from '@xyo-network/node-memory'
4
+ import type { NodeInstance } from '@xyo-network/node-model'
5
+ import { NodeConfigSchema } from '@xyo-network/node-model'
6
+ import {
7
+ describe, expect, it,
8
+ } from 'vitest'
9
+
10
+ import { HttpBridgeExpress, HttpBridgeExpressConfigSchema } from '../HttpBridge.ts'
11
+
12
+ /**
13
+ * @group module
14
+ * @group bridge
15
+ */
16
+
17
+ describe('HttpBridgeExpress', () => {
18
+ const baseUrl = `${process.env.API_DOMAIN ?? 'http://localhost:8080'}`
19
+
20
+ console.log(`HttpBridgeExpress:baseUrl ${baseUrl}`)
21
+ it('Discover', async () => {
22
+ const nodeUrl = `${baseUrl}/`
23
+ const memNode = await MemoryNode.create({ account: 'random', config: { name: 'MemoryNode', schema: NodeConfigSchema } })
24
+
25
+ const bridge = await HttpBridgeExpress.create({
26
+ account: 'random',
27
+ config: {
28
+ discoverRoots: 'start', name: 'HttpBridgeExpress', nodeUrl, schema: HttpBridgeExpressConfigSchema, security: { allowAnonymous: true },
29
+ },
30
+ })
31
+
32
+ await memNode.register(bridge)
33
+ await memNode.attach(bridge.address, true)
34
+
35
+ const publicNode = await bridge.resolve<NodeInstance>('XYOPublic')
36
+ expect(publicNode).toBeDefined()
37
+
38
+ if (publicNode) {
39
+ console.log(`publicNode[${publicNode.address}]: ${publicNode.modName}`)
40
+ const publicNodeModules = await publicNode.resolve('*', { direction: 'down' })
41
+ expect(publicNodeModules).toBeArray()
42
+ expect(publicNodeModules.length).toBeGreaterThan(0)
43
+ }
44
+
45
+ const bridgeModules = await bridge.resolve('*', { direction: 'down' })
46
+ expect(bridgeModules).toBeArray()
47
+ expect(bridgeModules.length).toBeGreaterThan(0)
48
+
49
+ /*
50
+ const modules = await memNode.resolve('*')
51
+ expect(modules).toBeArray()
52
+ expect(modules.length).toBeGreaterThan(20)
53
+ */
54
+ })
55
+ })