@dxos/functions 0.5.3-main.f1ddd61 → 0.5.3-main.f752aaa

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.
@@ -3,22 +3,31 @@
3
3
  //
4
4
 
5
5
  import { expect } from 'chai';
6
+ import WebSocket from 'ws';
6
7
 
7
8
  import { Trigger } from '@dxos/async';
8
9
  import { Client } from '@dxos/client';
9
10
  import { TestBuilder } from '@dxos/client/testing';
11
+ import { create, S, TypedObject } from '@dxos/echo-schema';
10
12
  import { describe, test } from '@dxos/test';
11
13
 
12
14
  import { Scheduler } from './scheduler';
13
- import { type FunctionManifest } from '../manifest';
15
+ import { type FunctionManifest } from '../types';
14
16
 
17
+ // TODO(burdon): Test we can add and remove triggers.
15
18
  describe('scheduler', () => {
16
- test.only('callback', async () => {
19
+ let client: Client;
20
+ before(async () => {
17
21
  const testBuilder = new TestBuilder();
18
- const client = new Client({ services: testBuilder.createLocal() });
22
+ client = new Client({ services: testBuilder.createLocal() });
19
23
  await client.initialize();
20
24
  await client.halo.createIdentity();
25
+ });
26
+ after(async () => {
27
+ await client.destroy();
28
+ });
21
29
 
30
+ test('timer', async () => {
22
31
  const manifest: FunctionManifest = {
23
32
  functions: [
24
33
  {
@@ -30,7 +39,9 @@ describe('scheduler', () => {
30
39
  triggers: [
31
40
  {
32
41
  function: 'example.com/function/test',
33
- schedule: '0/1 * * * * *', // Every 1s.
42
+ timer: {
43
+ cron: '0/1 * * * * *', // Every 1s.
44
+ },
34
45
  },
35
46
  ],
36
47
  };
@@ -42,17 +53,156 @@ describe('scheduler', () => {
42
53
  if (++count === 3) {
43
54
  done.wake();
44
55
  }
56
+ },
57
+ });
58
+
59
+ await scheduler.start();
60
+ after(async () => {
61
+ await scheduler.stop();
62
+ });
63
+
64
+ await done.wait({ timeout: 5_000 });
65
+ expect(count).to.equal(3);
66
+ });
67
+
68
+ test('webhook', async () => {
69
+ const manifest: FunctionManifest = {
70
+ functions: [
71
+ {
72
+ id: 'example.com/function/test',
73
+ name: 'test',
74
+ handler: 'test',
75
+ },
76
+ ],
77
+ triggers: [
78
+ {
79
+ function: 'example.com/function/test',
80
+ webhook: {
81
+ port: 8080,
82
+ },
83
+ },
84
+ ],
85
+ };
45
86
 
46
- return 200;
87
+ const done = new Trigger();
88
+ const scheduler = new Scheduler(client, manifest, {
89
+ callback: async () => {
90
+ done.wake();
47
91
  },
48
92
  });
49
93
 
50
94
  await scheduler.start();
95
+ after(async () => {
96
+ await scheduler.stop();
97
+ });
51
98
 
99
+ setTimeout(() => {
100
+ void fetch('http://localhost:8080');
101
+ });
52
102
  await done.wait();
53
- expect(count).to.equal(3);
103
+ });
54
104
 
55
- await scheduler.stop();
56
- await client.destroy();
105
+ test.only('websocket', async () => {
106
+ const manifest: FunctionManifest = {
107
+ functions: [
108
+ {
109
+ id: 'example.com/function/test',
110
+ name: 'test',
111
+ handler: 'test',
112
+ },
113
+ ],
114
+ triggers: [
115
+ {
116
+ function: 'example.com/function/test',
117
+ websocket: {
118
+ // url: 'https://hub.dxos.network/api/mailbox/test',
119
+ url: 'http://localhost:8081',
120
+ init: {
121
+ type: 'sync',
122
+ },
123
+ },
124
+ },
125
+ ],
126
+ };
127
+
128
+ const done = new Trigger();
129
+ const scheduler = new Scheduler(client, manifest, {
130
+ callback: async (data) => {
131
+ done.wake();
132
+ },
133
+ });
134
+
135
+ await scheduler.start();
136
+ after(async () => {
137
+ await scheduler.stop();
138
+ });
139
+
140
+ // Test server.
141
+ setTimeout(() => {
142
+ const wss = new WebSocket.Server({ port: 8081 });
143
+ wss.on('connection', (ws: WebSocket) => {
144
+ ws.on('message', (data) => {
145
+ const info = JSON.parse(new TextDecoder().decode(data as ArrayBuffer));
146
+ expect(info.type).to.equal('sync');
147
+ done.wake();
148
+ });
149
+ });
150
+ }, 500);
151
+
152
+ await done.wait();
153
+ });
154
+
155
+ test('subscription', async () => {
156
+ class TestType extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
157
+ title: S.string,
158
+ }) {}
159
+ client.addSchema(TestType);
160
+
161
+ const manifest: FunctionManifest = {
162
+ functions: [
163
+ {
164
+ id: 'example.com/function/test',
165
+ name: 'test',
166
+ handler: 'test',
167
+ },
168
+ ],
169
+ triggers: [
170
+ {
171
+ function: 'example.com/function/test',
172
+ subscription: {
173
+ spaceKey: client.spaces.default.key.toHex(),
174
+ filter: [
175
+ {
176
+ type: TestType.typename,
177
+ },
178
+ ],
179
+ },
180
+ },
181
+ ],
182
+ };
183
+
184
+ let count = 0;
185
+ const done = new Trigger();
186
+ const scheduler = new Scheduler(client, manifest, {
187
+ callback: async () => {
188
+ if (++count === 2) {
189
+ done.wake();
190
+ }
191
+ },
192
+ });
193
+
194
+ await scheduler.start();
195
+ after(async () => {
196
+ await scheduler.stop();
197
+ });
198
+
199
+ // TODO(burdon): Query for Expando?
200
+ setTimeout(() => {
201
+ const space = client.spaces.default;
202
+ const object = create(TestType, { title: 'Hello world!' });
203
+ space.db.add(object);
204
+ }, 100);
205
+
206
+ await done.wait();
57
207
  });
58
208
  });
@@ -3,30 +3,39 @@
3
3
  //
4
4
 
5
5
  import { CronJob } from 'cron';
6
+ import http from 'node:http';
7
+ import WebSocket from 'ws';
6
8
 
7
9
  import { TextV0Type } from '@braneframe/types';
8
- import { debounce, DeferredTask } from '@dxos/async';
10
+ import { debounce, DeferredTask, sleep, Trigger } from '@dxos/async';
9
11
  import { type Client, type PublicKey } from '@dxos/client';
10
- import { type Space, Filter, createSubscription, type Query, getAutomergeObjectCore } from '@dxos/client/echo';
12
+ import { createSubscription, Filter, getAutomergeObjectCore, type Query, type Space } from '@dxos/client/echo';
11
13
  import { Context } from '@dxos/context';
12
14
  import { invariant } from '@dxos/invariant';
13
15
  import { log } from '@dxos/log';
14
16
  import { ComplexMap } from '@dxos/util';
15
17
 
16
18
  import { type FunctionSubscriptionEvent } from '../handler';
17
- import { type FunctionDef, type FunctionManifest, type FunctionTrigger, type TriggerSubscription } from '../manifest';
19
+ import {
20
+ type FunctionDef,
21
+ type FunctionManifest,
22
+ type FunctionTrigger,
23
+ type SubscriptionTrigger,
24
+ type TimerTrigger,
25
+ type WebhookTrigger,
26
+ type WebsocketTrigger,
27
+ } from '../types';
18
28
 
19
- type Callback = (data: FunctionSubscriptionEvent) => Promise<number>;
29
+ type Callback = (data: FunctionSubscriptionEvent) => Promise<void>;
20
30
 
21
- type SchedulerOptions = {
31
+ export type SchedulerOptions = {
22
32
  endpoint?: string;
23
33
  callback?: Callback;
24
34
  };
25
35
 
26
36
  /**
27
- * Functions scheduler.
37
+ * The scheduler triggers function exectuion based on various triggers.
28
38
  */
29
- // TODO(burdon): Create tests.
30
39
  export class Scheduler {
31
40
  // Map of mounted functions.
32
41
  private readonly _mounts = new ComplexMap<
@@ -62,7 +71,7 @@ export class Scheduler {
62
71
  const def = this._manifest.functions.find((config) => config.id === trigger.function);
63
72
  invariant(def, `Function not found: ${trigger.function}`);
64
73
 
65
- // Currently supports only one trigger declaration per function.
74
+ // TODO(burdon): Currently supports only one trigger declaration per function.
66
75
  const exists = this._mounts.get(key);
67
76
  if (!exists) {
68
77
  this._mounts.set(key, { ctx, trigger });
@@ -71,14 +80,24 @@ export class Scheduler {
71
80
  return;
72
81
  }
73
82
 
74
- // Timer.
75
- if (trigger.schedule) {
76
- this._createTimer(ctx, space, def, trigger);
83
+ //
84
+ // Triggers types.
85
+ //
86
+
87
+ if (trigger.timer) {
88
+ await this._createTimer(ctx, space, def, trigger.timer);
89
+ }
90
+
91
+ if (trigger.webhook) {
92
+ await this._createWebhook(ctx, space, def, trigger.webhook);
93
+ }
94
+
95
+ if (trigger.websocket) {
96
+ await this._createWebsocket(ctx, space, def, trigger.websocket);
77
97
  }
78
98
 
79
- // Subscription.
80
- for (const triggerSubscription of trigger.subscriptions ?? []) {
81
- this._createSubscription(ctx, space, def, triggerSubscription);
99
+ if (trigger.subscription) {
100
+ await this._createSubscription(ctx, space, def, trigger.subscription);
82
101
  }
83
102
  }
84
103
  }
@@ -92,19 +111,51 @@ export class Scheduler {
92
111
  }
93
112
  }
94
113
 
95
- private _createTimer(ctx: Context, space: Space, def: FunctionDef, trigger: FunctionTrigger) {
114
+ // TODO(burdon): Pass in Space key (common context).
115
+ private async _execFunction(def: FunctionDef, data: any) {
116
+ try {
117
+ log.info('exec', { function: def.id });
118
+ const { endpoint, callback } = this._options;
119
+ if (endpoint) {
120
+ // TODO(burdon): Move out of scheduler (generalize as callback).
121
+ await fetch(`${this._options.endpoint}/${def.name}`, {
122
+ method: 'POST',
123
+ headers: {
124
+ 'Content-Type': 'application/json',
125
+ },
126
+ body: JSON.stringify(data),
127
+ });
128
+ } else if (callback) {
129
+ await callback(data);
130
+ }
131
+
132
+ // const result = await response.json();
133
+ log.info('done', { function: def.id });
134
+ } catch (err: any) {
135
+ log.error('error', { function: def.id, error: err.message });
136
+ }
137
+ }
138
+
139
+ //
140
+ // Triggers
141
+ //
142
+
143
+ /**
144
+ * Cron timer.
145
+ */
146
+ private async _createTimer(ctx: Context, space: Space, def: FunctionDef, trigger: TimerTrigger) {
147
+ log.info('timer', { space: space.key, trigger });
148
+ const { cron } = trigger;
149
+
96
150
  const task = new DeferredTask(ctx, async () => {
97
- await this._execFunction(def, {
98
- space: space.key,
99
- });
151
+ await this._execFunction(def, { space: space.key });
100
152
  });
101
153
 
102
- invariant(trigger.schedule);
103
154
  let last = 0;
104
155
  let run = 0;
105
156
  // https://www.npmjs.com/package/cron#constructor
106
157
  const job = CronJob.from({
107
- cronTime: trigger.schedule,
158
+ cronTime: cron,
108
159
  runOnInit: false,
109
160
  onTick: () => {
110
161
  // TODO(burdon): Check greater than 30s (use cron-parser).
@@ -122,18 +173,109 @@ export class Scheduler {
122
173
  ctx.onDispose(() => job.stop());
123
174
  }
124
175
 
125
- private _createSubscription(ctx: Context, space: Space, def: FunctionDef, triggerSubscription: TriggerSubscription) {
126
- log.info('subscription', { space: space.key, triggerSubscription });
176
+ /**
177
+ * Webhook.
178
+ */
179
+ private async _createWebhook(ctx: Context, space: Space, def: FunctionDef, trigger: WebhookTrigger) {
180
+ log.info('webhook', { space: space.key, trigger });
181
+ const { port } = trigger;
182
+
183
+ // TODO(burdon): POST JSON.
184
+ const server = http.createServer(async (req, res) => {
185
+ await this._execFunction(def, { space: space.key });
186
+ });
187
+
188
+ server.listen(port, () => {
189
+ log.info('started webhook', { port });
190
+ });
191
+
192
+ ctx.onDispose(() => {
193
+ server.close();
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Websocket.
199
+ */
200
+ private async _createWebsocket(
201
+ ctx: Context,
202
+ space: Space,
203
+ def: FunctionDef,
204
+ trigger: WebsocketTrigger,
205
+ options: {
206
+ retryDelay: number;
207
+ maxAttempts: number;
208
+ } = {
209
+ retryDelay: 2,
210
+ maxAttempts: 5,
211
+ },
212
+ ) {
213
+ log.info('websocket', { space: space.key, trigger });
214
+ const { url } = trigger;
215
+
216
+ let ws: WebSocket;
217
+ for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
218
+ const open = new Trigger<boolean>();
219
+
220
+ ws = new WebSocket(url);
221
+ Object.assign(ws, {
222
+ onopen: () => {
223
+ log.info('opened', { url });
224
+ if (trigger.init) {
225
+ ws.send(new TextEncoder().encode(JSON.stringify(trigger.init)));
226
+ }
227
+
228
+ open.wake(true);
229
+ },
230
+
231
+ onclose: () => {
232
+ log.info('closed', { url });
233
+ open.wake(false);
234
+ },
235
+
236
+ onerror: (event) => {
237
+ log.catch(event.error, { url });
238
+ },
239
+
240
+ onmessage: async (event) => {
241
+ try {
242
+ const data = JSON.parse(new TextDecoder().decode(event.data as Uint8Array));
243
+ await this._execFunction(def, { space: space.key, data });
244
+ } catch (err) {
245
+ log.catch(err, { url });
246
+ }
247
+ },
248
+ } satisfies Partial<WebSocket>);
249
+
250
+ const isOpen = await open.wait();
251
+ if (isOpen) {
252
+ break;
253
+ } else {
254
+ const wait = Math.pow(attempt, 2) * options.retryDelay;
255
+ if (attempt < options.maxAttempts) {
256
+ log.warn(`failed to connect; trying again in ${wait}s`, { attempt });
257
+ await sleep(wait * 1_000);
258
+ }
259
+ }
260
+ }
261
+
262
+ ctx.onDispose(() => {
263
+ ws?.close();
264
+ });
265
+ }
266
+
267
+ /**
268
+ * ECHO subscription.
269
+ */
270
+ private async _createSubscription(ctx: Context, space: Space, def: FunctionDef, trigger: SubscriptionTrigger) {
271
+ log.info('subscription', { space: space.key, trigger });
127
272
  const objectIds = new Set<string>();
128
273
  const task = new DeferredTask(ctx, async () => {
129
- await this._execFunction(def, {
130
- space: space.key,
131
- objects: Array.from(objectIds),
132
- });
274
+ await this._execFunction(def, { space: space.key, objects: Array.from(objectIds) });
133
275
  });
134
276
 
135
277
  // TODO(burdon): Don't fire initially.
136
- // TODO(burdon): Standardize subscription handles.
278
+ // TODO(burdon): Subscription is called THREE times.
137
279
  const subscriptions: (() => void)[] = [];
138
280
  const subscription = createSubscription(({ added, updated }) => {
139
281
  log.info('updated', { added: added.length, updated: updated.length });
@@ -150,13 +292,13 @@ export class Scheduler {
150
292
 
151
293
  // TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
152
294
  // TODO(burdon): Disable trigger if keeps failing.
153
- const { type, props, deep, delay } = triggerSubscription;
295
+ const { filter, options: { deep, delay } = {} } = trigger;
154
296
  const update = ({ objects }: Query) => {
155
297
  subscription.update(objects);
156
298
 
157
299
  // TODO(burdon): Hack to monitor changes to Document's text object.
158
300
  if (deep) {
159
- log.info('update', { type, deep, objects: objects.length });
301
+ log.info('update', { objects: objects.length });
160
302
  for (const object of objects) {
161
303
  const content = object.content;
162
304
  if (content instanceof TextV0Type) {
@@ -168,40 +310,14 @@ export class Scheduler {
168
310
  }
169
311
  };
170
312
 
313
+ // TODO(burdon): Is Filter.or implemented?
171
314
  // TODO(burdon): [Bug]: all callbacks are fired on the first mutation.
172
315
  // 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));
316
+ const query = space.db.query(Filter.or(filter.map(({ type, props }) => Filter.typename(type, props))));
317
+ subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
175
318
 
176
319
  ctx.onDispose(() => {
177
320
  subscriptions.forEach((unsubscribe) => unsubscribe());
178
321
  });
179
322
  }
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
323
  }
package/src/types.ts ADDED
@@ -0,0 +1,76 @@
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.struct({
12
+ port: S.number,
13
+ });
14
+
15
+ const WebsocketTriggerSchema = S.struct({
16
+ url: S.string,
17
+ init: S.optional(S.record(S.string, S.any)),
18
+ });
19
+
20
+ const SubscriptionTriggerSchema = S.struct({
21
+ spaceKey: S.optional(S.string),
22
+ // TODO(burdon): Define query DSL.
23
+ filter: S.array(
24
+ S.struct({
25
+ type: S.string,
26
+ props: S.optional(S.record(S.string, S.any)),
27
+ }),
28
+ ),
29
+ options: S.optional(
30
+ S.struct({
31
+ // Watch changes to object (not just creation).
32
+ deep: S.optional(S.boolean),
33
+ // Debounce changes (delay in ms).
34
+ delay: S.optional(S.number),
35
+ }),
36
+ ),
37
+ });
38
+
39
+ const FunctionTriggerSchema = S.struct({
40
+ function: S.string.pipe(S.description('Function ID/URI.')),
41
+
42
+ timer: S.optional(TimerTriggerSchema),
43
+ webhook: S.optional(WebhookTriggerSchema),
44
+ websocket: S.optional(WebsocketTriggerSchema),
45
+ subscription: S.optional(SubscriptionTriggerSchema),
46
+ });
47
+
48
+ export type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;
49
+ export type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;
50
+ export type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;
51
+ export type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;
52
+ export type FunctionTrigger = S.Schema.Type<typeof FunctionTriggerSchema>;
53
+
54
+ /**
55
+ * Function definition.
56
+ */
57
+ // TODO(burdon): Name vs. path?
58
+ const FunctionDefSchema = S.struct({
59
+ id: S.string,
60
+ description: S.optional(S.string),
61
+ name: S.string,
62
+ // TODO(burdon): NPM/GitHub URL?
63
+ handler: S.string,
64
+ });
65
+
66
+ export type FunctionDef = S.Schema.Type<typeof FunctionDefSchema>;
67
+
68
+ /**
69
+ * Function manifest file.
70
+ */
71
+ export const FunctionManifestSchema = S.struct({
72
+ functions: S.mutable(S.array(FunctionDefSchema)),
73
+ triggers: S.mutable(S.array(FunctionTriggerSchema)),
74
+ });
75
+
76
+ 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
- };