@dxos/functions 0.5.3-main.6f2dfea → 0.5.3-main.77c09ab
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-4D4I3YMJ.mjs +86 -0
- package/dist/lib/browser/chunk-4D4I3YMJ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +772 -477
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types.mjs +14 -0
- package/dist/lib/browser/types.mjs.map +7 -0
- package/dist/lib/node/chunk-3UYUR5N5.cjs +103 -0
- package/dist/lib/node/chunk-3UYUR5N5.cjs.map +7 -0
- package/dist/lib/node/index.cjs +759 -470
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +35 -0
- package/dist/lib/node/types.cjs.map +7 -0
- package/dist/types/src/browser/index.d.ts +2 -0
- package/dist/types/src/browser/index.d.ts.map +1 -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 +32 -12
- package/dist/types/src/handler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.d.ts +7 -10
- package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
- package/dist/types/src/runtime/scheduler.d.ts +11 -59
- 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 +1 -0
- package/dist/types/src/testing/test/handler.d.ts.map +1 -1
- 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 +37 -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 +152 -119
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +30 -14
- package/schema/functions.json +144 -111
- package/src/browser/index.ts +5 -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 +50 -27
- package/src/index.ts +2 -0
- package/src/runtime/dev-server.test.ts +15 -35
- package/src/runtime/dev-server.ts +42 -25
- package/src/runtime/scheduler.test.ts +59 -75
- package/src/runtime/scheduler.ts +79 -299
- package/src/testing/functions-integration.test.ts +100 -0
- package/src/testing/index.ts +7 -0
- package/src/testing/setup.ts +43 -0
- package/src/testing/test/handler.ts +8 -2
- 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 +272 -0
- package/src/trigger/trigger-registry.ts +211 -0
- package/src/trigger/type/index.ts +8 -0
- package/src/trigger/type/subscription-trigger.ts +86 -0
- package/src/trigger/type/timer-trigger.ts +45 -0
- package/src/trigger/type/webhook-trigger.ts +48 -0
- package/src/trigger/type/websocket-trigger.ts +92 -0
- package/src/types.ts +82 -52
package/src/handler.ts
CHANGED
|
@@ -8,38 +8,61 @@ import { type EchoReactiveObject } from '@dxos/echo-schema';
|
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
9
|
import { nonNullable } from '@dxos/util';
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// TODO(burdon): Model after http request. Ref Lambda/OpenFaaS.
|
|
12
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
|
|
12
13
|
// https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/#functions
|
|
13
14
|
// https://www.npmjs.com/package/aws-lambda
|
|
14
|
-
// https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
16
|
+
/**
|
|
17
|
+
* Function handler.
|
|
18
|
+
*/
|
|
19
|
+
export type FunctionHandler<TData = {}, TMeta = {}> = (params: {
|
|
20
|
+
context: FunctionContext;
|
|
21
|
+
event: FunctionEvent<TData, TMeta>;
|
|
22
|
+
response: FunctionResponse;
|
|
23
|
+
}) => Promise<FunctionResponse | void>;
|
|
20
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Function context.
|
|
27
|
+
*/
|
|
21
28
|
export interface FunctionContext {
|
|
22
29
|
// TODO(burdon): Limit access to individual space.
|
|
23
30
|
client: Client;
|
|
24
31
|
// TODO(burdon): Replace with storage service abstraction.
|
|
25
32
|
dataDir?: string;
|
|
26
|
-
// Data passed to function invocation.
|
|
27
|
-
data?: any;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
35
|
+
/**
|
|
36
|
+
* Event payload.
|
|
37
|
+
*/
|
|
38
|
+
export type FunctionEvent<TData = {}, TMeta = {}> = {
|
|
39
|
+
data: FunctionEventMeta<TMeta> & TData;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Metadata from trigger.
|
|
44
|
+
*/
|
|
45
|
+
export type FunctionEventMeta<TMeta = {}> = {
|
|
46
|
+
meta: TMeta;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Function response.
|
|
51
|
+
*/
|
|
52
|
+
export interface FunctionResponse {
|
|
53
|
+
status(code: number): FunctionResponse;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//
|
|
57
|
+
// Subscription utils.
|
|
58
|
+
//
|
|
36
59
|
|
|
37
|
-
export type
|
|
60
|
+
export type RawSubscriptionData = {
|
|
38
61
|
spaceKey?: string;
|
|
39
62
|
objects?: string[];
|
|
40
63
|
};
|
|
41
64
|
|
|
42
|
-
export type
|
|
65
|
+
export type SubscriptionData = {
|
|
43
66
|
space?: Space;
|
|
44
67
|
objects?: EchoReactiveObject<any>[];
|
|
45
68
|
};
|
|
@@ -54,22 +77,22 @@ export type SubscriptionEvent = {
|
|
|
54
77
|
*
|
|
55
78
|
* NOTE: Get space key from devtools or `dx space list --json`
|
|
56
79
|
*/
|
|
57
|
-
export const subscriptionHandler = (
|
|
58
|
-
handler: FunctionHandler<
|
|
59
|
-
): FunctionHandler<
|
|
60
|
-
return ({ event, context, ...rest }) => {
|
|
80
|
+
export const subscriptionHandler = <TMeta>(
|
|
81
|
+
handler: FunctionHandler<SubscriptionData, TMeta>,
|
|
82
|
+
): FunctionHandler<RawSubscriptionData, TMeta> => {
|
|
83
|
+
return ({ event: { data }, context, ...rest }) => {
|
|
61
84
|
const { client } = context;
|
|
62
|
-
const space =
|
|
63
|
-
const objects =
|
|
64
|
-
space
|
|
65
|
-
|
|
85
|
+
const space = data.spaceKey ? client.spaces.get(PublicKey.from(data.spaceKey)) : undefined;
|
|
86
|
+
const objects = space
|
|
87
|
+
? data.objects?.map<EchoReactiveObject<any> | undefined>((id) => space!.db.getObjectById(id)).filter(nonNullable)
|
|
88
|
+
: [];
|
|
66
89
|
|
|
67
|
-
if (!!
|
|
68
|
-
log.warn('invalid space', {
|
|
90
|
+
if (!!data.spaceKey && !space) {
|
|
91
|
+
log.warn('invalid space', { data });
|
|
69
92
|
} else {
|
|
70
93
|
log.info('handler', { space: space?.key.truncate(), objects: objects?.length });
|
|
71
94
|
}
|
|
72
95
|
|
|
73
|
-
return handler({ event: { space, objects }, context, ...rest });
|
|
96
|
+
return handler({ event: { data: { ...data, space, objects } }, context, ...rest });
|
|
74
97
|
};
|
|
75
98
|
};
|
package/src/index.ts
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { Client
|
|
8
|
+
import { waitForCondition } from '@dxos/async';
|
|
9
|
+
import { type Client } from '@dxos/client';
|
|
10
10
|
import { TestBuilder } from '@dxos/client/testing';
|
|
11
|
-
import { describe,
|
|
11
|
+
import { describe, test } from '@dxos/test';
|
|
12
12
|
|
|
13
13
|
import { DevServer } from './dev-server';
|
|
14
|
+
import { FunctionRegistry } from '../function';
|
|
15
|
+
import { createFunctionRuntime } from '../testing';
|
|
14
16
|
import { type FunctionManifest } from '../types';
|
|
15
17
|
|
|
16
18
|
describe('dev server', () => {
|
|
@@ -18,35 +20,10 @@ describe('dev server', () => {
|
|
|
18
20
|
let testBuilder: TestBuilder;
|
|
19
21
|
before(async () => {
|
|
20
22
|
testBuilder = new TestBuilder();
|
|
21
|
-
|
|
22
|
-
runtime: {
|
|
23
|
-
agent: {
|
|
24
|
-
plugins: [
|
|
25
|
-
{
|
|
26
|
-
id: 'dxos.org/agent/plugin/functions',
|
|
27
|
-
config: {
|
|
28
|
-
port: 8080,
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const services = testBuilder.createLocalClientServices();
|
|
37
|
-
client = new Client({ config, services });
|
|
38
|
-
|
|
39
|
-
await client.initialize();
|
|
40
|
-
await client.halo.createIdentity();
|
|
41
|
-
testBuilder.ctx.onDispose(() => client.destroy());
|
|
42
|
-
|
|
43
|
-
// TODO(burdon): Better way to configure plugin? (Rationalize chess.test).
|
|
44
|
-
const functionsPlugin = new FunctionsPlugin();
|
|
45
|
-
await functionsPlugin.initialize({ client, clientServices: services });
|
|
46
|
-
await openAndClose(functionsPlugin);
|
|
47
|
-
|
|
23
|
+
client = await createFunctionRuntime(testBuilder);
|
|
48
24
|
expect(client.services.services.FunctionRegistryService).to.exist;
|
|
49
25
|
});
|
|
26
|
+
|
|
50
27
|
after(async () => {
|
|
51
28
|
await testBuilder.destroy();
|
|
52
29
|
});
|
|
@@ -55,18 +32,19 @@ describe('dev server', () => {
|
|
|
55
32
|
const manifest: FunctionManifest = {
|
|
56
33
|
functions: [
|
|
57
34
|
{
|
|
58
|
-
|
|
59
|
-
|
|
35
|
+
uri: 'example.com/function/test',
|
|
36
|
+
route: 'test',
|
|
60
37
|
handler: 'test',
|
|
61
38
|
},
|
|
62
39
|
],
|
|
63
40
|
};
|
|
64
41
|
|
|
65
|
-
const
|
|
66
|
-
|
|
42
|
+
const registry = new FunctionRegistry(client);
|
|
43
|
+
const server = new DevServer(client, registry, {
|
|
67
44
|
baseDir: path.join(__dirname, '../testing'),
|
|
68
45
|
});
|
|
69
|
-
await
|
|
46
|
+
const space = await client.spaces.create();
|
|
47
|
+
await registry.register(space, manifest.functions);
|
|
70
48
|
await server.start();
|
|
71
49
|
|
|
72
50
|
// TODO(burdon): Doesn't shut down cleanly.
|
|
@@ -74,6 +52,8 @@ describe('dev server', () => {
|
|
|
74
52
|
testBuilder.ctx.onDispose(() => server.stop());
|
|
75
53
|
expect(server).to.exist;
|
|
76
54
|
|
|
55
|
+
await waitForCondition({ condition: () => server.functions.length > 0 });
|
|
56
|
+
|
|
77
57
|
await server.invoke('test', {});
|
|
78
58
|
expect(server.stats.seq).to.eq(1);
|
|
79
59
|
});
|
|
@@ -9,14 +9,15 @@ import { join } from 'node:path';
|
|
|
9
9
|
|
|
10
10
|
import { Event, Trigger } from '@dxos/async';
|
|
11
11
|
import { type Client } from '@dxos/client';
|
|
12
|
+
import { Context } from '@dxos/context';
|
|
12
13
|
import { invariant } from '@dxos/invariant';
|
|
13
14
|
import { log } from '@dxos/log';
|
|
14
15
|
|
|
15
|
-
import { type
|
|
16
|
-
import { type
|
|
16
|
+
import { type FunctionRegistry } from '../function';
|
|
17
|
+
import { type FunctionContext, type FunctionEvent, type FunctionHandler, type FunctionResponse } from '../handler';
|
|
18
|
+
import { type FunctionDef } from '../types';
|
|
17
19
|
|
|
18
20
|
export type DevServerOptions = {
|
|
19
|
-
manifest: FunctionManifest;
|
|
20
21
|
baseDir: string;
|
|
21
22
|
port?: number;
|
|
22
23
|
reload?: boolean;
|
|
@@ -27,6 +28,8 @@ export type DevServerOptions = {
|
|
|
27
28
|
* Functions dev server provides a local HTTP server for testing functions.
|
|
28
29
|
*/
|
|
29
30
|
export class DevServer {
|
|
31
|
+
private _ctx = createContext();
|
|
32
|
+
|
|
30
33
|
// Function handlers indexed by name (URL path).
|
|
31
34
|
private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};
|
|
32
35
|
|
|
@@ -38,11 +41,18 @@ export class DevServer {
|
|
|
38
41
|
|
|
39
42
|
public readonly update = new Event<number>();
|
|
40
43
|
|
|
41
|
-
// prettier-ignore
|
|
42
44
|
constructor(
|
|
43
45
|
private readonly _client: Client,
|
|
46
|
+
private readonly _functionsRegistry: FunctionRegistry,
|
|
44
47
|
private readonly _options: DevServerOptions,
|
|
45
|
-
) {
|
|
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
|
+
}
|
|
46
56
|
|
|
47
57
|
get stats() {
|
|
48
58
|
return {
|
|
@@ -63,19 +73,10 @@ export class DevServer {
|
|
|
63
73
|
return Object.values(this._handlers);
|
|
64
74
|
}
|
|
65
75
|
|
|
66
|
-
async initialize() {
|
|
67
|
-
for (const def of this._options.manifest.functions) {
|
|
68
|
-
try {
|
|
69
|
-
await this._load(def);
|
|
70
|
-
} catch (err) {
|
|
71
|
-
log.error('parsing function (check manifest)', err);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
76
|
async start() {
|
|
77
77
|
invariant(!this._server);
|
|
78
78
|
log.info('starting...');
|
|
79
|
+
this._ctx = createContext();
|
|
79
80
|
|
|
80
81
|
// TODO(burdon): Move to hono.
|
|
81
82
|
const app = express();
|
|
@@ -107,12 +108,14 @@ export class DevServer {
|
|
|
107
108
|
// Register functions.
|
|
108
109
|
const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({
|
|
109
110
|
endpoint: this.endpoint,
|
|
110
|
-
functions: this.functions.map(({ def: { id, path } }) => ({ id, path })),
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
log.info('registered', { endpoint });
|
|
114
114
|
this._proxy = endpoint;
|
|
115
115
|
this._functionServiceRegistration = registrationId;
|
|
116
|
+
|
|
117
|
+
// Open after registration, so that it can be updated with the list of function definitions.
|
|
118
|
+
await this._functionsRegistry.open(this._ctx);
|
|
116
119
|
} catch (err: any) {
|
|
117
120
|
await this.stop();
|
|
118
121
|
throw new Error('FunctionRegistryService not available (check plugin is configured).');
|
|
@@ -155,10 +158,10 @@ export class DevServer {
|
|
|
155
158
|
/**
|
|
156
159
|
* Load function.
|
|
157
160
|
*/
|
|
158
|
-
private async _load(def: FunctionDef, force
|
|
159
|
-
const {
|
|
161
|
+
private async _load(def: FunctionDef, force?: boolean | undefined) {
|
|
162
|
+
const { uri, route, handler } = def;
|
|
160
163
|
const filePath = join(this._options.baseDir, handler);
|
|
161
|
-
log.info('loading', {
|
|
164
|
+
log.info('loading', { uri, force });
|
|
162
165
|
|
|
163
166
|
// Remove from cache.
|
|
164
167
|
if (force) {
|
|
@@ -169,13 +172,26 @@ export class DevServer {
|
|
|
169
172
|
});
|
|
170
173
|
}
|
|
171
174
|
|
|
175
|
+
// TODO(burdon): Import types.
|
|
172
176
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
173
177
|
const module = require(filePath);
|
|
174
178
|
if (typeof module.default !== 'function') {
|
|
175
|
-
throw new Error(`Handler must export default function: ${
|
|
179
|
+
throw new Error(`Handler must export default function: ${uri}`);
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
this._handlers[
|
|
182
|
+
this._handlers[route] = { def, handler: module.default };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private async _safeUpdateRegistration(): Promise<void> {
|
|
186
|
+
invariant(this._functionServiceRegistration);
|
|
187
|
+
try {
|
|
188
|
+
await this._client.services.services.FunctionRegistryService!.updateRegistration({
|
|
189
|
+
registrationId: this._functionServiceRegistration,
|
|
190
|
+
functions: this.functions.map(({ def: { id, route } }) => ({ id, route })),
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
log.catch(err);
|
|
194
|
+
}
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
/**
|
|
@@ -186,24 +202,23 @@ export class DevServer {
|
|
|
186
202
|
const now = Date.now();
|
|
187
203
|
|
|
188
204
|
log.info('req', { seq, path });
|
|
189
|
-
const statusCode = await this._invoke(path, data);
|
|
205
|
+
const statusCode = await this._invoke(path, { data });
|
|
190
206
|
|
|
191
207
|
log.info('res', { seq, path, statusCode, duration: Date.now() - now });
|
|
192
208
|
this.update.emit(statusCode);
|
|
193
209
|
return statusCode;
|
|
194
210
|
}
|
|
195
211
|
|
|
196
|
-
private async _invoke(path: string, event:
|
|
212
|
+
private async _invoke(path: string, event: FunctionEvent) {
|
|
197
213
|
const { handler } = this._handlers[path] ?? {};
|
|
198
214
|
invariant(handler, `invalid path: ${path}`);
|
|
199
|
-
|
|
200
215
|
const context: FunctionContext = {
|
|
201
216
|
client: this._client,
|
|
202
217
|
dataDir: this._options.dataDir,
|
|
203
218
|
};
|
|
204
219
|
|
|
205
220
|
let statusCode = 200;
|
|
206
|
-
const response:
|
|
221
|
+
const response: FunctionResponse = {
|
|
207
222
|
status: (code: number) => {
|
|
208
223
|
statusCode = code;
|
|
209
224
|
return response;
|
|
@@ -214,3 +229,5 @@ export class DevServer {
|
|
|
214
229
|
return statusCode;
|
|
215
230
|
}
|
|
216
231
|
}
|
|
232
|
+
|
|
233
|
+
const createContext = () => new Context({ name: 'DevServer' });
|
|
@@ -6,40 +6,53 @@ import { expect } from 'chai';
|
|
|
6
6
|
import WebSocket from 'ws';
|
|
7
7
|
|
|
8
8
|
import { Trigger } from '@dxos/async';
|
|
9
|
-
import { Client } from '@dxos/client';
|
|
9
|
+
import { type Client } from '@dxos/client';
|
|
10
10
|
import { TestBuilder } from '@dxos/client/testing';
|
|
11
|
-
import { create
|
|
11
|
+
import { create } from '@dxos/echo-schema';
|
|
12
12
|
import { describe, test } from '@dxos/test';
|
|
13
13
|
|
|
14
|
-
import { Scheduler } from './scheduler';
|
|
15
|
-
import {
|
|
14
|
+
import { Scheduler, type SchedulerOptions } from './scheduler';
|
|
15
|
+
import { FunctionRegistry } from '../function';
|
|
16
|
+
import { createInitializedClients, TestType, triggerWebhook } from '../testing';
|
|
17
|
+
import { TriggerRegistry } from '../trigger';
|
|
18
|
+
import { type FunctionManifest } from '../types';
|
|
16
19
|
|
|
17
20
|
// TODO(burdon): Test we can add and remove triggers.
|
|
18
21
|
describe('scheduler', () => {
|
|
22
|
+
let testBuilder: TestBuilder;
|
|
19
23
|
let client: Client;
|
|
20
24
|
before(async () => {
|
|
21
|
-
|
|
22
|
-
client =
|
|
23
|
-
await client.initialize();
|
|
24
|
-
await client.halo.createIdentity();
|
|
25
|
+
testBuilder = new TestBuilder();
|
|
26
|
+
client = (await createInitializedClients(testBuilder, 1))[0];
|
|
25
27
|
});
|
|
26
28
|
after(async () => {
|
|
27
|
-
await
|
|
29
|
+
await testBuilder.destroy();
|
|
28
30
|
});
|
|
29
31
|
|
|
32
|
+
const createScheduler = (callback: SchedulerOptions['callback']) => {
|
|
33
|
+
const scheduler = new Scheduler(new FunctionRegistry(client), new TriggerRegistry(client), { callback });
|
|
34
|
+
after(async () => {
|
|
35
|
+
await scheduler.stop();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return scheduler;
|
|
39
|
+
};
|
|
40
|
+
|
|
30
41
|
test('timer', async () => {
|
|
31
42
|
const manifest: FunctionManifest = {
|
|
32
43
|
functions: [
|
|
33
44
|
{
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
uri: 'example.com/function/test',
|
|
46
|
+
route: '/test',
|
|
36
47
|
handler: 'test',
|
|
37
48
|
},
|
|
38
49
|
],
|
|
39
50
|
triggers: [
|
|
40
51
|
{
|
|
41
52
|
function: 'example.com/function/test',
|
|
42
|
-
|
|
53
|
+
enabled: true,
|
|
54
|
+
spec: {
|
|
55
|
+
type: 'timer',
|
|
43
56
|
cron: '0/1 * * * * *', // Every 1s.
|
|
44
57
|
},
|
|
45
58
|
},
|
|
@@ -48,18 +61,13 @@ describe('scheduler', () => {
|
|
|
48
61
|
|
|
49
62
|
let count = 0;
|
|
50
63
|
const done = new Trigger();
|
|
51
|
-
const scheduler =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
},
|
|
64
|
+
const scheduler = createScheduler(async () => {
|
|
65
|
+
if (++count === 3) {
|
|
66
|
+
done.wake();
|
|
67
|
+
}
|
|
57
68
|
});
|
|
58
|
-
|
|
69
|
+
await scheduler.register(client.spaces.default, manifest);
|
|
59
70
|
await scheduler.start();
|
|
60
|
-
after(async () => {
|
|
61
|
-
await scheduler.stop();
|
|
62
|
-
});
|
|
63
71
|
|
|
64
72
|
await done.wait({ timeout: 5_000 });
|
|
65
73
|
expect(count).to.equal(3);
|
|
@@ -69,15 +77,17 @@ describe('scheduler', () => {
|
|
|
69
77
|
const manifest: FunctionManifest = {
|
|
70
78
|
functions: [
|
|
71
79
|
{
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
uri: 'example.com/function/test',
|
|
81
|
+
route: '/test',
|
|
74
82
|
handler: 'test',
|
|
75
83
|
},
|
|
76
84
|
],
|
|
77
85
|
triggers: [
|
|
78
86
|
{
|
|
79
87
|
function: 'example.com/function/test',
|
|
80
|
-
|
|
88
|
+
enabled: true,
|
|
89
|
+
spec: {
|
|
90
|
+
type: 'webhook',
|
|
81
91
|
method: 'GET',
|
|
82
92
|
},
|
|
83
93
|
},
|
|
@@ -85,23 +95,14 @@ describe('scheduler', () => {
|
|
|
85
95
|
};
|
|
86
96
|
|
|
87
97
|
const done = new Trigger();
|
|
88
|
-
const scheduler =
|
|
89
|
-
|
|
90
|
-
done.wake();
|
|
91
|
-
},
|
|
98
|
+
const scheduler = createScheduler(async () => {
|
|
99
|
+
done.wake();
|
|
92
100
|
});
|
|
93
|
-
|
|
101
|
+
const space = await client.spaces.create();
|
|
102
|
+
await scheduler.register(space, manifest);
|
|
94
103
|
await scheduler.start();
|
|
95
|
-
after(async () => {
|
|
96
|
-
await scheduler.stop();
|
|
97
|
-
});
|
|
98
104
|
|
|
99
|
-
setTimeout(() =>
|
|
100
|
-
const mount: WebhookTrigger = scheduler.mounts.find(
|
|
101
|
-
(mount) => mount.function === 'example.com/function/test',
|
|
102
|
-
)!.webhook!;
|
|
103
|
-
void fetch(`http://localhost:${mount.port}`);
|
|
104
|
-
});
|
|
105
|
+
setTimeout(async () => triggerWebhook(space, manifest.functions![0].uri));
|
|
105
106
|
|
|
106
107
|
await done.wait();
|
|
107
108
|
});
|
|
@@ -110,15 +111,17 @@ describe('scheduler', () => {
|
|
|
110
111
|
const manifest: FunctionManifest = {
|
|
111
112
|
functions: [
|
|
112
113
|
{
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
uri: 'example.com/function/test',
|
|
115
|
+
route: '/test',
|
|
115
116
|
handler: 'test',
|
|
116
117
|
},
|
|
117
118
|
],
|
|
118
119
|
triggers: [
|
|
119
120
|
{
|
|
120
121
|
function: 'example.com/function/test',
|
|
121
|
-
|
|
122
|
+
enabled: true,
|
|
123
|
+
spec: {
|
|
124
|
+
type: 'websocket',
|
|
122
125
|
// url: 'https://hub.dxos.network/api/mailbox/test',
|
|
123
126
|
url: 'http://localhost:8081',
|
|
124
127
|
init: {
|
|
@@ -130,16 +133,11 @@ describe('scheduler', () => {
|
|
|
130
133
|
};
|
|
131
134
|
|
|
132
135
|
const done = new Trigger();
|
|
133
|
-
const scheduler =
|
|
134
|
-
|
|
135
|
-
done.wake();
|
|
136
|
-
},
|
|
136
|
+
const scheduler = createScheduler(async () => {
|
|
137
|
+
done.wake();
|
|
137
138
|
});
|
|
138
|
-
|
|
139
|
+
await scheduler.register(client.spaces.default, manifest);
|
|
139
140
|
await scheduler.start();
|
|
140
|
-
after(async () => {
|
|
141
|
-
await scheduler.stop();
|
|
142
|
-
});
|
|
143
141
|
|
|
144
142
|
// Test server.
|
|
145
143
|
setTimeout(() => {
|
|
@@ -157,29 +155,21 @@ describe('scheduler', () => {
|
|
|
157
155
|
});
|
|
158
156
|
|
|
159
157
|
test('subscription', async () => {
|
|
160
|
-
class TestType extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
161
|
-
title: S.string,
|
|
162
|
-
}) {}
|
|
163
|
-
client.addSchema(TestType);
|
|
164
|
-
|
|
165
158
|
const manifest: FunctionManifest = {
|
|
166
159
|
functions: [
|
|
167
160
|
{
|
|
168
|
-
|
|
169
|
-
|
|
161
|
+
uri: 'example.com/function/test',
|
|
162
|
+
route: '/test',
|
|
170
163
|
handler: 'test',
|
|
171
164
|
},
|
|
172
165
|
],
|
|
173
166
|
triggers: [
|
|
174
167
|
{
|
|
175
168
|
function: 'example.com/function/test',
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
type: TestType.typename,
|
|
181
|
-
},
|
|
182
|
-
],
|
|
169
|
+
enabled: true,
|
|
170
|
+
spec: {
|
|
171
|
+
type: 'subscription',
|
|
172
|
+
filter: [{ type: TestType.typename }],
|
|
183
173
|
},
|
|
184
174
|
},
|
|
185
175
|
],
|
|
@@ -187,20 +177,14 @@ describe('scheduler', () => {
|
|
|
187
177
|
|
|
188
178
|
let count = 0;
|
|
189
179
|
const done = new Trigger();
|
|
190
|
-
const scheduler =
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
},
|
|
180
|
+
const scheduler = createScheduler(async () => {
|
|
181
|
+
if (++count === 1) {
|
|
182
|
+
done.wake();
|
|
183
|
+
}
|
|
196
184
|
});
|
|
197
|
-
|
|
185
|
+
await scheduler.register(client.spaces.default, manifest);
|
|
198
186
|
await scheduler.start();
|
|
199
|
-
after(async () => {
|
|
200
|
-
await scheduler.stop();
|
|
201
|
-
});
|
|
202
187
|
|
|
203
|
-
// TODO(burdon): Query for Expando?
|
|
204
188
|
setTimeout(() => {
|
|
205
189
|
const space = client.spaces.default;
|
|
206
190
|
const object = create(TestType, { title: 'Hello world!' });
|