@dxos/functions 0.5.2 → 0.5.3-main.088a2c8
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/index.mjs +492 -146
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +488 -143
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- 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 +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.d.ts +17 -6
- 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 +55 -7
- package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
- package/dist/types/src/testing/test/handler.d.ts +3 -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/types.d.ts +182 -0
- package/dist/types/src/types.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 +20 -11
- package/schema/functions.json +183 -0
- package/src/handler.ts +56 -26
- package/src/index.ts +1 -1
- package/src/runtime/dev-server.test.ts +80 -0
- package/src/runtime/dev-server.ts +74 -40
- package/src/runtime/scheduler.test.ts +163 -9
- package/src/runtime/scheduler.ts +228 -64
- package/src/testing/test/handler.ts +9 -0
- package/src/testing/test/index.ts +7 -0
- package/src/types.ts +87 -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
|
@@ -3,36 +3,39 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { CronJob } from 'cron';
|
|
6
|
+
import { getPort } from 'get-port-please';
|
|
7
|
+
import http from 'node:http';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import WebSocket from 'ws';
|
|
6
10
|
|
|
7
11
|
import { TextV0Type } from '@braneframe/types';
|
|
8
|
-
import { debounce, DeferredTask } from '@dxos/async';
|
|
12
|
+
import { debounce, DeferredTask, sleep, Trigger } from '@dxos/async';
|
|
9
13
|
import { type Client, type PublicKey } from '@dxos/client';
|
|
10
|
-
import {
|
|
14
|
+
import { createSubscription, Filter, getAutomergeObjectCore, type Query, type Space } from '@dxos/client/echo';
|
|
11
15
|
import { Context } from '@dxos/context';
|
|
12
16
|
import { invariant } from '@dxos/invariant';
|
|
13
17
|
import { log } from '@dxos/log';
|
|
14
18
|
import { ComplexMap } from '@dxos/util';
|
|
15
19
|
|
|
16
|
-
import { type
|
|
17
|
-
import { type FunctionDef, type FunctionManifest, type FunctionTrigger
|
|
20
|
+
import { type FunctionEventMeta } from '../handler';
|
|
21
|
+
import { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../types';
|
|
18
22
|
|
|
19
|
-
type Callback = (data:
|
|
23
|
+
export type Callback = (data: any) => Promise<void | number>;
|
|
20
24
|
|
|
21
|
-
type SchedulerOptions = {
|
|
25
|
+
export type SchedulerOptions = {
|
|
22
26
|
endpoint?: string;
|
|
23
27
|
callback?: Callback;
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
/**
|
|
27
|
-
*
|
|
31
|
+
* The scheduler triggers function execution based on various triggers.
|
|
28
32
|
*/
|
|
29
|
-
// TODO(burdon): Create tests.
|
|
30
33
|
export class Scheduler {
|
|
31
34
|
// Map of mounted functions.
|
|
32
35
|
private readonly _mounts = new ComplexMap<
|
|
33
|
-
{
|
|
36
|
+
{ spaceKey: PublicKey; id: string },
|
|
34
37
|
{ ctx: Context; trigger: FunctionTrigger }
|
|
35
|
-
>(({
|
|
38
|
+
>(({ spaceKey, id }) => `${spaceKey.toHex()}:${id}`);
|
|
36
39
|
|
|
37
40
|
constructor(
|
|
38
41
|
private readonly _client: Client,
|
|
@@ -40,6 +43,13 @@ export class Scheduler {
|
|
|
40
43
|
private readonly _options: SchedulerOptions = {},
|
|
41
44
|
) {}
|
|
42
45
|
|
|
46
|
+
get mounts() {
|
|
47
|
+
return Array.from(this._mounts.values()).reduce<FunctionTrigger[]>((acc, { trigger }) => {
|
|
48
|
+
acc.push(trigger);
|
|
49
|
+
return acc;
|
|
50
|
+
}, []);
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
async start() {
|
|
44
54
|
this._client.spaces.subscribe(async (spaces) => {
|
|
45
55
|
for (const space of spaces) {
|
|
@@ -57,12 +67,15 @@ export class Scheduler {
|
|
|
57
67
|
}
|
|
58
68
|
}
|
|
59
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Mount trigger.
|
|
72
|
+
*/
|
|
60
73
|
private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {
|
|
61
|
-
const key = {
|
|
74
|
+
const key = { spaceKey: space.key, id: trigger.function };
|
|
62
75
|
const def = this._manifest.functions.find((config) => config.id === trigger.function);
|
|
63
76
|
invariant(def, `Function not found: ${trigger.function}`);
|
|
64
77
|
|
|
65
|
-
// Currently supports only one trigger declaration per function.
|
|
78
|
+
// TODO(burdon): Currently supports only one trigger declaration per function.
|
|
66
79
|
const exists = this._mounts.get(key);
|
|
67
80
|
if (!exists) {
|
|
68
81
|
this._mounts.set(key, { ctx, trigger });
|
|
@@ -71,14 +84,24 @@ export class Scheduler {
|
|
|
71
84
|
return;
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
//
|
|
88
|
+
// Triggers types.
|
|
89
|
+
//
|
|
90
|
+
|
|
91
|
+
if (trigger.timer) {
|
|
92
|
+
await this._createTimer(ctx, space, def, trigger);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (trigger.webhook) {
|
|
96
|
+
await this._createWebhook(ctx, space, def, trigger);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (trigger.websocket) {
|
|
100
|
+
await this._createWebsocket(ctx, space, def, trigger);
|
|
77
101
|
}
|
|
78
102
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this._createSubscription(ctx, space, def, triggerSubscription);
|
|
103
|
+
if (trigger.subscription) {
|
|
104
|
+
await this._createSubscription(ctx, space, def, trigger);
|
|
82
105
|
}
|
|
83
106
|
}
|
|
84
107
|
}
|
|
@@ -92,19 +115,66 @@ export class Scheduler {
|
|
|
92
115
|
}
|
|
93
116
|
}
|
|
94
117
|
|
|
95
|
-
private
|
|
118
|
+
private async _execFunction<TData, TMeta>(def: FunctionDef, trigger: FunctionTrigger, data: TData): Promise<number> {
|
|
119
|
+
let status = 0;
|
|
120
|
+
try {
|
|
121
|
+
// TODO(burdon): Pass in Space key (common context)?
|
|
122
|
+
const payload = Object.assign({}, { meta: trigger.meta as TMeta } satisfies FunctionEventMeta<TMeta>, data);
|
|
123
|
+
|
|
124
|
+
const { endpoint, callback } = this._options;
|
|
125
|
+
if (endpoint) {
|
|
126
|
+
// TODO(burdon): Move out of scheduler (generalize as callback).
|
|
127
|
+
const url = path.join(endpoint, def.path);
|
|
128
|
+
log.info('exec', { function: def.id, url });
|
|
129
|
+
const response = await fetch(url, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify(payload),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
status = response.status;
|
|
138
|
+
} else if (callback) {
|
|
139
|
+
log.info('exec', { function: def.id });
|
|
140
|
+
status = (await callback(payload)) ?? 200;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check errors.
|
|
144
|
+
if (status && status >= 400) {
|
|
145
|
+
throw new Error(`Response: ${status}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// const result = await response.json();
|
|
149
|
+
log.info('done', { function: def.id, status });
|
|
150
|
+
} catch (err: any) {
|
|
151
|
+
log.error('error', { function: def.id, error: err.message });
|
|
152
|
+
status = 500;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return status;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
//
|
|
159
|
+
// Triggers
|
|
160
|
+
//
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Cron timer.
|
|
164
|
+
*/
|
|
165
|
+
private async _createTimer(ctx: Context, space: Space, def: FunctionDef, trigger: FunctionTrigger) {
|
|
166
|
+
log.info('timer', { space: space.key, trigger });
|
|
167
|
+
const spec = trigger.timer!;
|
|
168
|
+
|
|
96
169
|
const task = new DeferredTask(ctx, async () => {
|
|
97
|
-
await this._execFunction(def, {
|
|
98
|
-
space: space.key,
|
|
99
|
-
});
|
|
170
|
+
await this._execFunction(def, trigger, { spaceKey: space.key });
|
|
100
171
|
});
|
|
101
172
|
|
|
102
|
-
invariant(trigger.schedule);
|
|
103
173
|
let last = 0;
|
|
104
174
|
let run = 0;
|
|
105
175
|
// https://www.npmjs.com/package/cron#constructor
|
|
106
176
|
const job = CronJob.from({
|
|
107
|
-
cronTime:
|
|
177
|
+
cronTime: spec.cron,
|
|
108
178
|
runOnInit: false,
|
|
109
179
|
onTick: () => {
|
|
110
180
|
// TODO(burdon): Check greater than 30s (use cron-parser).
|
|
@@ -122,18 +192,138 @@ export class Scheduler {
|
|
|
122
192
|
ctx.onDispose(() => job.stop());
|
|
123
193
|
}
|
|
124
194
|
|
|
125
|
-
|
|
126
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Webhook.
|
|
197
|
+
*/
|
|
198
|
+
private async _createWebhook(ctx: Context, space: Space, def: FunctionDef, trigger: FunctionTrigger) {
|
|
199
|
+
log.info('webhook', { space: space.key, trigger });
|
|
200
|
+
const spec = trigger.webhook!;
|
|
201
|
+
|
|
202
|
+
// TODO(burdon): Enable POST hook with payload.
|
|
203
|
+
const server = http.createServer(async (req, res) => {
|
|
204
|
+
if (req.method !== spec.method) {
|
|
205
|
+
res.statusCode = 405;
|
|
206
|
+
return res.end();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
res.statusCode = await this._execFunction(def, trigger, { spaceKey: space.key });
|
|
210
|
+
res.end();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// TODO(burdon): Not used.
|
|
214
|
+
// const DEF_PORT_RANGE = { min: 7500, max: 7599 };
|
|
215
|
+
// const portRange = Object.assign({}, trigger.port, DEF_PORT_RANGE) as WebhookTrigger['port'];
|
|
216
|
+
const port = await getPort({
|
|
217
|
+
random: true,
|
|
218
|
+
// portRange: [portRange!.min, portRange!.max],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// TODO(burdon): Update trigger object with actual port.
|
|
222
|
+
server.listen(port, () => {
|
|
223
|
+
log.info('started webhook', { port });
|
|
224
|
+
spec.port = port;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
ctx.onDispose(() => {
|
|
228
|
+
server.close();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Websocket.
|
|
234
|
+
* NOTE: The port must be unique, so the same hook cannot be used for multiple spaces.
|
|
235
|
+
*/
|
|
236
|
+
private async _createWebsocket(
|
|
237
|
+
ctx: Context,
|
|
238
|
+
space: Space,
|
|
239
|
+
def: FunctionDef,
|
|
240
|
+
trigger: FunctionTrigger,
|
|
241
|
+
options: {
|
|
242
|
+
retryDelay: number;
|
|
243
|
+
maxAttempts: number;
|
|
244
|
+
} = {
|
|
245
|
+
retryDelay: 2,
|
|
246
|
+
maxAttempts: 5,
|
|
247
|
+
},
|
|
248
|
+
) {
|
|
249
|
+
log.info('websocket', { space: space.key, trigger });
|
|
250
|
+
const spec = trigger.websocket!;
|
|
251
|
+
const { url, init } = spec;
|
|
252
|
+
|
|
253
|
+
let ws: WebSocket;
|
|
254
|
+
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
|
|
255
|
+
const open = new Trigger<boolean>();
|
|
256
|
+
|
|
257
|
+
ws = new WebSocket(url);
|
|
258
|
+
Object.assign(ws, {
|
|
259
|
+
onopen: () => {
|
|
260
|
+
log.info('opened', { url });
|
|
261
|
+
if (spec.init) {
|
|
262
|
+
ws.send(new TextEncoder().encode(JSON.stringify(init)));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
open.wake(true);
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
onclose: (event) => {
|
|
269
|
+
log.info('closed', { url, code: event.code });
|
|
270
|
+
// Reconnect if server closes (e.g., CF restart).
|
|
271
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code
|
|
272
|
+
if (event.code === 1006) {
|
|
273
|
+
setTimeout(async () => {
|
|
274
|
+
log.info(`reconnecting in ${options.retryDelay}s...`, { url });
|
|
275
|
+
await this._createWebsocket(ctx, space, def, trigger, options);
|
|
276
|
+
}, options.retryDelay * 1_000);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
open.wake(false);
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
onerror: (event) => {
|
|
283
|
+
log.catch(event.error, { url });
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
onmessage: async (event) => {
|
|
287
|
+
try {
|
|
288
|
+
const data = JSON.parse(new TextDecoder().decode(event.data as Uint8Array));
|
|
289
|
+
await this._execFunction(def, trigger, { spaceKey: space.key, data });
|
|
290
|
+
} catch (err) {
|
|
291
|
+
log.catch(err, { url });
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
} satisfies Partial<WebSocket>);
|
|
295
|
+
|
|
296
|
+
const isOpen = await open.wait();
|
|
297
|
+
if (isOpen) {
|
|
298
|
+
break;
|
|
299
|
+
} else {
|
|
300
|
+
const wait = Math.pow(attempt, 2) * options.retryDelay;
|
|
301
|
+
if (attempt < options.maxAttempts) {
|
|
302
|
+
log.warn(`failed to connect; trying again in ${wait}s`, { attempt });
|
|
303
|
+
await sleep(wait * 1_000);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
ctx.onDispose(() => {
|
|
309
|
+
ws?.close();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* ECHO subscription.
|
|
315
|
+
*/
|
|
316
|
+
private async _createSubscription(ctx: Context, space: Space, def: FunctionDef, trigger: FunctionTrigger) {
|
|
317
|
+
log.info('subscription', { space: space.key, trigger });
|
|
318
|
+
const spec = trigger.subscription!;
|
|
319
|
+
|
|
127
320
|
const objectIds = new Set<string>();
|
|
128
321
|
const task = new DeferredTask(ctx, async () => {
|
|
129
|
-
await this._execFunction(def, {
|
|
130
|
-
space: space.key,
|
|
131
|
-
objects: Array.from(objectIds),
|
|
132
|
-
});
|
|
322
|
+
await this._execFunction(def, trigger, { spaceKey: space.key, objects: Array.from(objectIds) });
|
|
133
323
|
});
|
|
134
324
|
|
|
135
|
-
// TODO(burdon): Don't fire initially
|
|
136
|
-
// TODO(burdon):
|
|
325
|
+
// TODO(burdon): Don't fire initially?
|
|
326
|
+
// TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
|
|
137
327
|
const subscriptions: (() => void)[] = [];
|
|
138
328
|
const subscription = createSubscription(({ added, updated }) => {
|
|
139
329
|
log.info('updated', { added: added.length, updated: updated.length });
|
|
@@ -146,17 +336,17 @@ export class Scheduler {
|
|
|
146
336
|
|
|
147
337
|
task.schedule();
|
|
148
338
|
});
|
|
339
|
+
|
|
149
340
|
subscriptions.push(() => subscription.unsubscribe());
|
|
150
341
|
|
|
151
|
-
// TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
|
|
152
342
|
// TODO(burdon): Disable trigger if keeps failing.
|
|
153
|
-
const {
|
|
343
|
+
const { filter, options: { deep, delay } = {} } = spec;
|
|
154
344
|
const update = ({ objects }: Query) => {
|
|
155
345
|
subscription.update(objects);
|
|
156
346
|
|
|
157
347
|
// TODO(burdon): Hack to monitor changes to Document's text object.
|
|
158
348
|
if (deep) {
|
|
159
|
-
log.info('update', {
|
|
349
|
+
log.info('update', { objects: objects.length });
|
|
160
350
|
for (const object of objects) {
|
|
161
351
|
const content = object.content;
|
|
162
352
|
if (content instanceof TextV0Type) {
|
|
@@ -168,40 +358,14 @@ export class Scheduler {
|
|
|
168
358
|
}
|
|
169
359
|
};
|
|
170
360
|
|
|
361
|
+
// TODO(burdon): Is Filter.or implemented?
|
|
171
362
|
// TODO(burdon): [Bug]: all callbacks are fired on the first mutation.
|
|
172
363
|
// 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
|
|
364
|
+
const query = space.db.query(Filter.or(filter.map(({ type, props }) => Filter.typename(type, props))));
|
|
365
|
+
subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
|
|
175
366
|
|
|
176
367
|
ctx.onDispose(() => {
|
|
177
368
|
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
178
369
|
});
|
|
179
370
|
}
|
|
180
|
-
|
|
181
|
-
private async _execFunction(def: FunctionDef, data: any) {
|
|
182
|
-
try {
|
|
183
|
-
log('request', { function: def.id });
|
|
184
|
-
const { endpoint, callback } = this._options;
|
|
185
|
-
let status = 0;
|
|
186
|
-
if (endpoint) {
|
|
187
|
-
// TODO(burdon): Move out of scheduler (generalize as callback).
|
|
188
|
-
const response = await fetch(`${this._options.endpoint}/${def.name}`, {
|
|
189
|
-
method: 'POST',
|
|
190
|
-
headers: {
|
|
191
|
-
'Content-Type': 'application/json',
|
|
192
|
-
},
|
|
193
|
-
body: JSON.stringify(data),
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
status = response.status;
|
|
197
|
-
} else if (callback) {
|
|
198
|
-
status = await callback(data);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// const result = await response.json();
|
|
202
|
-
log('result', { function: def.id, result: status });
|
|
203
|
-
} catch (err: any) {
|
|
204
|
-
log.error('error', { function: def.id, error: err.message });
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
371
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as S from '@effect/schema/Schema';
|
|
6
|
+
|
|
7
|
+
const TimerTriggerSchema = S.struct({
|
|
8
|
+
cron: S.string,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const WebhookTriggerSchema = S.mutable(
|
|
12
|
+
S.struct({
|
|
13
|
+
method: S.string,
|
|
14
|
+
// Assigned port.
|
|
15
|
+
port: S.optional(S.number),
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const WebsocketTriggerSchema = S.struct({
|
|
20
|
+
url: S.string,
|
|
21
|
+
init: S.optional(S.record(S.string, S.any)),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const SubscriptionTriggerSchema = S.struct({
|
|
25
|
+
spaceKey: S.optional(S.string),
|
|
26
|
+
// TODO(burdon): Define query DSL.
|
|
27
|
+
filter: S.array(
|
|
28
|
+
S.struct({
|
|
29
|
+
type: S.string,
|
|
30
|
+
props: S.optional(S.record(S.string, S.any)),
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
options: S.optional(
|
|
34
|
+
S.struct({
|
|
35
|
+
// Watch changes to object (not just creation).
|
|
36
|
+
deep: S.optional(S.boolean),
|
|
37
|
+
// Debounce changes (delay in ms).
|
|
38
|
+
delay: S.optional(S.number),
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const FunctionTriggerSchema = S.struct({
|
|
44
|
+
function: S.string.pipe(S.description('Function ID/URI.')),
|
|
45
|
+
|
|
46
|
+
// Context passed to function.
|
|
47
|
+
meta: S.optional(S.record(S.string, S.any)),
|
|
48
|
+
|
|
49
|
+
// Triggers.
|
|
50
|
+
timer: S.optional(TimerTriggerSchema),
|
|
51
|
+
webhook: S.optional(WebhookTriggerSchema),
|
|
52
|
+
websocket: S.optional(WebsocketTriggerSchema),
|
|
53
|
+
subscription: S.optional(SubscriptionTriggerSchema),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export type FunctionTrigger = S.Schema.Type<typeof FunctionTriggerSchema>;
|
|
57
|
+
|
|
58
|
+
export type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;
|
|
59
|
+
export type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;
|
|
60
|
+
export type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;
|
|
61
|
+
export type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Function definition.
|
|
65
|
+
*/
|
|
66
|
+
// TODO(burdon): Name vs. path?
|
|
67
|
+
const FunctionDefSchema = S.struct({
|
|
68
|
+
id: S.string,
|
|
69
|
+
// name: S.string,
|
|
70
|
+
description: S.optional(S.string),
|
|
71
|
+
// TODO(burdon): Rename route?
|
|
72
|
+
path: S.string,
|
|
73
|
+
// TODO(burdon): NPM/GitHub/Docker/CF URL?
|
|
74
|
+
handler: S.string,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export type FunctionDef = S.Schema.Type<typeof FunctionDefSchema>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Function manifest file.
|
|
81
|
+
*/
|
|
82
|
+
export const FunctionManifestSchema = S.struct({
|
|
83
|
+
functions: S.mutable(S.array(FunctionDefSchema)),
|
|
84
|
+
triggers: S.optional(S.mutable(S.array(FunctionTriggerSchema))),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export type FunctionDef = {
|
|
2
|
-
id: string;
|
|
3
|
-
name: string;
|
|
4
|
-
handler: string;
|
|
5
|
-
description?: string;
|
|
6
|
-
};
|
|
7
|
-
export type TriggerSubscription = {
|
|
8
|
-
type: string;
|
|
9
|
-
spaceKey: string;
|
|
10
|
-
props?: Record<string, any>;
|
|
11
|
-
deep?: boolean;
|
|
12
|
-
delay?: number;
|
|
13
|
-
};
|
|
14
|
-
export type FunctionTrigger = {
|
|
15
|
-
function: string;
|
|
16
|
-
schedule?: string;
|
|
17
|
-
subscriptions?: TriggerSubscription[];
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Function manifest file.
|
|
21
|
-
*/
|
|
22
|
-
export type FunctionManifest = {
|
|
23
|
-
functions: FunctionDef[];
|
|
24
|
-
triggers: FunctionTrigger[];
|
|
25
|
-
};
|
|
26
|
-
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/manifest.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG;IAExB,EAAE,EAAE,MAAM,CAAC;IAEX,IAAI,EAAE,MAAM,CAAC;IAEb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAGF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAKF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,mBAAmB,EAAE,CAAC;CACvC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC"}
|
package/src/manifest.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
// Lambda-like function definitions.
|
|
6
|
-
// See: https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/#functions
|
|
7
|
-
|
|
8
|
-
export type FunctionDef = {
|
|
9
|
-
// FQ function name.
|
|
10
|
-
id: string;
|
|
11
|
-
// URL path.
|
|
12
|
-
name: string;
|
|
13
|
-
// File path of handler.
|
|
14
|
-
handler: string;
|
|
15
|
-
description?: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// TODO(burdon): Query DSL.
|
|
19
|
-
export type TriggerSubscription = {
|
|
20
|
-
type: string;
|
|
21
|
-
spaceKey: string;
|
|
22
|
-
props?: Record<string, any>;
|
|
23
|
-
deep?: boolean; // Watch changes to object (not just creation).
|
|
24
|
-
delay?: number;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// TODO(burdon): Generalize binding.
|
|
28
|
-
// https://www.npmjs.com/package/aws-lambda
|
|
29
|
-
// https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
|
|
30
|
-
export type FunctionTrigger = {
|
|
31
|
-
function: string;
|
|
32
|
-
schedule?: string;
|
|
33
|
-
subscriptions?: TriggerSubscription[];
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Function manifest file.
|
|
38
|
-
*/
|
|
39
|
-
export type FunctionManifest = {
|
|
40
|
-
functions: FunctionDef[];
|
|
41
|
-
triggers: FunctionTrigger[];
|
|
42
|
-
};
|