@dxos/functions 0.5.4 → 0.5.5-main.23d7ea6
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.
- package/README.md +1 -1
- package/dist/lib/browser/chunk-ERL6PHMU.mjs +1084 -0
- package/dist/lib/browser/chunk-ERL6PHMU.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +17 -1075
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +150 -0
- package/dist/lib/browser/testing/index.mjs.map +7 -0
- package/dist/lib/node/chunk-INM6XAL7.cjs +1097 -0
- package/dist/lib/node/chunk-INM6XAL7.cjs.map +7 -0
- package/dist/lib/node/index.cjs +15 -1069
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +171 -0
- package/dist/lib/node/testing/index.cjs.map +7 -0
- package/dist/types/src/function/function-registry.d.ts +1 -0
- package/dist/types/src/function/function-registry.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.d.ts +1 -0
- package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/manifest.d.ts +3 -0
- package/dist/types/src/testing/manifest.d.ts.map +1 -0
- package/dist/types/src/testing/plugin-init.d.ts +6 -0
- package/dist/types/src/testing/plugin-init.d.ts.map +1 -0
- package/dist/types/src/testing/setup.d.ts +11 -1
- package/dist/types/src/testing/setup.d.ts.map +1 -1
- package/dist/types/src/testing/util.d.ts +2 -0
- package/dist/types/src/testing/util.d.ts.map +1 -1
- package/package.json +23 -14
- package/src/function/function-registry.test.ts +14 -1
- package/src/function/function-registry.ts +10 -0
- package/src/runtime/dev-server.test.ts +42 -24
- package/src/runtime/dev-server.ts +17 -13
- package/src/runtime/scheduler.test.ts +4 -2
- package/src/testing/functions-integration.test.ts +8 -45
- package/src/testing/index.ts +1 -0
- package/src/testing/manifest.ts +15 -0
- package/src/testing/plugin-init.ts +20 -0
- package/src/testing/setup.ts +64 -6
- package/src/testing/util.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/functions",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5-main.23d7ea6",
|
|
4
4
|
"description": "Functions API and runtime.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"node": "./dist/lib/node/types.cjs",
|
|
19
19
|
"default": "./dist/lib/node/types.cjs",
|
|
20
20
|
"types": "./dist/types/src/types.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./testing": {
|
|
23
|
+
"import": "./dist/lib/browser/testing/index.mjs",
|
|
24
|
+
"require": "./dist/lib/node/testing/index.cjs",
|
|
25
|
+
"node": "./dist/lib/node/testing/index.cjs",
|
|
26
|
+
"types": "./dist/types/src/testing/index.d.ts"
|
|
21
27
|
}
|
|
22
28
|
},
|
|
23
29
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -25,6 +31,9 @@
|
|
|
25
31
|
"*": {
|
|
26
32
|
"types": [
|
|
27
33
|
"dist/types/src/types.d.ts"
|
|
34
|
+
],
|
|
35
|
+
"testing": [
|
|
36
|
+
"dist/types/src/testing/index.d.ts"
|
|
28
37
|
]
|
|
29
38
|
}
|
|
30
39
|
},
|
|
@@ -40,23 +49,23 @@
|
|
|
40
49
|
"express": "^4.19.2",
|
|
41
50
|
"get-port-please": "^3.1.1",
|
|
42
51
|
"ws": "^8.14.2",
|
|
43
|
-
"@braneframe/types": "0.5.
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/echo-
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/
|
|
49
|
-
"@dxos/
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/log": "0.5.
|
|
52
|
-
"@dxos/
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/util": "0.5.
|
|
52
|
+
"@braneframe/types": "0.5.5-main.23d7ea6",
|
|
53
|
+
"@dxos/client": "0.5.5-main.23d7ea6",
|
|
54
|
+
"@dxos/async": "0.5.5-main.23d7ea6",
|
|
55
|
+
"@dxos/echo-schema": "0.5.5-main.23d7ea6",
|
|
56
|
+
"@dxos/context": "0.5.5-main.23d7ea6",
|
|
57
|
+
"@dxos/echo-db": "0.5.5-main.23d7ea6",
|
|
58
|
+
"@dxos/invariant": "0.5.5-main.23d7ea6",
|
|
59
|
+
"@dxos/keys": "0.5.5-main.23d7ea6",
|
|
60
|
+
"@dxos/log": "0.5.5-main.23d7ea6",
|
|
61
|
+
"@dxos/protocols": "0.5.5-main.23d7ea6",
|
|
62
|
+
"@dxos/node-std": "0.5.5-main.23d7ea6",
|
|
63
|
+
"@dxos/util": "0.5.5-main.23d7ea6"
|
|
55
64
|
},
|
|
56
65
|
"devDependencies": {
|
|
57
66
|
"@types/express": "^4.17.17",
|
|
58
67
|
"@types/ws": "^7.4.0",
|
|
59
|
-
"@dxos/agent": "0.5.
|
|
68
|
+
"@dxos/agent": "0.5.5-main.23d7ea6"
|
|
60
69
|
},
|
|
61
70
|
"publishConfig": {
|
|
62
71
|
"access": "public"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
|
|
7
|
-
import { Trigger } from '@dxos/async';
|
|
7
|
+
import { sleep, Trigger } from '@dxos/async';
|
|
8
8
|
import { type Client } from '@dxos/client';
|
|
9
9
|
import { TestBuilder } from '@dxos/client/testing';
|
|
10
10
|
import { Context } from '@dxos/context';
|
|
@@ -39,6 +39,19 @@ describe('function registry', () => {
|
|
|
39
39
|
await testBuilder.destroy();
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
test('getUniqueByUri', async () => {
|
|
43
|
+
const client = (await createInitializedClients(testBuilder))[0];
|
|
44
|
+
const registry = createRegistry(client);
|
|
45
|
+
await registry.open();
|
|
46
|
+
for (let i = 0; i < 2; i++) {
|
|
47
|
+
const space = await client.spaces.create();
|
|
48
|
+
await registry.register(space, testManifest.functions);
|
|
49
|
+
}
|
|
50
|
+
await sleep(10);
|
|
51
|
+
const definitions = registry.getUniqueByUri();
|
|
52
|
+
expect(definitions.length).to.eq(testManifest.functions?.length);
|
|
53
|
+
});
|
|
54
|
+
|
|
42
55
|
describe('register', () => {
|
|
43
56
|
test('creates new functions', async () => {
|
|
44
57
|
const client = (await createInitializedClients(testBuilder))[0];
|
|
@@ -30,6 +30,16 @@ export class FunctionRegistry extends Resource {
|
|
|
30
30
|
return this._functionBySpaceKey.get(space.key) ?? [];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
public getUniqueByUri(): FunctionDef[] {
|
|
34
|
+
const uniqueByUri = [...this._functionBySpaceKey.values()]
|
|
35
|
+
.flatMap((defs) => defs)
|
|
36
|
+
.reduce((acc, v) => {
|
|
37
|
+
acc.set(v.uri, v);
|
|
38
|
+
return acc;
|
|
39
|
+
}, new Map<string, FunctionDef>());
|
|
40
|
+
return [...uniqueByUri.values()];
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
/**
|
|
34
44
|
* Loads function definitions from the manifest into the space.
|
|
35
45
|
* We first load all the definitions from the space to deduplicate by functionId.
|
|
@@ -3,24 +3,25 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
|
+
import { getRandomPort } from 'get-port-please';
|
|
6
7
|
import path from 'path';
|
|
7
8
|
|
|
8
|
-
import { waitForCondition } from '@dxos/async';
|
|
9
|
+
import { sleep, waitForCondition } from '@dxos/async';
|
|
9
10
|
import { type Client } from '@dxos/client';
|
|
10
11
|
import { TestBuilder } from '@dxos/client/testing';
|
|
11
12
|
import { describe, test } from '@dxos/test';
|
|
12
13
|
|
|
13
14
|
import { DevServer } from './dev-server';
|
|
14
15
|
import { FunctionRegistry } from '../function';
|
|
15
|
-
import { createFunctionRuntime } from '../testing';
|
|
16
|
-
import {
|
|
16
|
+
import { createFunctionRuntime, testFunctionManifest } from '../testing';
|
|
17
|
+
import { initFunctionsPlugin } from '../testing/plugin-init';
|
|
17
18
|
|
|
18
19
|
describe('dev server', () => {
|
|
19
20
|
let client: Client;
|
|
20
21
|
let testBuilder: TestBuilder;
|
|
21
22
|
before(async () => {
|
|
22
23
|
testBuilder = new TestBuilder();
|
|
23
|
-
client = await createFunctionRuntime(testBuilder);
|
|
24
|
+
client = await createFunctionRuntime(testBuilder, initFunctionsPlugin);
|
|
24
25
|
expect(client.services.services.FunctionRegistryService).to.exist;
|
|
25
26
|
});
|
|
26
27
|
|
|
@@ -28,33 +29,50 @@ describe('dev server', () => {
|
|
|
28
29
|
await testBuilder.destroy();
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
test('
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
test('function registry open after dev server started', async () => {
|
|
33
|
+
const { registry, server, space } = await setupTest();
|
|
34
|
+
await registry.register(space, testFunctionManifest.functions);
|
|
35
|
+
await server.start();
|
|
36
|
+
await registry.open();
|
|
37
|
+
await waitForCondition({ condition: () => server.functions.length > 0 });
|
|
38
|
+
await expectTestFunctionInvocable(server);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('function registry open before dev server started', async () => {
|
|
42
|
+
const { registry, server, space } = await setupTest();
|
|
43
|
+
await registry.register(space, testFunctionManifest.functions);
|
|
44
|
+
await registry.open();
|
|
45
|
+
await server.start();
|
|
46
|
+
await waitForCondition({ condition: () => server.functions.length > 0 });
|
|
47
|
+
await expectTestFunctionInvocable(server);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('unsubscribes from functions after stopped', async () => {
|
|
51
|
+
const { registry, server, space } = await setupTest();
|
|
52
|
+
await registry.register(space, testFunctionManifest.functions);
|
|
53
|
+
await server.start();
|
|
54
|
+
await server.stop();
|
|
55
|
+
await registry.open();
|
|
56
|
+
await sleep(20);
|
|
57
|
+
expect(server.functions.length).to.eq(0);
|
|
58
|
+
});
|
|
41
59
|
|
|
60
|
+
const expectTestFunctionInvocable = async (server: DevServer) => {
|
|
61
|
+
const seq = server.stats.seq;
|
|
62
|
+
await server.invoke('test', {});
|
|
63
|
+
expect(server.stats.seq).to.eq(seq + 1);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const setupTest = async () => {
|
|
42
67
|
const registry = new FunctionRegistry(client);
|
|
43
68
|
const server = new DevServer(client, registry, {
|
|
44
69
|
baseDir: path.join(__dirname, '../testing'),
|
|
70
|
+
port: await getRandomPort('127.0.0.1'),
|
|
45
71
|
});
|
|
46
72
|
const space = await client.spaces.create();
|
|
47
|
-
await registry.register(space, manifest.functions);
|
|
48
|
-
await server.start();
|
|
49
|
-
|
|
50
73
|
// TODO(burdon): Doesn't shut down cleanly.
|
|
51
74
|
// Error: invariant violation [this._client.services.services.FunctionRegistryService]
|
|
52
75
|
testBuilder.ctx.onDispose(() => server.stop());
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await waitForCondition({ condition: () => server.functions.length > 0 });
|
|
56
|
-
|
|
57
|
-
await server.invoke('test', {});
|
|
58
|
-
expect(server.stats.seq).to.eq(1);
|
|
59
|
-
});
|
|
76
|
+
return { registry, server, space };
|
|
77
|
+
};
|
|
60
78
|
});
|
|
@@ -7,7 +7,7 @@ import { getPort } from 'get-port-please';
|
|
|
7
7
|
import type http from 'http';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
|
|
10
|
-
import { Event, Trigger } from '@dxos/async';
|
|
10
|
+
import { asyncTimeout, Event, Trigger } from '@dxos/async';
|
|
11
11
|
import { type Client } from '@dxos/client';
|
|
12
12
|
import { Context } from '@dxos/context';
|
|
13
13
|
import { invariant } from '@dxos/invariant';
|
|
@@ -45,14 +45,7 @@ export class DevServer {
|
|
|
45
45
|
private readonly _client: Client,
|
|
46
46
|
private readonly _functionsRegistry: FunctionRegistry,
|
|
47
47
|
private readonly _options: DevServerOptions,
|
|
48
|
-
) {
|
|
49
|
-
// TODO(burdon): Add/remove listener in start/stop.
|
|
50
|
-
this._functionsRegistry.registered.on(async ({ added }) => {
|
|
51
|
-
added.forEach((def) => this._load(def));
|
|
52
|
-
await this._safeUpdateRegistration();
|
|
53
|
-
log('new functions loaded', { added });
|
|
54
|
-
});
|
|
55
|
-
}
|
|
48
|
+
) {}
|
|
56
49
|
|
|
57
50
|
get stats() {
|
|
58
51
|
return {
|
|
@@ -92,7 +85,7 @@ export class DevServer {
|
|
|
92
85
|
}
|
|
93
86
|
|
|
94
87
|
// TODO(burdon): Get function context.
|
|
95
|
-
res.statusCode = await this.invoke('/' + path, req.body);
|
|
88
|
+
res.statusCode = await asyncTimeout(this.invoke('/' + path, req.body), 20_000);
|
|
96
89
|
res.end();
|
|
97
90
|
} catch (err: any) {
|
|
98
91
|
log.catch(err);
|
|
@@ -101,7 +94,7 @@ export class DevServer {
|
|
|
101
94
|
}
|
|
102
95
|
});
|
|
103
96
|
|
|
104
|
-
this._port = await getPort({ host: 'localhost', port: 7200, portRange: [7200, 7299] });
|
|
97
|
+
this._port = this._options.port ?? (await getPort({ host: 'localhost', port: 7200, portRange: [7200, 7299] }));
|
|
105
98
|
this._server = app.listen(this._port);
|
|
106
99
|
|
|
107
100
|
try {
|
|
@@ -115,7 +108,8 @@ export class DevServer {
|
|
|
115
108
|
this._functionServiceRegistration = registrationId;
|
|
116
109
|
|
|
117
110
|
// Open after registration, so that it can be updated with the list of function definitions.
|
|
118
|
-
await this._functionsRegistry.
|
|
111
|
+
await this._handleNewFunctions(this._functionsRegistry.getUniqueByUri());
|
|
112
|
+
this._ctx.onDispose(this._functionsRegistry.registered.on(({ added }) => this._handleNewFunctions(added)));
|
|
119
113
|
} catch (err: any) {
|
|
120
114
|
await this.stop();
|
|
121
115
|
throw new Error('FunctionRegistryService not available (check plugin is configured).');
|
|
@@ -125,8 +119,12 @@ export class DevServer {
|
|
|
125
119
|
}
|
|
126
120
|
|
|
127
121
|
async stop() {
|
|
128
|
-
|
|
122
|
+
if (!this._server) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
129
126
|
log.info('stopping...');
|
|
127
|
+
await this._ctx.dispose();
|
|
130
128
|
|
|
131
129
|
const trigger = new Trigger();
|
|
132
130
|
this._server.close(async () => {
|
|
@@ -155,6 +153,12 @@ export class DevServer {
|
|
|
155
153
|
log.info('stopped');
|
|
156
154
|
}
|
|
157
155
|
|
|
156
|
+
private async _handleNewFunctions(newFunctions: FunctionDef[]) {
|
|
157
|
+
newFunctions.forEach((def) => this._load(def));
|
|
158
|
+
await this._safeUpdateRegistration();
|
|
159
|
+
log('new functions loaded', { newFunctions });
|
|
160
|
+
}
|
|
161
|
+
|
|
158
162
|
/**
|
|
159
163
|
* Load function.
|
|
160
164
|
*/
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
|
+
import { getRandomPort } from 'get-port-please';
|
|
6
7
|
import WebSocket from 'ws';
|
|
7
8
|
|
|
8
9
|
import { Trigger } from '@dxos/async';
|
|
@@ -108,6 +109,7 @@ describe('scheduler', () => {
|
|
|
108
109
|
});
|
|
109
110
|
|
|
110
111
|
test('websocket', async () => {
|
|
112
|
+
const port = await getRandomPort('127.0.0.1');
|
|
111
113
|
const manifest: FunctionManifest = {
|
|
112
114
|
functions: [
|
|
113
115
|
{
|
|
@@ -123,7 +125,7 @@ describe('scheduler', () => {
|
|
|
123
125
|
spec: {
|
|
124
126
|
type: 'websocket',
|
|
125
127
|
// url: 'https://hub.dxos.network/api/mailbox/test',
|
|
126
|
-
url:
|
|
128
|
+
url: `http://localhost:${port}`,
|
|
127
129
|
init: {
|
|
128
130
|
type: 'sync',
|
|
129
131
|
},
|
|
@@ -141,7 +143,7 @@ describe('scheduler', () => {
|
|
|
141
143
|
|
|
142
144
|
// Test server.
|
|
143
145
|
setTimeout(() => {
|
|
144
|
-
const wss = new WebSocket.Server({ port
|
|
146
|
+
const wss = new WebSocket.Server({ port });
|
|
145
147
|
wss.on('connection', (ws: WebSocket) => {
|
|
146
148
|
ws.on('message', (data) => {
|
|
147
149
|
const info = JSON.parse(new TextDecoder().decode(data as ArrayBuffer));
|
|
@@ -3,20 +3,15 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
|
-
import path from 'path';
|
|
7
6
|
|
|
8
|
-
import { Trigger
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { performInvitation, TestBuilder } from '@dxos/client/testing';
|
|
12
|
-
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
|
+
import { Trigger } from '@dxos/async';
|
|
8
|
+
import { create } from '@dxos/client/echo';
|
|
9
|
+
import { TestBuilder } from '@dxos/client/testing';
|
|
13
10
|
import { describe, test } from '@dxos/test';
|
|
14
11
|
|
|
12
|
+
import { initFunctionsPlugin } from './plugin-init';
|
|
15
13
|
import { setTestCallHandler } from './test/handler';
|
|
16
|
-
import {
|
|
17
|
-
import { DevServer, Scheduler } from '../runtime';
|
|
18
|
-
import { createFunctionRuntime, createInitializedClients, TestType } from '../testing';
|
|
19
|
-
import { TriggerRegistry } from '../trigger';
|
|
14
|
+
import { createInitializedClients, inviteMember, startFunctionsHost, TestType } from '../testing';
|
|
20
15
|
import { FunctionDef, FunctionTrigger } from '../types';
|
|
21
16
|
|
|
22
17
|
describe('functions e2e', () => {
|
|
@@ -30,13 +25,11 @@ describe('functions e2e', () => {
|
|
|
30
25
|
|
|
31
26
|
test('a function gets triggered in response to another peer object creations', async () => {
|
|
32
27
|
// TODO(burdon): Create builder pattern.
|
|
33
|
-
const functionRuntime = await
|
|
34
|
-
const devServer = await startDevServer(functionRuntime);
|
|
35
|
-
const scheduler = await startScheduler(functionRuntime, devServer);
|
|
28
|
+
const functionRuntime = await startFunctionsHost(testBuilder, initFunctionsPlugin);
|
|
36
29
|
|
|
37
30
|
const app = (await createInitializedClients(testBuilder, 1))[0];
|
|
38
31
|
const space = await app.spaces.create();
|
|
39
|
-
await inviteMember(space, functionRuntime);
|
|
32
|
+
await inviteMember(space, functionRuntime.client);
|
|
40
33
|
|
|
41
34
|
const uri = 'example.com/function/test';
|
|
42
35
|
space.db.add(create(FunctionDef, { uri, route: '/test', handler: 'test' }));
|
|
@@ -59,7 +52,7 @@ describe('functions e2e', () => {
|
|
|
59
52
|
return args.response.status(200);
|
|
60
53
|
});
|
|
61
54
|
|
|
62
|
-
await
|
|
55
|
+
await functionRuntime.waitHasActiveTriggers(space);
|
|
63
56
|
const addedObject = space.db.add(create(TestType, { title: '42' }));
|
|
64
57
|
|
|
65
58
|
const callArgs = await called.wait();
|
|
@@ -67,34 +60,4 @@ describe('functions e2e', () => {
|
|
|
67
60
|
expect(callArgs.objects).to.deep.eq([addedObject.id]);
|
|
68
61
|
expect(callArgs.spaceKey).to.eq(space.key.toHex());
|
|
69
62
|
});
|
|
70
|
-
|
|
71
|
-
const waitTriggersReplicated = async (space: Space, scheduler: Scheduler) => {
|
|
72
|
-
await waitForCondition({ condition: () => scheduler.triggers.getActiveTriggers(space).length > 0 });
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// TODO(burdon): Factor out utils to builder pattern.
|
|
76
|
-
|
|
77
|
-
const startScheduler = async (client: Client, devServer: DevServer) => {
|
|
78
|
-
const functionRegistry = new FunctionRegistry(client);
|
|
79
|
-
const triggerRegistry = new TriggerRegistry(client);
|
|
80
|
-
const scheduler = new Scheduler(functionRegistry, triggerRegistry, { endpoint: devServer.endpoint });
|
|
81
|
-
await scheduler.start();
|
|
82
|
-
testBuilder.ctx.onDispose(() => scheduler.stop());
|
|
83
|
-
return scheduler;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const startDevServer = async (client: Client) => {
|
|
87
|
-
const functionRegistry = new FunctionRegistry(client);
|
|
88
|
-
const server = new DevServer(client, functionRegistry, {
|
|
89
|
-
baseDir: path.join(__dirname, '../testing'),
|
|
90
|
-
});
|
|
91
|
-
await server.start();
|
|
92
|
-
testBuilder.ctx.onDispose(() => server.stop());
|
|
93
|
-
return server;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const inviteMember = async (host: Space, guest: Client) => {
|
|
97
|
-
const [{ invitation: hostInvitation }] = await Promise.all(performInvitation({ host, guest: guest.spaces }));
|
|
98
|
-
expect(hostInvitation?.state).to.eq(Invitation.State.SUCCESS);
|
|
99
|
-
};
|
|
100
63
|
});
|
package/src/testing/index.ts
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { FunctionManifest } from '../types';
|
|
6
|
+
|
|
7
|
+
export const testFunctionManifest: FunctionManifest = {
|
|
8
|
+
functions: [
|
|
9
|
+
{
|
|
10
|
+
uri: 'example.com/function/test',
|
|
11
|
+
route: 'test',
|
|
12
|
+
handler: 'test',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { FunctionsPlugin } from '@dxos/agent';
|
|
6
|
+
import { type Client } from '@dxos/client';
|
|
7
|
+
|
|
8
|
+
import { type FunctionsPluginInitializer } from './setup';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Deliberately in a non-exported file to keep @dxos/agent as a devDependency.
|
|
12
|
+
*/
|
|
13
|
+
export const initFunctionsPlugin: FunctionsPluginInitializer = async (client: Client) => {
|
|
14
|
+
const plugin = new FunctionsPlugin();
|
|
15
|
+
await plugin.initialize({ client, clientServices: client.services });
|
|
16
|
+
await plugin.open();
|
|
17
|
+
return {
|
|
18
|
+
close: () => plugin.close(),
|
|
19
|
+
};
|
|
20
|
+
};
|
package/src/testing/setup.ts
CHANGED
|
@@ -2,14 +2,23 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { getRandomPort } from 'get-port-please';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
import { waitForCondition } from '@dxos/async';
|
|
6
9
|
import { Client, Config } from '@dxos/client';
|
|
10
|
+
import { type Space } from '@dxos/client/echo';
|
|
7
11
|
import { type TestBuilder } from '@dxos/client/testing';
|
|
8
12
|
import { range } from '@dxos/util';
|
|
9
13
|
|
|
10
14
|
import { TestType } from './types';
|
|
15
|
+
import { FunctionRegistry } from '../function';
|
|
16
|
+
import { DevServer, type DevServerOptions, Scheduler } from '../runtime';
|
|
17
|
+
import { TriggerRegistry } from '../trigger';
|
|
11
18
|
import { FunctionDef, FunctionTrigger } from '../types';
|
|
12
19
|
|
|
20
|
+
export type FunctionsPluginInitializer = (client: Client) => Promise<{ close: () => Promise<void> }>;
|
|
21
|
+
|
|
13
22
|
// TODO(burdon): Extend/wrap TestBuilder.
|
|
14
23
|
|
|
15
24
|
export const createInitializedClients = async (testBuilder: TestBuilder, count: number = 1, config?: Config) => {
|
|
@@ -25,19 +34,68 @@ export const createInitializedClients = async (testBuilder: TestBuilder, count:
|
|
|
25
34
|
);
|
|
26
35
|
};
|
|
27
36
|
|
|
28
|
-
export const createFunctionRuntime = async (
|
|
37
|
+
export const createFunctionRuntime = async (
|
|
38
|
+
testBuilder: TestBuilder,
|
|
39
|
+
pluginInitializer: FunctionsPluginInitializer,
|
|
40
|
+
): Promise<Client> => {
|
|
41
|
+
const functionsPort = await getRandomPort('127.0.0.1');
|
|
29
42
|
const config = new Config({
|
|
30
43
|
runtime: {
|
|
31
44
|
agent: {
|
|
32
|
-
plugins: [{ id: 'dxos.org/agent/plugin/functions', config: { port:
|
|
45
|
+
plugins: [{ id: 'dxos.org/agent/plugin/functions', config: { port: functionsPort } }],
|
|
33
46
|
},
|
|
34
47
|
},
|
|
35
48
|
});
|
|
36
49
|
|
|
37
50
|
const [client] = await createInitializedClients(testBuilder, 1, config);
|
|
38
|
-
const plugin =
|
|
39
|
-
await plugin.initialize({ client, clientServices: client.services });
|
|
40
|
-
await plugin.open();
|
|
51
|
+
const plugin = await pluginInitializer(client);
|
|
41
52
|
testBuilder.ctx.onDispose(() => plugin.close());
|
|
42
53
|
return client;
|
|
43
54
|
};
|
|
55
|
+
|
|
56
|
+
export const startFunctionsHost = async (
|
|
57
|
+
testBuilder: TestBuilder,
|
|
58
|
+
pluginInitializer: FunctionsPluginInitializer,
|
|
59
|
+
options?: DevServerOptions,
|
|
60
|
+
) => {
|
|
61
|
+
const functionRuntime = await createFunctionRuntime(testBuilder, pluginInitializer);
|
|
62
|
+
const functionsRegistry = new FunctionRegistry(functionRuntime);
|
|
63
|
+
const devServer = await startDevServer(testBuilder, functionRuntime, functionsRegistry, options);
|
|
64
|
+
const scheduler = await startScheduler(testBuilder, functionRuntime, devServer, functionsRegistry);
|
|
65
|
+
return {
|
|
66
|
+
scheduler,
|
|
67
|
+
client: functionRuntime,
|
|
68
|
+
waitHasActiveTriggers: async (space: Space) => {
|
|
69
|
+
await waitForCondition({ condition: () => scheduler.triggers.getActiveTriggers(space).length > 0 });
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const startScheduler = async (
|
|
75
|
+
testBuilder: TestBuilder,
|
|
76
|
+
client: Client,
|
|
77
|
+
devServer: DevServer,
|
|
78
|
+
functionRegistry: FunctionRegistry,
|
|
79
|
+
) => {
|
|
80
|
+
const triggerRegistry = new TriggerRegistry(client);
|
|
81
|
+
const scheduler = new Scheduler(functionRegistry, triggerRegistry, { endpoint: devServer.endpoint });
|
|
82
|
+
await scheduler.start();
|
|
83
|
+
testBuilder.ctx.onDispose(() => scheduler.stop());
|
|
84
|
+
return scheduler;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const startDevServer = async (
|
|
88
|
+
testBuilder: TestBuilder,
|
|
89
|
+
client: Client,
|
|
90
|
+
functionRegistry: FunctionRegistry,
|
|
91
|
+
options?: { baseDir?: string },
|
|
92
|
+
) => {
|
|
93
|
+
const server = new DevServer(client, functionRegistry, {
|
|
94
|
+
baseDir: path.join(__dirname, '../testing'),
|
|
95
|
+
port: await getRandomPort('127.0.0.1'),
|
|
96
|
+
...options,
|
|
97
|
+
});
|
|
98
|
+
await server.start();
|
|
99
|
+
testBuilder.ctx.onDispose(() => server.stop());
|
|
100
|
+
return server;
|
|
101
|
+
};
|
package/src/testing/util.ts
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import type { Client } from '@dxos/client';
|
|
5
6
|
import { Filter, type Space } from '@dxos/client/echo';
|
|
7
|
+
import { performInvitation } from '@dxos/client/testing';
|
|
6
8
|
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
10
|
|
|
8
11
|
import { FunctionTrigger } from '../types';
|
|
9
12
|
|
|
@@ -14,3 +17,10 @@ export const triggerWebhook = async (space: Space, uri: string) => {
|
|
|
14
17
|
invariant(trigger.spec.type === 'webhook');
|
|
15
18
|
void fetch(`http://localhost:${trigger.spec.port}`);
|
|
16
19
|
};
|
|
20
|
+
|
|
21
|
+
export const inviteMember = async (host: Space, guest: Client) => {
|
|
22
|
+
const [{ invitation: hostInvitation }] = await Promise.all(performInvitation({ host, guest: guest.spaces }));
|
|
23
|
+
if (hostInvitation?.state !== Invitation.State.SUCCESS) {
|
|
24
|
+
throw new Error(`Expected ${hostInvitation?.state} to be ${Invitation.State.SUCCESS}.`);
|
|
25
|
+
}
|
|
26
|
+
};
|