@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.
Files changed (38) hide show
  1. package/dist/lib/browser/index.mjs +492 -146
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +488 -143
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/handler.d.ts +33 -12
  8. package/dist/types/src/handler.d.ts.map +1 -1
  9. package/dist/types/src/index.d.ts +1 -1
  10. package/dist/types/src/index.d.ts.map +1 -1
  11. package/dist/types/src/runtime/dev-server.d.ts +17 -6
  12. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  13. package/dist/types/src/runtime/dev-server.test.d.ts +2 -0
  14. package/dist/types/src/runtime/dev-server.test.d.ts.map +1 -0
  15. package/dist/types/src/runtime/scheduler.d.ts +55 -7
  16. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  17. package/dist/types/src/testing/test/handler.d.ts +3 -0
  18. package/dist/types/src/testing/test/handler.d.ts.map +1 -0
  19. package/dist/types/src/testing/test/index.d.ts +3 -0
  20. package/dist/types/src/testing/test/index.d.ts.map +1 -0
  21. package/dist/types/src/types.d.ts +182 -0
  22. package/dist/types/src/types.d.ts.map +1 -0
  23. package/dist/types/tools/schema.d.ts +2 -0
  24. package/dist/types/tools/schema.d.ts.map +1 -0
  25. package/package.json +20 -11
  26. package/schema/functions.json +183 -0
  27. package/src/handler.ts +56 -26
  28. package/src/index.ts +1 -1
  29. package/src/runtime/dev-server.test.ts +80 -0
  30. package/src/runtime/dev-server.ts +74 -40
  31. package/src/runtime/scheduler.test.ts +163 -9
  32. package/src/runtime/scheduler.ts +228 -64
  33. package/src/testing/test/handler.ts +9 -0
  34. package/src/testing/test/index.ts +7 -0
  35. package/src/types.ts +87 -0
  36. package/dist/types/src/manifest.d.ts +0 -26
  37. package/dist/types/src/manifest.d.ts.map +0 -1
  38. package/src/manifest.ts +0 -42
@@ -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 { type Space, Filter, createSubscription, type Query, getAutomergeObjectCore } from '@dxos/client/echo';
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 FunctionSubscriptionEvent } from '../handler';
17
- import { type FunctionDef, type FunctionManifest, type FunctionTrigger, type TriggerSubscription } from '../manifest';
20
+ import { type FunctionEventMeta } from '../handler';
21
+ import { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../types';
18
22
 
19
- type Callback = (data: FunctionSubscriptionEvent) => Promise<number>;
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
- * Functions scheduler.
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
- { id: string; spaceKey: PublicKey },
36
+ { spaceKey: PublicKey; id: string },
34
37
  { ctx: Context; trigger: FunctionTrigger }
35
- >(({ id, spaceKey }) => `${spaceKey.toHex()}:${id}`);
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 = { id: trigger.function, spaceKey: space.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
- // Timer.
75
- if (trigger.schedule) {
76
- this._createTimer(ctx, space, def, trigger);
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
- // Subscription.
80
- for (const triggerSubscription of trigger.subscriptions ?? []) {
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 _createTimer(ctx: Context, space: Space, def: FunctionDef, trigger: FunctionTrigger) {
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: trigger.schedule,
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
- private _createSubscription(ctx: Context, space: Space, def: FunctionDef, triggerSubscription: TriggerSubscription) {
126
- log.info('subscription', { space: space.key, triggerSubscription });
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): Standardize subscription handles.
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 { type, props, deep, delay } = triggerSubscription;
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', { type, deep, objects: objects.length });
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 * 1_000) : update));
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
  }
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type FunctionHandler } from '../../handler';
6
+
7
+ export const handler: FunctionHandler<any> = async ({ response }) => {
8
+ return response.status(200);
9
+ };
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { handler } from './handler';
6
+
7
+ export default handler;
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
- };