@dxos/functions 0.3.9-main.325dd0a → 0.3.9-main.35264cb

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.
@@ -8,12 +8,13 @@ export type TriggerSubscription = {
8
8
  type: string;
9
9
  spaceKey: string;
10
10
  props?: Record<string, any>;
11
- nested?: string[];
11
+ deep?: boolean;
12
+ delay?: number;
12
13
  };
13
14
  export type FunctionTrigger = {
14
15
  function: string;
15
16
  schedule?: string;
16
- subscription?: TriggerSubscription;
17
+ subscriptions?: TriggerSubscription[];
17
18
  };
18
19
  /**
19
20
  * Function manifest file.
@@ -1 +1 @@
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;AAEF,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,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAKF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC"}
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"}
@@ -6,6 +6,7 @@ export type DevServerOptions = {
6
6
  directory: string;
7
7
  manifest: FunctionManifest;
8
8
  reload?: boolean;
9
+ dataDir?: string;
9
10
  };
10
11
  /**
11
12
  * Functions dev server provides a local HTTP server for testing functions.
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../../../src/runtime/dev-server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAI3C,OAAO,EAAwB,KAAK,eAAe,EAAiB,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,qBAAa,SAAS;IAYlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAErG,OAAO,CAAC,OAAO,CAAC,CAAc;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;gBAIE,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,gBAAgB;IAG7C,IAAI,QAAQ,WAGX;IAED,IAAI,KAAK,uBAER;IAED,IAAI,SAAS;;;QAEZ;IAEK,UAAU;IAUV,KAAK;IAwCL,IAAI;IAqBV;;OAEG;YACW,KAAK;IAqBnB;;OAEG;YACW,OAAO;CAwBtB"}
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../../../src/runtime/dev-server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAI3C,OAAO,EAAwB,KAAK,eAAe,EAAiB,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,qBAAa,SAAS;IAYlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAErG,OAAO,CAAC,OAAO,CAAC,CAAc;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;gBAIE,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,gBAAgB;IAG7C,IAAI,QAAQ,WAGX;IAED,IAAI,KAAK,uBAER;IAED,IAAI,SAAS;;;QAEZ;IAEK,UAAU;IAUV,KAAK;IAwCL,IAAI;IAqBV;;OAEG;YACW,KAAK;IAqBnB;;OAEG;YACW,OAAO;CAyBtB"}
@@ -1,7 +1,10 @@
1
1
  import { type Client } from '@dxos/client';
2
+ import { type FunctionSubscriptionEvent } from '../handler';
2
3
  import { type FunctionManifest } from '../manifest';
4
+ type Callback = (data: FunctionSubscriptionEvent) => Promise<number>;
3
5
  type SchedulerOptions = {
4
- endpoint: string;
6
+ endpoint?: string;
7
+ callback?: Callback;
5
8
  };
6
9
  /**
7
10
  * Functions scheduler.
@@ -11,12 +14,14 @@ export declare class Scheduler {
11
14
  private readonly _manifest;
12
15
  private readonly _options;
13
16
  private readonly _mounts;
14
- constructor(_client: Client, _manifest: FunctionManifest, _options: SchedulerOptions);
17
+ constructor(_client: Client, _manifest: FunctionManifest, _options?: SchedulerOptions);
15
18
  start(): Promise<void>;
16
19
  stop(): Promise<void>;
17
20
  private mount;
18
21
  private unmount;
19
- private execFunction;
22
+ private _createTimer;
23
+ private _createSubscription;
24
+ private _execFunction;
20
25
  }
21
26
  export {};
22
27
  //# sourceMappingURL=scheduler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../../src/runtime/scheduler.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,MAAM,EAAkB,MAAM,cAAc,CAAC;AAQ3D,OAAO,EAAoB,KAAK,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAE5F,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AAEH,qBAAa,SAAS;IAQlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAG6B;gBAGlC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,EAAE,gBAAgB;IAGvC,KAAK;IAWL,IAAI;YAMI,KAAK;YA+DL,OAAO;YASP,YAAY;CAiB3B"}
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../../src/runtime/scheduler.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,MAAM,EAAkB,MAAM,cAAc,CAAC;AAQ3D,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAoB,KAAK,gBAAgB,EAAkD,MAAM,aAAa,CAAC;AAEtH,KAAK,QAAQ,GAAG,CAAC,IAAI,EAAE,yBAAyB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAErE,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAEF;;GAEG;AAEH,qBAAa,SAAS;IAQlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAG6B;gBAGlC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,GAAE,gBAAqB;IAG5C,KAAK;IAWL,IAAI;YAMI,KAAK;YA0BL,OAAO;IASrB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,mBAAmB;YAoDb,aAAa;CA0B5B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scheduler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.test.d.ts","sourceRoot":"","sources":["../../../../src/runtime/scheduler.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/functions",
3
- "version": "0.3.9-main.325dd0a",
3
+ "version": "0.3.9-main.35264cb",
4
4
  "description": "Functions SDK and runtime.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -19,17 +19,19 @@
19
19
  "src"
20
20
  ],
21
21
  "dependencies": {
22
+ "@preact/signals-core": "^1.5.0",
22
23
  "cron": "^3.1.6",
24
+ "deepsignal": "1.4.0-shallow.0",
23
25
  "express": "^4.17.1",
24
26
  "get-port-please": "^3.1.1",
25
- "@dxos/async": "0.3.9-main.325dd0a",
26
- "@dxos/client": "0.3.9-main.325dd0a",
27
- "@dxos/echo-schema": "0.3.9-main.325dd0a",
28
- "@dxos/context": "0.3.9-main.325dd0a",
29
- "@dxos/invariant": "0.3.9-main.325dd0a",
30
- "@dxos/log": "0.3.9-main.325dd0a",
31
- "@dxos/node-std": "0.3.9-main.325dd0a",
32
- "@dxos/util": "0.3.9-main.325dd0a"
27
+ "@dxos/async": "0.3.9-main.35264cb",
28
+ "@dxos/client": "0.3.9-main.35264cb",
29
+ "@dxos/context": "0.3.9-main.35264cb",
30
+ "@dxos/echo-schema": "0.3.9-main.35264cb",
31
+ "@dxos/invariant": "0.3.9-main.35264cb",
32
+ "@dxos/log": "0.3.9-main.35264cb",
33
+ "@dxos/node-std": "0.3.9-main.35264cb",
34
+ "@dxos/util": "0.3.9-main.35264cb"
33
35
  },
34
36
  "devDependencies": {
35
37
  "@types/express": "^4.17.17"
package/src/handler.ts CHANGED
@@ -2,7 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Client } from '@dxos/client';
5
+ import { type Client, PublicKey } from '@dxos/client';
6
+ import { type Space } from '@dxos/client/echo';
7
+ import { isTypedObject, type TypedObject } from '@dxos/echo-schema';
8
+ import { log } from '@dxos/log';
9
+ import { nonNullable } from '@dxos/util';
6
10
 
7
11
  // TODO(burdon): No response?
8
12
  export interface Response {
@@ -12,6 +16,7 @@ export interface Response {
12
16
  // TODO(burdon): Limit access to individual space?
13
17
  export interface FunctionContext {
14
18
  client: Client;
19
+ dataDir?: string;
15
20
  }
16
21
 
17
22
  // TODO(burdon): Model after http request. Ref Lambda/OpenFaaS.
@@ -23,6 +28,44 @@ export type FunctionHandler<T extends {}> = (params: {
23
28
  }) => Promise<Response | void>;
24
29
 
25
30
  export type FunctionSubscriptionEvent = {
26
- space: string; // TODO(burdon): Convert to PublicKey.
27
- objects: string[];
31
+ space?: string; // TODO(burdon): Convert to PublicKey.
32
+ objects?: string[];
33
+ };
34
+
35
+ export type FunctionSubscriptionEvent2 = {
36
+ space?: Space;
37
+ objects?: TypedObject[];
38
+ };
39
+
40
+ /**
41
+ * Handler wrapper for subscription events; extracts space and objects.
42
+ *
43
+ * To test:
44
+ * ```
45
+ * curl -s -X POST -H "Content-Type: application/json" --data '{"space": "0446...1cbb"}' http://localhost:7100/dev/email-extractor
46
+ * ```
47
+ *
48
+ * NOTE: Get space key from devtools or `dx space list --json`
49
+ */
50
+ export const subscriptionHandler = (
51
+ handler: FunctionHandler<FunctionSubscriptionEvent2>,
52
+ ): FunctionHandler<FunctionSubscriptionEvent> => {
53
+ return ({ event, context, ...rest }) => {
54
+ const { client } = context;
55
+ const space = event.space ? client.spaces.get(PublicKey.from(event.space)) : undefined;
56
+ const objects =
57
+ space &&
58
+ event.objects
59
+ ?.map<TypedObject | undefined>((id) => space!.db.getObjectById(id))
60
+ .filter(nonNullable)
61
+ .filter(isTypedObject);
62
+
63
+ if (!!event.space && !space) {
64
+ log.warn('invalid space', { event });
65
+ } else {
66
+ log.info('handler', { space: space?.key.truncate(), objects: objects?.length });
67
+ }
68
+
69
+ return handler({ event: { space, objects }, context, ...rest });
70
+ };
28
71
  };
