@dxos/functions 0.5.3-main.43e79dd → 0.5.3-main.495a683
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/dist/lib/browser/chunk-366QG6IX.mjs +81 -0
- package/dist/lib/browser/chunk-366QG6IX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +849 -304
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types.mjs +12 -0
- package/dist/lib/browser/types.mjs.map +7 -0
- package/dist/lib/node/chunk-3VSJ57ZZ.cjs +97 -0
- package/dist/lib/node/chunk-3VSJ57ZZ.cjs.map +7 -0
- package/dist/lib/node/index.cjs +831 -297
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +33 -0
- package/dist/lib/node/types.cjs.map +7 -0
- package/dist/types/src/function/function-registry.d.ts +24 -0
- package/dist/types/src/function/function-registry.d.ts.map +1 -0
- package/dist/types/src/function/function-registry.test.d.ts +2 -0
- package/dist/types/src/function/function-registry.test.d.ts.map +1 -0
- package/dist/types/src/function/index.d.ts +2 -0
- package/dist/types/src/function/index.d.ts.map +1 -0
- package/dist/types/src/handler.d.ts +33 -12
- package/dist/types/src/handler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.d.ts +15 -7
- package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.test.d.ts +2 -0
- package/dist/types/src/runtime/dev-server.test.d.ts.map +1 -0
- package/dist/types/src/runtime/scheduler.d.ts +15 -15
- package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
- package/dist/types/src/testing/functions-integration.test.d.ts +2 -0
- package/dist/types/src/testing/functions-integration.test.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +4 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/setup.d.ts +5 -0
- package/dist/types/src/testing/setup.d.ts.map +1 -0
- package/dist/types/src/testing/test/handler.d.ts +4 -0
- package/dist/types/src/testing/test/handler.d.ts.map +1 -0
- package/dist/types/src/testing/test/index.d.ts +3 -0
- package/dist/types/src/testing/test/index.d.ts.map +1 -0
- package/dist/types/src/testing/types.d.ts +9 -0
- package/dist/types/src/testing/types.d.ts.map +1 -0
- package/dist/types/src/testing/util.d.ts +3 -0
- package/dist/types/src/testing/util.d.ts.map +1 -0
- package/dist/types/src/trigger/index.d.ts +2 -0
- package/dist/types/src/trigger/index.d.ts.map +1 -0
- package/dist/types/src/trigger/trigger-registry.d.ts +40 -0
- package/dist/types/src/trigger/trigger-registry.d.ts.map +1 -0
- package/dist/types/src/trigger/trigger-registry.test.d.ts +2 -0
- package/dist/types/src/trigger/trigger-registry.test.d.ts.map +1 -0
- package/dist/types/src/trigger/type/index.d.ts +5 -0
- package/dist/types/src/trigger/type/index.d.ts.map +1 -0
- package/dist/types/src/trigger/type/subscription-trigger.d.ts +4 -0
- package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +1 -0
- package/dist/types/src/trigger/type/timer-trigger.d.ts +4 -0
- package/dist/types/src/trigger/type/timer-trigger.d.ts.map +1 -0
- package/dist/types/src/trigger/type/webhook-trigger.d.ts +4 -0
- package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +1 -0
- package/dist/types/src/trigger/type/websocket-trigger.d.ts +13 -0
- package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +202 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/util.d.ts +15 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/dist/types/src/util.test.d.ts +2 -0
- package/dist/types/src/util.test.d.ts.map +1 -0
- package/dist/types/tools/schema.d.ts +2 -0
- package/dist/types/tools/schema.d.ts.map +1 -0
- package/package.json +38 -13
- package/schema/functions.json +206 -0
- package/src/function/function-registry.test.ts +105 -0
- package/src/function/function-registry.ts +90 -0
- package/src/function/index.ts +5 -0
- package/src/handler.ts +56 -26
- package/src/index.ts +3 -1
- package/src/runtime/dev-server.test.ts +60 -0
- package/src/runtime/dev-server.ts +104 -53
- package/src/runtime/scheduler.test.ts +154 -21
- package/src/runtime/scheduler.ts +83 -151
- package/src/testing/functions-integration.test.ts +99 -0
- package/src/testing/index.ts +7 -0
- package/src/testing/setup.ts +45 -0
- package/src/testing/test/handler.ts +15 -0
- package/src/testing/test/index.ts +7 -0
- package/src/testing/types.ts +9 -0
- package/src/testing/util.ts +16 -0
- package/src/trigger/index.ts +5 -0
- package/src/trigger/trigger-registry.test.ts +255 -0
- package/src/trigger/trigger-registry.ts +189 -0
- package/src/trigger/type/index.ts +8 -0
- package/src/trigger/type/subscription-trigger.ts +80 -0
- package/src/trigger/type/timer-trigger.ts +44 -0
- package/src/trigger/type/webhook-trigger.ts +47 -0
- package/src/trigger/type/websocket-trigger.ts +91 -0
- package/src/types.ts +103 -0
- package/src/util.test.ts +43 -0
- package/src/util.ts +48 -0
- package/dist/types/src/manifest.d.ts +0 -26
- package/dist/types/src/manifest.d.ts.map +0 -1
- package/src/manifest.ts +0 -42
package/src/runtime/scheduler.ts
CHANGED
|
@@ -2,206 +2,138 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import path from 'node:path';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { type Client, type PublicKey } from '@dxos/client';
|
|
10
|
-
import { type Space, Filter, createSubscription, type Query, getAutomergeObjectCore } from '@dxos/client/echo';
|
|
7
|
+
import { Mutex } from '@dxos/async';
|
|
8
|
+
import { type Space } from '@dxos/client/echo';
|
|
11
9
|
import { Context } from '@dxos/context';
|
|
12
|
-
import { invariant } from '@dxos/invariant';
|
|
13
10
|
import { log } from '@dxos/log';
|
|
14
|
-
import { ComplexMap } from '@dxos/util';
|
|
15
11
|
|
|
16
|
-
import { type
|
|
17
|
-
import { type
|
|
12
|
+
import { type FunctionRegistry } from '../function';
|
|
13
|
+
import { type FunctionEventMeta } from '../handler';
|
|
14
|
+
import { type TriggerRegistry } from '../trigger';
|
|
15
|
+
import { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../types';
|
|
18
16
|
|
|
19
|
-
type Callback = (data:
|
|
17
|
+
export type Callback = (data: any) => Promise<void | number>;
|
|
20
18
|
|
|
21
|
-
type SchedulerOptions = {
|
|
19
|
+
export type SchedulerOptions = {
|
|
22
20
|
endpoint?: string;
|
|
23
21
|
callback?: Callback;
|
|
24
22
|
};
|
|
25
23
|
|
|
26
24
|
/**
|
|
27
|
-
*
|
|
25
|
+
* The scheduler triggers function execution based on various triggers.
|
|
28
26
|
*/
|
|
29
|
-
// TODO(burdon): Create tests.
|
|
30
27
|
export class Scheduler {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{ ctx: Context; trigger: FunctionTrigger }
|
|
35
|
-
>(({ id, spaceKey }) => `${spaceKey.toHex()}:${id}`);
|
|
28
|
+
private _ctx = createContext();
|
|
29
|
+
|
|
30
|
+
private readonly _callMutex = new Mutex();
|
|
36
31
|
|
|
37
32
|
constructor(
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
public readonly functions: FunctionRegistry,
|
|
34
|
+
public readonly triggers: TriggerRegistry,
|
|
40
35
|
private readonly _options: SchedulerOptions = {},
|
|
41
|
-
) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
for (const trigger of this._manifest.triggers ?? []) {
|
|
48
|
-
await this.mount(new Context(), space, trigger);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
36
|
+
) {
|
|
37
|
+
this.functions.registered.on(async ({ space, added }) => {
|
|
38
|
+
await this._safeActivateTriggers(space, this.triggers.getInactiveTriggers(space), added);
|
|
39
|
+
});
|
|
40
|
+
this.triggers.registered.on(async ({ space, triggers }) => {
|
|
41
|
+
await this._safeActivateTriggers(space, triggers, this.functions.getFunctions(space));
|
|
51
42
|
});
|
|
52
43
|
}
|
|
53
44
|
|
|
54
|
-
async
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
async start() {
|
|
46
|
+
await this._ctx.dispose();
|
|
47
|
+
this._ctx = createContext();
|
|
48
|
+
await this.functions.open(this._ctx);
|
|
49
|
+
await this.triggers.open(this._ctx);
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// Currently supports only one trigger declaration per function.
|
|
66
|
-
const exists = this._mounts.get(key);
|
|
67
|
-
if (!exists) {
|
|
68
|
-
this._mounts.set(key, { ctx, trigger });
|
|
69
|
-
log('mount', { space: space.key, trigger });
|
|
70
|
-
if (ctx.disposed) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Timer.
|
|
75
|
-
if (trigger.schedule) {
|
|
76
|
-
this._createTimer(ctx, space, def, trigger);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Subscription.
|
|
80
|
-
for (const triggerSubscription of trigger.subscriptions ?? []) {
|
|
81
|
-
this._createSubscription(ctx, space, def, triggerSubscription);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
52
|
+
async stop() {
|
|
53
|
+
await this._ctx.dispose();
|
|
54
|
+
await this.functions.close();
|
|
55
|
+
await this.triggers.close();
|
|
84
56
|
}
|
|
85
57
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this._mounts.delete(key);
|
|
91
|
-
await ctx.dispose();
|
|
92
|
-
}
|
|
58
|
+
// TODO(burdon): Remove and update registries directly.
|
|
59
|
+
public async register(space: Space, manifest: FunctionManifest) {
|
|
60
|
+
await this.functions.register(space, manifest.functions);
|
|
61
|
+
await this.triggers.register(space, manifest);
|
|
93
62
|
}
|
|
94
63
|
|
|
95
|
-
private
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
invariant(trigger.schedule);
|
|
103
|
-
let last = 0;
|
|
104
|
-
let run = 0;
|
|
105
|
-
// https://www.npmjs.com/package/cron#constructor
|
|
106
|
-
const job = CronJob.from({
|
|
107
|
-
cronTime: trigger.schedule,
|
|
108
|
-
runOnInit: false,
|
|
109
|
-
onTick: () => {
|
|
110
|
-
// TODO(burdon): Check greater than 30s (use cron-parser).
|
|
111
|
-
const now = Date.now();
|
|
112
|
-
const delta = last ? now - last : 0;
|
|
113
|
-
last = now;
|
|
114
|
-
|
|
115
|
-
run++;
|
|
116
|
-
log.info('tick', { space: space.key.truncate(), count: run, delta });
|
|
117
|
-
task.schedule();
|
|
118
|
-
},
|
|
64
|
+
private async _safeActivateTriggers(
|
|
65
|
+
space: Space,
|
|
66
|
+
triggers: FunctionTrigger[],
|
|
67
|
+
functions: FunctionDef[],
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
const mountTasks = triggers.map((trigger) => {
|
|
70
|
+
return this.activate(space, functions, trigger);
|
|
119
71
|
});
|
|
120
|
-
|
|
121
|
-
job.start();
|
|
122
|
-
ctx.onDispose(() => job.stop());
|
|
72
|
+
await Promise.all(mountTasks).catch(log.catch);
|
|
123
73
|
}
|
|
124
74
|
|
|
125
|
-
private
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
objects: Array.from(objectIds),
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// TODO(burdon): Don't fire initially.
|
|
136
|
-
// TODO(burdon): Standardize subscription handles.
|
|
137
|
-
const subscriptions: (() => void)[] = [];
|
|
138
|
-
const subscription = createSubscription(({ added, updated }) => {
|
|
139
|
-
log.info('updated', { added: added.length, updated: updated.length });
|
|
140
|
-
for (const object of added) {
|
|
141
|
-
objectIds.add(object.id);
|
|
142
|
-
}
|
|
143
|
-
for (const object of updated) {
|
|
144
|
-
objectIds.add(object.id);
|
|
145
|
-
}
|
|
75
|
+
private async activate(space: Space, functions: FunctionDef[], fnTrigger: FunctionTrigger) {
|
|
76
|
+
const definition = functions.find((def) => def.uri === fnTrigger.function);
|
|
77
|
+
if (!definition) {
|
|
78
|
+
log.info('function is not found for trigger', { fnTrigger });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
146
81
|
|
|
147
|
-
|
|
82
|
+
await this.triggers.activate({ space }, fnTrigger, async (args) => {
|
|
83
|
+
return this._callMutex.executeSynchronized(() => {
|
|
84
|
+
return this._execFunction(definition, fnTrigger, {
|
|
85
|
+
meta: fnTrigger.meta,
|
|
86
|
+
data: { ...args, spaceKey: space.key },
|
|
87
|
+
});
|
|
88
|
+
});
|
|
148
89
|
});
|
|
149
|
-
subscriptions.push(() => subscription.unsubscribe());
|
|
150
|
-
|
|
151
|
-
// TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
|
|
152
|
-
// TODO(burdon): Disable trigger if keeps failing.
|
|
153
|
-
const { type, props, deep, delay } = triggerSubscription;
|
|
154
|
-
const update = ({ objects }: Query) => {
|
|
155
|
-
subscription.update(objects);
|
|
156
|
-
|
|
157
|
-
// TODO(burdon): Hack to monitor changes to Document's text object.
|
|
158
|
-
if (deep) {
|
|
159
|
-
log.info('update', { type, deep, objects: objects.length });
|
|
160
|
-
for (const object of objects) {
|
|
161
|
-
const content = object.content;
|
|
162
|
-
if (content instanceof TextV0Type) {
|
|
163
|
-
subscriptions.push(
|
|
164
|
-
getAutomergeObjectCore(content).updates.on(debounce(() => subscription.update([object]), 1_000)),
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// TODO(burdon): [Bug]: all callbacks are fired on the first mutation.
|
|
172
|
-
// TODO(burdon): [Bug]: not updated when document is deleted (either top or hierarchically).
|
|
173
|
-
const query = space.db.query(Filter.typename(type, props));
|
|
174
|
-
subscriptions.push(query.subscribe(delay ? debounce(update, delay * 1_000) : update));
|
|
175
90
|
|
|
176
|
-
|
|
177
|
-
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
178
|
-
});
|
|
91
|
+
log('activated trigger', { space: space.key, trigger: fnTrigger });
|
|
179
92
|
}
|
|
180
93
|
|
|
181
|
-
private async _execFunction
|
|
94
|
+
private async _execFunction<TData, TMeta>(
|
|
95
|
+
def: FunctionDef,
|
|
96
|
+
trigger: FunctionTrigger,
|
|
97
|
+
{ data, meta }: { data: TData; meta?: TMeta },
|
|
98
|
+
): Promise<number> {
|
|
99
|
+
let status = 0;
|
|
182
100
|
try {
|
|
183
|
-
|
|
101
|
+
// TODO(burdon): Pass in Space key (common context)?
|
|
102
|
+
const payload = Object.assign({}, meta && ({ meta } satisfies FunctionEventMeta<TMeta>), data);
|
|
103
|
+
|
|
184
104
|
const { endpoint, callback } = this._options;
|
|
185
|
-
let status = 0;
|
|
186
105
|
if (endpoint) {
|
|
187
106
|
// TODO(burdon): Move out of scheduler (generalize as callback).
|
|
188
|
-
const
|
|
107
|
+
const url = path.join(endpoint, def.route);
|
|
108
|
+
log.info('exec', { function: def.uri, url, triggerType: trigger.spec.type });
|
|
109
|
+
const response = await fetch(url, {
|
|
189
110
|
method: 'POST',
|
|
190
111
|
headers: {
|
|
191
112
|
'Content-Type': 'application/json',
|
|
192
113
|
},
|
|
193
|
-
body: JSON.stringify(
|
|
114
|
+
body: JSON.stringify(payload),
|
|
194
115
|
});
|
|
195
116
|
|
|
196
117
|
status = response.status;
|
|
197
118
|
} else if (callback) {
|
|
198
|
-
|
|
119
|
+
log.info('exec', { function: def.uri });
|
|
120
|
+
status = (await callback(payload)) ?? 200;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check errors.
|
|
124
|
+
if (status && status >= 400) {
|
|
125
|
+
throw new Error(`Response: ${status}`);
|
|
199
126
|
}
|
|
200
127
|
|
|
201
128
|
// const result = await response.json();
|
|
202
|
-
log('
|
|
129
|
+
log.info('done', { function: def.uri, status });
|
|
203
130
|
} catch (err: any) {
|
|
204
|
-
log.error('error', { function: def.
|
|
131
|
+
log.error('error', { function: def.uri, error: err.message });
|
|
132
|
+
status = 500;
|
|
205
133
|
}
|
|
134
|
+
|
|
135
|
+
return status;
|
|
206
136
|
}
|
|
207
137
|
}
|
|
138
|
+
|
|
139
|
+
const createContext = () => new Context({ name: 'FunctionScheduler' });
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
import { Trigger, waitForCondition } from '@dxos/async';
|
|
9
|
+
import { type Client } from '@dxos/client';
|
|
10
|
+
import { create, type Space } from '@dxos/client/echo';
|
|
11
|
+
import { performInvitation, TestBuilder } from '@dxos/client/testing';
|
|
12
|
+
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
13
|
+
import { describe, test } from '@dxos/test';
|
|
14
|
+
|
|
15
|
+
import { setTestCallHandler } from './test/handler';
|
|
16
|
+
import { FunctionRegistry } from '../function';
|
|
17
|
+
import { DevServer, Scheduler } from '../runtime';
|
|
18
|
+
import { createFunctionRuntime, createInitializedClients, TestType } from '../testing';
|
|
19
|
+
import { TriggerRegistry } from '../trigger';
|
|
20
|
+
import { FunctionDef, FunctionTrigger } from '../types';
|
|
21
|
+
|
|
22
|
+
describe('functions e2e', () => {
|
|
23
|
+
let testBuilder: TestBuilder;
|
|
24
|
+
before(async () => {
|
|
25
|
+
testBuilder = new TestBuilder();
|
|
26
|
+
});
|
|
27
|
+
after(async () => {
|
|
28
|
+
await testBuilder.destroy();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('a function gets triggered in response to another peer object creations', async () => {
|
|
32
|
+
// TODO(burdon): Create builder pattern.
|
|
33
|
+
const functionRuntime = await createFunctionRuntime(testBuilder);
|
|
34
|
+
const devServer = await startDevServer(functionRuntime);
|
|
35
|
+
const scheduler = await startScheduler(functionRuntime, devServer);
|
|
36
|
+
|
|
37
|
+
const app = (await createInitializedClients(testBuilder, 1))[0];
|
|
38
|
+
const space = await app.spaces.create();
|
|
39
|
+
await inviteMember(space, functionRuntime);
|
|
40
|
+
|
|
41
|
+
const uri = 'example.com/function/test';
|
|
42
|
+
space.db.add(create(FunctionDef, { uri, route: '/test', handler: 'test' }));
|
|
43
|
+
const triggerMeta: FunctionTrigger['meta'] = { name: 'DXOS' };
|
|
44
|
+
space.db.add(
|
|
45
|
+
create(FunctionTrigger, {
|
|
46
|
+
function: uri,
|
|
47
|
+
meta: triggerMeta,
|
|
48
|
+
spec: {
|
|
49
|
+
type: 'subscription',
|
|
50
|
+
filter: [{ type: TestType.typename }],
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const called = new Trigger<any>();
|
|
56
|
+
setTestCallHandler(async (args) => {
|
|
57
|
+
called.wake(args.event.data);
|
|
58
|
+
return args.response.status(200);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await waitTriggersReplicated(space, scheduler);
|
|
62
|
+
const addedObject = space.db.add(create(TestType, { title: '42' }));
|
|
63
|
+
|
|
64
|
+
const callArgs = await called.wait();
|
|
65
|
+
expect(callArgs.meta).to.deep.eq(triggerMeta);
|
|
66
|
+
expect(callArgs.objects).to.deep.eq([addedObject.id]);
|
|
67
|
+
expect(callArgs.spaceKey).to.eq(space.key.toHex());
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const waitTriggersReplicated = async (space: Space, scheduler: Scheduler) => {
|
|
71
|
+
await waitForCondition({ condition: () => scheduler.triggers.getActiveTriggers(space).length > 0 });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// TODO(burdon): Factor out utils to builder pattern.
|
|
75
|
+
|
|
76
|
+
const startScheduler = async (client: Client, devServer: DevServer) => {
|
|
77
|
+
const functionRegistry = new FunctionRegistry(client);
|
|
78
|
+
const triggerRegistry = new TriggerRegistry(client);
|
|
79
|
+
const scheduler = new Scheduler(functionRegistry, triggerRegistry, { endpoint: devServer.endpoint });
|
|
80
|
+
await scheduler.start();
|
|
81
|
+
testBuilder.ctx.onDispose(() => scheduler.stop());
|
|
82
|
+
return scheduler;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const startDevServer = async (client: Client) => {
|
|
86
|
+
const functionRegistry = new FunctionRegistry(client);
|
|
87
|
+
const server = new DevServer(client, functionRegistry, {
|
|
88
|
+
baseDir: path.join(__dirname, '../testing'),
|
|
89
|
+
});
|
|
90
|
+
await server.start();
|
|
91
|
+
testBuilder.ctx.onDispose(() => server.stop());
|
|
92
|
+
return server;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const inviteMember = async (host: Space, guest: Client) => {
|
|
96
|
+
const [{ invitation: hostInvitation }] = await Promise.all(performInvitation({ host, guest: guest.spaces }));
|
|
97
|
+
expect(hostInvitation?.state).to.eq(Invitation.State.SUCCESS);
|
|
98
|
+
};
|
|
99
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { FunctionsPlugin } from '@dxos/agent';
|
|
6
|
+
import { Client, Config } from '@dxos/client';
|
|
7
|
+
import { type TestBuilder } from '@dxos/client/testing';
|
|
8
|
+
import { range } from '@dxos/util';
|
|
9
|
+
|
|
10
|
+
import { TestType } from './types';
|
|
11
|
+
import { FunctionDef, FunctionTrigger } from '../types';
|
|
12
|
+
|
|
13
|
+
// TODO(burdon): Create new or extend existing TestBuilder.
|
|
14
|
+
|
|
15
|
+
export const createInitializedClients = async (testBuilder: TestBuilder, count: number = 1, config?: Config) => {
|
|
16
|
+
const clients = range(count).map(() => new Client({ config, services: testBuilder.createLocalClientServices() }));
|
|
17
|
+
testBuilder.ctx.onDispose(() => Promise.all(clients.map((c) => c.destroy())));
|
|
18
|
+
return Promise.all(
|
|
19
|
+
clients.map(async (client, index) => {
|
|
20
|
+
await client.initialize();
|
|
21
|
+
await client.halo.createIdentity({ displayName: `Peer ${index}` });
|
|
22
|
+
client.addSchema(TestType, FunctionDef, FunctionTrigger);
|
|
23
|
+
return client;
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const createFunctionRuntime = async (testBuilder: TestBuilder): Promise<Client> => {
|
|
29
|
+
const config = new Config({
|
|
30
|
+
runtime: {
|
|
31
|
+
agent: {
|
|
32
|
+
plugins: [{ id: 'dxos.org/agent/plugin/functions', config: { port: 8080 } }],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const client = (await createInitializedClients(testBuilder, 1, config))[0];
|
|
38
|
+
|
|
39
|
+
// TODO(burdon): Better way to configure plugin? (Rationalize chess.test).
|
|
40
|
+
const functionsPlugin = new FunctionsPlugin();
|
|
41
|
+
await functionsPlugin.initialize({ client, clientServices: client.services });
|
|
42
|
+
await functionsPlugin.open();
|
|
43
|
+
testBuilder.ctx.onDispose(() => functionsPlugin.close());
|
|
44
|
+
return client;
|
|
45
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type FunctionHandler } from '../../handler';
|
|
6
|
+
|
|
7
|
+
let callHandler: FunctionHandler<any> = async ({ response }) => response.status(200);
|
|
8
|
+
|
|
9
|
+
export const setTestCallHandler = (handler: FunctionHandler<any>) => {
|
|
10
|
+
callHandler = handler;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const handler: FunctionHandler<any> = async (args) => {
|
|
14
|
+
return callHandler(args);
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Filter, type Space } from '@dxos/client/echo';
|
|
6
|
+
import { invariant } from '@dxos/invariant';
|
|
7
|
+
|
|
8
|
+
import { FunctionTrigger } from '../types';
|
|
9
|
+
|
|
10
|
+
export const triggerWebhook = async (space: Space, uri: string) => {
|
|
11
|
+
const trigger = (
|
|
12
|
+
await space.db.query(Filter.schema(FunctionTrigger, (t: FunctionTrigger) => t.function === uri)).run()
|
|
13
|
+
).objects[0];
|
|
14
|
+
invariant(trigger.spec.type === 'webhook');
|
|
15
|
+
void fetch(`http://localhost:${trigger.spec.port}`);
|
|
16
|
+
};
|