package/src/manifest.ts CHANGED
@@ -15,11 +15,13 @@ export type FunctionDef = {
15
15
  description?: string;
16
16
  };
17
17
 
18
+ // TODO(burdon): Query DSL.
18
19
  export type TriggerSubscription = {
19
20
  type: string;
20
21
  spaceKey: string;
21
22
  props?: Record<string, any>;
22
- nested?: string[];
23
+ deep?: boolean; // Watch changes to object (not just creation).
24
+ delay?: number;
23
25
  };
24
26
 
25
27
  // TODO(burdon): Generalize binding.
@@ -28,7 +30,7 @@ export type TriggerSubscription = {
28
30
  export type FunctionTrigger = {
29
31
  function: string;
30
32
  schedule?: string;
31
- subscription?: TriggerSubscription;
33
+ subscriptions?: TriggerSubscription[];
32
34
  };
33
35
 
34
36
  /**
@@ -20,6 +20,7 @@ export type DevServerOptions = {
20
20
  directory: string;
21
21
  manifest: FunctionManifest;
22
22
  reload?: boolean;
23
+ dataDir?: string;
23
24
  };
24
25
 
25
26
  /**
@@ -79,13 +80,13 @@ export class DevServer {
79
80
  res.statusCode = await this._invoke(name, req.body);
80
81
  res.end();
81
82
  } catch (err: any) {
82
- log.error(err);
83
+ log.error(`Function failed: ${name}`, err);
83
84
  res.statusCode = 500;
84
85
  res.end();
85
86
  }
86
87
  });
87
88
 
88
- this._port = await getPort({ port: 7200, portRange: [7200, 7299] });
89
+ this._port = await getPort({ host: 'localhost', port: 7200, portRange: [7200, 7299] });
89
90
  this._server = app.listen(this._port);
90
91
 
91
92
  try {
@@ -161,6 +162,7 @@ export class DevServer {
161
162
 
162
163
  const context: FunctionContext = {
163
164
  client: this._client,
165
+ dataDir: this._options.dataDir,
164
166
  };
165
167
 
166
168
  let statusCode = 200;
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+
7
+ import { Trigger } from '@dxos/async';
8
+ import { Client } from '@dxos/client';
9
+ import { TestBuilder } from '@dxos/client/testing';
10
+ import { describe, test } from '@dxos/test';
11
+
12
+ import { Scheduler } from './scheduler';
13
+ import { type FunctionManifest } from '../manifest';
14
+
15
+ describe('scheduler', () => {
16
+ test('basic', async () => {
17
+ const testBuilder = new TestBuilder();
18
+ const client = new Client({ services: testBuilder.createLocal() });
19
+ await client.initialize();
20
+ await client.halo.createIdentity();
21
+
22
+ const manifest: FunctionManifest = {
23
+ functions: [
24
+ {
25
+ id: 'example.com/function/test',
26
+ name: 'test',
27
+ handler: 'test',
28
+ },
29
+ ],
30
+ triggers: [
31
+ {
32
+ function: 'example.com/function/test',
33
+ schedule: '* * * * * *', // Every second.
34
+ },
35
+ ],
36
+ };
37
+
38
+ let count = 0;
39
+ const done = new Trigger();
40
+ const scheduler = new Scheduler(client, manifest, {
41
+ callback: async () => {
42
+ if (++count === 3) {
43
+ done.wake();
44
+ }
45
+
46
+ return 200;
47
+ },
48
+ });
49
+
50
+ await scheduler.start();
51
+ await done.wait();
52
+ expect(count).to.equal(3);
53
+
54
+ await scheduler.stop();
55
+ await client.destroy();
56
+ });
57
+ });
@@ -4,19 +4,23 @@
4
4
 
5
5
  import { CronJob } from 'cron';
6
6
 
7
- import { DeferredTask } from '@dxos/async';
7
+ import { debounce, DeferredTask } from '@dxos/async';
8
8
  import { type Client, type PublicKey } from '@dxos/client';
9
- import { type Space } from '@dxos/client/echo';
9
+ import { type Space, TextObject } from '@dxos/client/echo';
10
10
  import { Context } from '@dxos/context';
11
- import { Filter, createSubscription } from '@dxos/echo-schema';
11
+ import { Filter, createSubscription, type Query, subscribe } from '@dxos/echo-schema';
12
12
  import { invariant } from '@dxos/invariant';
13
13
  import { log } from '@dxos/log';
14
14
  import { ComplexMap } from '@dxos/util';
15
15
 
16
- import { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../manifest';
16
+ import { type FunctionSubscriptionEvent } from '../handler';
17
+ import { type FunctionDef, type FunctionManifest, type FunctionTrigger, type TriggerSubscription } from '../manifest';
18
+
19
+ type Callback = (data: FunctionSubscriptionEvent) => Promise<number>;
17
20
 
18
21
  type SchedulerOptions = {
19
- endpoint: string;
22
+ endpoint?: string;
23
+ callback?: Callback;
20
24
  };
21
25
 
22
26
  /**
@@ -33,7 +37,7 @@ export class Scheduler {
33
37
  constructor(
34
38
  private readonly _client: Client,
35
39
  private readonly _manifest: FunctionManifest,
36
- private readonly _options: SchedulerOptions,
40
+ private readonly _options: SchedulerOptions = {},
37
41
  ) {}
38
42
 
39
43
  async start() {
@@ -58,6 +62,7 @@ export class Scheduler {
58
62
  const def = this._manifest.functions.find((config) => config.id === trigger.function);
59
63
  invariant(def, `Function not found: ${trigger.function}`);
60
64
 
65
+ // Currently supports only one trigger declaration per function.
61
66
  const exists = this._mounts.get(key);
62
67
  if (!exists) {
63
68
  this._mounts.set(key, { ctx, trigger });
@@ -66,52 +71,14 @@ export class Scheduler {
66
71
  return;
67
72
  }
68
73
 
69
- // Cron schedule.
74
+ // Timer.
70
75
  if (trigger.schedule) {
71
- const task = new DeferredTask(ctx, async () => {
72
- await this.execFunction(def, {
73
- space: space.key,
74
- });
75
- });
76
-
77
- // TODO(burdon): Check greater than 30s min (use cron-parser).
78
- const job = new CronJob(trigger.schedule, () => task.schedule());
79
-
80
- job.start();
81
- ctx.onDispose(() => job.stop());
76
+ this._createTimer(ctx, space, def, trigger);
82
77
  }
83
78
 
84
- // ECHO subscription.
85
- if (trigger.subscription) {
86
- const objectIds = new Set<string>();
87
- const task = new DeferredTask(ctx, async () => {
88
- await this.execFunction(def, {
89
- space: space.key,
90
- objects: Array.from(objectIds),
91
- });
92
- });
93
-
94
- const subscription = createSubscription(({ added, updated }) => {
95
- for (const object of added) {
96
- objectIds.add(object.id);
97
- }
98
- for (const object of updated) {
99
- objectIds.add(object.id);
100
- }
101
-
102
- task.schedule();
103
- });
104
-
105
- const { type, props } = trigger.subscription;
106
- const query = space.db.query(Filter.typename(type, props));
107
- const unsubscribe = query.subscribe(({ objects }) => {
108
- subscription.update(objects);
109
- }, true);
110
-
111
- ctx.onDispose(() => {
112
- subscription.unsubscribe();
113
- unsubscribe();
114
- });
79
+ // Subscription.
80
+ for (const triggerSubscription of trigger.subscriptions ?? []) {
81
+ this._createSubscription(ctx, space, def, triggerSubscription);
115
82
  }
116
83
  }
117
84
  }
@@ -125,19 +92,95 @@ export class Scheduler {
125
92
  }
126
93
  }
127
94
 
128
- private async execFunction(def: FunctionDef, data: any) {
95
+ private _createTimer(ctx: Context, space: Space, def: FunctionDef, trigger: FunctionTrigger) {
96
+ const task = new DeferredTask(ctx, async () => {
97
+ await this._execFunction(def, {
98
+ space: space.key,
99
+ });
100
+ });
101
+
102
+ // TODO(burdon): Check greater than 30s min (use cron-parser).
103
+ invariant(trigger.schedule);
104
+ const job = new CronJob(trigger.schedule, () => task.schedule());
105
+
106
+ job.start();
107
+ ctx.onDispose(() => job.stop());
108
+ }
109
+
110
+ private _createSubscription(ctx: Context, space: Space, def: FunctionDef, triggerSubscription: TriggerSubscription) {
111
+ const objectIds = new Set<string>();
112
+ const task = new DeferredTask(ctx, async () => {
113
+ await this._execFunction(def, {
114
+ space: space.key,
115
+ objects: Array.from(objectIds),
116
+ });
117
+ });
118
+
119
+ // TODO(burdon): Don't fire initially.
120
+ // TODO(burdon): Standardize subscription handles.
121
+ const subscriptions: (() => void)[] = [];
122
+ const subscription = createSubscription(({ added, updated }) => {
123
+ for (const object of added) {
124
+ objectIds.add(object.id);
125
+ }
126
+ for (const object of updated) {
127
+ objectIds.add(object.id);
128
+ }
129
+
130
+ task.schedule();
131
+ });
132
+ subscriptions.push(() => subscription.unsubscribe());
133
+
134
+ // TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
135
+ // TODO(burdon): Disable trigger if keeps failing.
136
+ const { type, props, deep, delay } = triggerSubscription;
137
+ const update = ({ objects }: Query) => {
138
+ subscription.update(objects);
139
+
140
+ // TODO(burdon): Hack to monitor changes to Document's text object.
141
+ if (deep) {
142
+ log.info('update', { type, deep, objects: objects.length });
143
+ for (const object of objects) {
144
+ const content = object.content;
145
+ if (content instanceof TextObject) {
146
+ subscriptions.push(content[subscribe](debounce(() => subscription.update([object]), 1_000)));
147
+ }
148
+ }
149
+ }
150
+ };
151
+
152
+ // TODO(burdon): [Bug]: all callbacks are fired on the first mutation.
153
+ // TODO(burdon): [Bug]: not updated when document is deleted (either top or hierarchically).
154
+ const query = space.db.query(Filter.typename(type, props));
155
+ subscriptions.push(query.subscribe(delay ? debounce(update, delay * 1_000) : update));
156
+
157
+ ctx.onDispose(() => {
158
+ subscriptions.forEach((unsubscribe) => unsubscribe());
159
+ });
160
+ }
161
+
162
+ private async _execFunction(def: FunctionDef, data: any) {
129
163
  try {
130
164
  log('request', { function: def.id });
131
- const response = await fetch(`${this._options.endpoint}/${def.name}`, {
132
- method: 'POST',
133
- headers: {
134
- 'Content-Type': 'application/json',
135
- },
136
- body: JSON.stringify(data),
137
- });
165
+ const { endpoint, callback } = this._options;
166
+ let status = 0;
167
+ if (endpoint) {
168
+ // TODO(burdon): Move out of scheduler (generalize as callback).
169
+ const response = await fetch(`${this._options.endpoint}/${def.name}`, {
170
+ method: 'POST',
171
+ headers: {
172
+ 'Content-Type': 'application/json',
173
+ },
174
+ body: JSON.stringify(data),
175
+ });
176
+
177
+ status = response.status;
178
+ } else if (callback) {
179
+ status = await callback(data);
180
+ }
138
181
 
139
182
  // const result = await response.json();
140
- log('result', { function: def.id, result: response.status });
183
+ log('result', { function: def.id, result: status });
141
184
  } catch (err: any) {
142
185
  log.error('error', { function: def.id, error: err.message });
143
186
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=function.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"function.test.d.ts","sourceRoot":"","sources":["../../../src/function.test.ts"],"names":[],"mappings":""}
@@ -1,7 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { test } from '@dxos/test';
6
-
7
- test('works', () => {});