@dxos/functions 0.5.3-next.57eca40 → 0.5.3-next.63cdcad

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 (53) hide show
  1. package/dist/lib/browser/{chunk-366QG6IX.mjs → chunk-4D4I3YMJ.mjs} +16 -11
  2. package/dist/lib/browser/chunk-4D4I3YMJ.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +206 -133
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types.mjs +3 -1
  7. package/dist/lib/node/{chunk-3VSJ57ZZ.cjs → chunk-3UYUR5N5.cjs} +19 -13
  8. package/dist/lib/node/chunk-3UYUR5N5.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +216 -139
  10. package/dist/lib/node/index.cjs.map +4 -4
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types.cjs +6 -4
  13. package/dist/lib/node/types.cjs.map +2 -2
  14. package/dist/types/src/browser/index.d.ts +2 -0
  15. package/dist/types/src/browser/index.d.ts.map +1 -0
  16. package/dist/types/src/function/function-registry.d.ts.map +1 -1
  17. package/dist/types/src/handler.d.ts.map +1 -1
  18. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  19. package/dist/types/src/runtime/scheduler.d.ts +1 -1
  20. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  21. package/dist/types/src/testing/setup.d.ts.map +1 -1
  22. package/dist/types/src/trigger/trigger-registry.d.ts +2 -5
  23. package/dist/types/src/trigger/trigger-registry.d.ts.map +1 -1
  24. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +1 -1
  25. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +1 -1
  26. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +1 -1
  27. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -1
  28. package/dist/types/src/types.d.ts +46 -33
  29. package/dist/types/src/types.d.ts.map +1 -1
  30. package/package.json +14 -14
  31. package/schema/functions.json +5 -0
  32. package/src/browser/index.ts +5 -0
  33. package/src/function/function-registry.ts +5 -5
  34. package/src/runtime/dev-server.ts +3 -3
  35. package/src/runtime/scheduler.test.ts +14 -9
  36. package/src/runtime/scheduler.ts +14 -9
  37. package/src/testing/functions-integration.test.ts +1 -0
  38. package/src/testing/setup.ts +8 -10
  39. package/src/trigger/trigger-registry.test.ts +30 -13
  40. package/src/trigger/trigger-registry.ts +59 -37
  41. package/src/trigger/type/subscription-trigger.ts +13 -7
  42. package/src/trigger/type/timer-trigger.ts +4 -3
  43. package/src/trigger/type/webhook-trigger.ts +3 -2
  44. package/src/trigger/type/websocket-trigger.ts +4 -3
  45. package/src/types.ts +42 -30
  46. package/dist/lib/browser/chunk-366QG6IX.mjs.map +0 -7
  47. package/dist/lib/node/chunk-3VSJ57ZZ.cjs.map +0 -7
  48. package/dist/types/src/util.d.ts +0 -15
  49. package/dist/types/src/util.d.ts.map +0 -1
  50. package/dist/types/src/util.test.d.ts +0 -2
  51. package/dist/types/src/util.test.d.ts.map +0 -1
  52. package/src/util.test.ts +0 -43
  53. package/src/util.ts +0 -48
@@ -6,7 +6,7 @@ import { S } from '@dxos/echo-schema';
6
6
  * https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
7
7
  */
8
8
  export type FunctionTriggerType = 'subscription' | 'timer' | 'webhook' | 'websocket';
9
- declare const SubscriptionTriggerSchema: S.struct<{
9
+ declare const SubscriptionTriggerSchema: S.mutable<S.struct<{
10
10
  type: S.literal<["subscription"]>;
11
11
  filter: S.array<S.struct<{
12
12
  type: S.$string;
@@ -23,12 +23,12 @@ declare const SubscriptionTriggerSchema: S.struct<{
23
23
  readonly deep?: boolean | undefined;
24
24
  readonly delay?: number | undefined;
25
25
  } | undefined, never>;
26
- }>;
26
+ }>>;
27
27
  export type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;
28
- declare const TimerTriggerSchema: S.struct<{
28
+ declare const TimerTriggerSchema: S.mutable<S.struct<{
29
29
  type: S.literal<["timer"]>;
30
30
  cron: S.$string;
31
- }>;
31
+ }>>;
32
32
  export type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;
33
33
  declare const WebhookTriggerSchema: S.mutable<S.struct<{
34
34
  type: S.literal<["webhook"]>;
@@ -36,7 +36,7 @@ declare const WebhookTriggerSchema: S.mutable<S.struct<{
36
36
  port: S.PropertySignature<"?:", number | undefined, never, "?:", number | undefined, never>;
37
37
  }>>;
38
38
  export type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;
39
- declare const WebsocketTriggerSchema: S.struct<{
39
+ declare const WebsocketTriggerSchema: S.mutable<S.struct<{
40
40
  type: S.literal<["websocket"]>;
41
41
  url: S.$string;
42
42
  init: S.PropertySignature<"?:", {
@@ -44,7 +44,7 @@ declare const WebsocketTriggerSchema: S.struct<{
44
44
  } | undefined, never, "?:", {
45
45
  readonly [x: string]: any;
46
46
  } | undefined, never>;
47
- }>;
47
+ }>>;
48
48
  export type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;
49
49
  export type TriggerSpec = TimerTrigger | WebhookTrigger | WebsocketTrigger | SubscriptionTrigger;
50
50
  declare const FunctionDef_base: import("@dxos/echo-schema").EchoSchemaClass<{
@@ -62,36 +62,42 @@ export declare class FunctionDef extends FunctionDef_base {
62
62
  }
63
63
  declare const FunctionTrigger_base: import("@dxos/echo-schema").EchoSchemaClass<{
64
64
  function: string;
65
- meta?: object | undefined;
65
+ enabled?: boolean | undefined;
66
+ meta?: {
67
+ [x: string]: any;
68
+ } | undefined;
66
69
  spec: {
67
- readonly filter: readonly {
70
+ filter: readonly {
68
71
  readonly type: string;
69
72
  readonly props?: {
70
73
  readonly [x: string]: any;
71
74
  } | undefined;
72
75
  }[];
73
- readonly type: "subscription";
74
- readonly options?: {
76
+ type: "subscription";
77
+ options?: {
75
78
  readonly deep?: boolean | undefined;
76
79
  readonly delay?: number | undefined;
77
80
  } | undefined;
78
81
  } | {
79
- readonly type: "timer";
80
- readonly cron: string;
82
+ type: "timer";
83
+ cron: string;
81
84
  } | {
82
85
  port?: number | undefined;
83
86
  type: "webhook";
84
87
  method: string;
85
88
  } | {
86
- readonly type: "websocket";
87
- readonly url: string;
88
- readonly init?: {
89
+ type: "websocket";
90
+ url: string;
91
+ init?: {
89
92
  readonly [x: string]: any;
90
93
  } | undefined;
91
94
  };
92
95
  } & {
93
96
  id: string;
94
97
  }>;
98
+ /**
99
+ * Function trigger.
100
+ */
95
101
  export declare class FunctionTrigger extends FunctionTrigger_base {
96
102
  }
97
103
  /**
@@ -125,30 +131,33 @@ export declare const FunctionManifestSchema: S.struct<{
125
131
  })[] | undefined, never>;
126
132
  triggers: S.PropertySignature<"?:", ({
127
133
  function: string;
128
- meta?: object | undefined;
134
+ enabled?: boolean | undefined;
135
+ meta?: {
136
+ [x: string]: any;
137
+ } | undefined;
129
138
  spec: {
130
- readonly filter: readonly {
139
+ filter: readonly {
131
140
  readonly type: string;
132
141
  readonly props?: {
133
142
  readonly [x: string]: any;
134
143
  } | undefined;
135
144
  }[];
136
- readonly type: "subscription";
137
- readonly options?: {
145
+ type: "subscription";
146
+ options?: {
138
147
  readonly deep?: boolean | undefined;
139
148
  readonly delay?: number | undefined;
140
149
  } | undefined;
141
150
  } | {
142
- readonly type: "timer";
143
- readonly cron: string;
151
+ type: "timer";
152
+ cron: string;
144
153
  } | {
145
154
  port?: number | undefined;
146
155
  type: "webhook";
147
156
  method: string;
148
157
  } | {
149
- readonly type: "websocket";
150
- readonly url: string;
151
- readonly init?: {
158
+ type: "websocket";
159
+ url: string;
160
+ init?: {
152
161
  readonly [x: string]: any;
153
162
  } | undefined;
154
163
  };
@@ -161,30 +170,33 @@ export declare const FunctionManifestSchema: S.struct<{
161
170
  } | undefined;
162
171
  })[] | undefined, never, "?:", ({
163
172
  function: string;
164
- meta?: object | undefined;
173
+ enabled?: boolean | undefined;
174
+ meta?: {
175
+ [x: string]: any;
176
+ } | undefined;
165
177
  spec: {
166
- readonly filter: readonly {
178
+ filter: readonly {
167
179
  readonly type: string;
168
180
  readonly props?: {
169
181
  readonly [x: string]: any;
170
182
  } | undefined;
171
183
  }[];
172
- readonly type: "subscription";
173
- readonly options?: {
184
+ type: "subscription";
185
+ options?: {
174
186
  readonly deep?: boolean | undefined;
175
187
  readonly delay?: number | undefined;
176
188
  } | undefined;
177
189
  } | {
178
- readonly type: "timer";
179
- readonly cron: string;
190
+ type: "timer";
191
+ cron: string;
180
192
  } | {
181
193
  port?: number | undefined;
182
194
  type: "webhook";
183
195
  method: string;
184
196
  } | {
185
- readonly type: "websocket";
186
- readonly url: string;
187
- readonly init?: {
197
+ type: "websocket";
198
+ url: string;
199
+ init?: {
188
200
  readonly [x: string]: any;
189
201
  } | undefined;
190
202
  };
@@ -198,5 +210,6 @@ export declare const FunctionManifestSchema: S.struct<{
198
210
  })[] | undefined, never>;
199
211
  }>;
200
212
  export type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;
213
+ export declare const FUNCTION_SCHEMA: (typeof FunctionDef | typeof FunctionTrigger)[];
201
214
  export {};
202
215
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAa,CAAC,EAAe,MAAM,mBAAmB,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAErF,QAAA,MAAM,yBAAyB;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,QAAA,MAAM,kBAAkB;;;EAGtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,QAAA,MAAM,oBAAoB;;;;GAOzB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,QAAA,MAAM,sBAAsB;;;;;;;;EAI1B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAS5E,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,cAAc,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;;;;;;;;;AAEjG;;GAEG;AACH,qBAAa,WAAY,SAAQ,gBAS/B;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEL,qBAAa,eAAgB,SAAQ,oBAQnC;CAAG;AAEL;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAa,CAAC,EAAe,MAAM,mBAAmB,CAAC;AAE9D;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAErF,QAAA,MAAM,yBAAyB;;;;;;;;;;;;;;;;;GAmB9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,QAAA,MAAM,kBAAkB;;;GAKvB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,QAAA,MAAM,oBAAoB;;;;GAOzB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,QAAA,MAAM,sBAAsB;;;;;;;;GAM3B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAS5E,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,cAAc,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;;;;;;;;;AAEjG;;GAEG;AACH,qBAAa,WAAY,SAAQ,gBAQ/B;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEL;;GAEG;AACH,qBAAa,eAAgB,SAAQ,oBASnC;CAAG;AAEL;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAG5E,eAAO,MAAM,eAAe,iDAAiC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/functions",
3
- "version": "0.5.3-next.57eca40",
3
+ "version": "0.5.3-next.63cdcad",
4
4
  "description": "Functions API and runtime.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -40,23 +40,23 @@
40
40
  "express": "^4.19.2",
41
41
  "get-port-please": "^3.1.1",
42
42
  "ws": "^8.14.2",
43
- "@braneframe/types": "0.5.3-next.57eca40",
44
- "@dxos/client": "0.5.3-next.57eca40",
45
- "@dxos/context": "0.5.3-next.57eca40",
46
- "@dxos/echo-db": "0.5.3-next.57eca40",
47
- "@dxos/async": "0.5.3-next.57eca40",
48
- "@dxos/invariant": "0.5.3-next.57eca40",
49
- "@dxos/echo-schema": "0.5.3-next.57eca40",
50
- "@dxos/protocols": "0.5.3-next.57eca40",
51
- "@dxos/util": "0.5.3-next.57eca40",
52
- "@dxos/node-std": "0.5.3-next.57eca40",
53
- "@dxos/log": "0.5.3-next.57eca40",
54
- "@dxos/keys": "0.5.3-next.57eca40"
43
+ "@braneframe/types": "0.5.3-next.63cdcad",
44
+ "@dxos/async": "0.5.3-next.63cdcad",
45
+ "@dxos/context": "0.5.3-next.63cdcad",
46
+ "@dxos/client": "0.5.3-next.63cdcad",
47
+ "@dxos/echo-db": "0.5.3-next.63cdcad",
48
+ "@dxos/invariant": "0.5.3-next.63cdcad",
49
+ "@dxos/keys": "0.5.3-next.63cdcad",
50
+ "@dxos/log": "0.5.3-next.63cdcad",
51
+ "@dxos/node-std": "0.5.3-next.63cdcad",
52
+ "@dxos/echo-schema": "0.5.3-next.63cdcad",
53
+ "@dxos/protocols": "0.5.3-next.63cdcad",
54
+ "@dxos/util": "0.5.3-next.63cdcad"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/express": "^4.17.17",
58
58
  "@types/ws": "^7.4.0",
59
- "@dxos/agent": "0.5.3-next.57eca40"
59
+ "@dxos/agent": "0.5.3-next.63cdcad"
60
60
  },
61
61
  "publishConfig": {
62
62
  "access": "public"
@@ -51,6 +51,11 @@
51
51
  "description": "Function URI.",
52
52
  "title": "string"
53
53
  },
54
+ "enabled": {
55
+ "type": "boolean",
56
+ "description": "a boolean",
57
+ "title": "boolean"
58
+ },
54
59
  "meta": {
55
60
  "$ref": "#/$defs/object"
56
61
  },
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from '../types';
@@ -8,10 +8,9 @@ import { create, Filter, type Space } from '@dxos/client/echo';
8
8
  import { type Context, Resource } from '@dxos/context';
9
9
  import { PublicKey } from '@dxos/keys';
10
10
  import { log } from '@dxos/log';
11
- import { ComplexMap } from '@dxos/util';
11
+ import { ComplexMap, diff } from '@dxos/util';
12
12
 
13
13
  import { FunctionDef, type FunctionManifest } from '../types';
14
- import { diff } from '../util';
15
14
 
16
15
  export type FunctionsRegisteredEvent = {
17
16
  space: Space;
@@ -46,13 +45,13 @@ export class FunctionRegistry extends Resource {
46
45
 
47
46
  // Sync definitions.
48
47
  const { objects: existing } = await space.db.query(Filter.schema(FunctionDef)).run();
49
- const { added, removed } = diff(existing, functions, (a, b) => a.uri === b.uri);
50
- added.forEach((def) => space.db.add(create(FunctionDef, def)));
48
+ const { added } = diff(existing, functions, (a, b) => a.uri === b.uri);
51
49
  // TODO(burdon): Update existing templates.
52
- removed.forEach((def) => space.db.remove(def));
50
+ added.forEach((def) => space.db.add(create(FunctionDef, def)));
53
51
  }
54
52
 
55
53
  protected override async _open(): Promise<void> {
54
+ log.info('opening...');
56
55
  const spacesSubscription = this._client.spaces.subscribe(async (spaces) => {
57
56
  for (const space of spaces) {
58
57
  if (this._functionBySpaceKey.has(space.key)) {
@@ -85,6 +84,7 @@ export class FunctionRegistry extends Resource {
85
84
  }
86
85
 
87
86
  protected override async _close(_: Context): Promise<void> {
87
+ log.info('closing...');
88
88
  this._functionBySpaceKey.clear();
89
89
  }
90
90
  }
@@ -46,6 +46,7 @@ export class DevServer {
46
46
  private readonly _functionsRegistry: FunctionRegistry,
47
47
  private readonly _options: DevServerOptions,
48
48
  ) {
49
+ // TODO(burdon): Add/remove listener in start/stop.
49
50
  this._functionsRegistry.registered.on(async ({ added }) => {
50
51
  added.forEach((def) => this._load(def));
51
52
  await this._safeUpdateRegistration();
@@ -188,8 +189,8 @@ export class DevServer {
188
189
  registrationId: this._functionServiceRegistration,
189
190
  functions: this.functions.map(({ def: { id, route } }) => ({ id, route })),
190
191
  });
191
- } catch (e) {
192
- log.catch(e);
192
+ } catch (err) {
193
+ log.catch(err);
193
194
  }
194
195
  }
195
196
 
@@ -211,7 +212,6 @@ export class DevServer {
211
212
  private async _invoke(path: string, event: FunctionEvent) {
212
213
  const { handler } = this._handlers[path] ?? {};
213
214
  invariant(handler, `invalid path: ${path}`);
214
-
215
215
  const context: FunctionContext = {
216
216
  client: this._client,
217
217
  dataDir: this._options.dataDir,
@@ -29,6 +29,15 @@ describe('scheduler', () => {
29
29
  await testBuilder.destroy();
30
30
  });
31
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
+
32
41
  test('timer', async () => {
33
42
  const manifest: FunctionManifest = {
34
43
  functions: [
@@ -41,6 +50,7 @@ describe('scheduler', () => {
41
50
  triggers: [
42
51
  {
43
52
  function: 'example.com/function/test',
53
+ enabled: true,
44
54
  spec: {
45
55
  type: 'timer',
46
56
  cron: '0/1 * * * * *', // Every 1s.
@@ -75,6 +85,7 @@ describe('scheduler', () => {
75
85
  triggers: [
76
86
  {
77
87
  function: 'example.com/function/test',
88
+ enabled: true,
78
89
  spec: {
79
90
  type: 'webhook',
80
91
  method: 'GET',
@@ -108,6 +119,7 @@ describe('scheduler', () => {
108
119
  triggers: [
109
120
  {
110
121
  function: 'example.com/function/test',
122
+ enabled: true,
111
123
  spec: {
112
124
  type: 'websocket',
113
125
  // url: 'https://hub.dxos.network/api/mailbox/test',
@@ -154,6 +166,7 @@ describe('scheduler', () => {
154
166
  triggers: [
155
167
  {
156
168
  function: 'example.com/function/test',
169
+ enabled: true,
157
170
  spec: {
158
171
  type: 'subscription',
159
172
  filter: [{ type: TestType.typename }],
@@ -165,7 +178,7 @@ describe('scheduler', () => {
165
178
  let count = 0;
166
179
  const done = new Trigger();
167
180
  const scheduler = createScheduler(async () => {
168
- if (++count === 2) {
181
+ if (++count === 1) {
169
182
  done.wake();
170
183
  }
171
184
  });
@@ -180,12 +193,4 @@ describe('scheduler', () => {
180
193
 
181
194
  await done.wait();
182
195
  });
183
-
184
- const createScheduler = (callback: SchedulerOptions['callback']) => {
185
- const scheduler = new Scheduler(new FunctionRegistry(client), new TriggerRegistry(client), { callback });
186
- after(async () => {
187
- await scheduler.stop();
188
- });
189
- return scheduler;
190
- };
191
196
  });
@@ -27,7 +27,7 @@ export type SchedulerOptions = {
27
27
  export class Scheduler {
28
28
  private _ctx = createContext();
29
29
 
30
- private readonly _callMutex = new Mutex();
30
+ private readonly _functionUriToCallMutex = new Map<string, Mutex>();
31
31
 
32
32
  constructor(
33
33
  public readonly functions: FunctionRegistry,
@@ -72,23 +72,28 @@ export class Scheduler {
72
72
  await Promise.all(mountTasks).catch(log.catch);
73
73
  }
74
74
 
75
- private async activate(space: Space, functions: FunctionDef[], fnTrigger: FunctionTrigger) {
76
- const definition = functions.find((def) => def.uri === fnTrigger.function);
75
+ private async activate(space: Space, functions: FunctionDef[], trigger: FunctionTrigger) {
76
+ const definition = functions.find((def) => def.uri === trigger.function);
77
77
  if (!definition) {
78
- log.info('function is not found for trigger', { fnTrigger });
78
+ log.info('function is not found for trigger', { trigger });
79
79
  return;
80
80
  }
81
81
 
82
- await this.triggers.activate({ space }, fnTrigger, async (args) => {
83
- return this._callMutex.executeSynchronized(() => {
84
- return this._execFunction(definition, fnTrigger, {
85
- meta: fnTrigger.meta,
82
+ await this.triggers.activate(space, trigger, async (args) => {
83
+ const mutex = this._functionUriToCallMutex.get(definition.uri) ?? new Mutex();
84
+ this._functionUriToCallMutex.set(definition.uri, mutex);
85
+
86
+ log.info('function triggered, waiting for mutex', { uri: definition.uri });
87
+ return mutex.executeSynchronized(() => {
88
+ log.info('mutex acquired', { uri: definition.uri });
89
+ return this._execFunction(definition, trigger, {
90
+ meta: trigger.meta ?? {},
86
91
  data: { ...args, spaceKey: space.key },
87
92
  });
88
93
  });
89
94
  });
90
95
 
91
- log('activated trigger', { space: space.key, trigger: fnTrigger });
96
+ log('activated trigger', { space: space.key, trigger });
92
97
  }
93
98
 
94
99
  private async _execFunction<TData, TMeta>(
@@ -44,6 +44,7 @@ describe('functions e2e', () => {
44
44
  space.db.add(
45
45
  create(FunctionTrigger, {
46
46
  function: uri,
47
+ enabled: true,
47
48
  meta: triggerMeta,
48
49
  spec: {
49
50
  type: 'subscription',
@@ -10,16 +10,16 @@ import { range } from '@dxos/util';
10
10
  import { TestType } from './types';
11
11
  import { FunctionDef, FunctionTrigger } from '../types';
12
12
 
13
- // TODO(burdon): Create new or extend existing TestBuilder.
13
+ // TODO(burdon): Extend/wrap TestBuilder.
14
14
 
15
15
  export const createInitializedClients = async (testBuilder: TestBuilder, count: number = 1, config?: Config) => {
16
16
  const clients = range(count).map(() => new Client({ config, services: testBuilder.createLocalClientServices() }));
17
- testBuilder.ctx.onDispose(() => Promise.all(clients.map((c) => c.destroy())));
17
+ testBuilder.ctx.onDispose(() => Promise.all(clients.map((client) => client.destroy())));
18
18
  return Promise.all(
19
19
  clients.map(async (client, index) => {
20
20
  await client.initialize();
21
21
  await client.halo.createIdentity({ displayName: `Peer ${index}` });
22
- client.addSchema(TestType, FunctionDef, FunctionTrigger);
22
+ client.addSchema(FunctionDef, FunctionTrigger, TestType);
23
23
  return client;
24
24
  }),
25
25
  );
@@ -34,12 +34,10 @@ export const createFunctionRuntime = async (testBuilder: TestBuilder): Promise<C
34
34
  },
35
35
  });
36
36
 
37
- const client = (await createInitializedClients(testBuilder, 1, config))[0];
38
-
39
- // TODO(burdon): Better way to configure plugin? (Rationalize chess.test).
40
- const functionsPlugin = new FunctionsPlugin();
41
- await functionsPlugin.initialize({ client, clientServices: client.services });
42
- await functionsPlugin.open();
43
- testBuilder.ctx.onDispose(() => functionsPlugin.close());
37
+ const [client] = await createInitializedClients(testBuilder, 1, config);
38
+ const plugin = new FunctionsPlugin();
39
+ await plugin.initialize({ client, clientServices: client.services });
40
+ await plugin.open();
41
+ testBuilder.ctx.onDispose(() => plugin.close());
44
42
  return client;
45
43
  };
@@ -31,6 +31,7 @@ const manifest: FunctionManifest = {
31
31
  ],
32
32
  },
33
33
  function: 'example.com/function/webhook-test',
34
+ enabled: true,
34
35
  spec: {
35
36
  type: 'webhook',
36
37
  method: 'GET',
@@ -46,6 +47,7 @@ const manifest: FunctionManifest = {
46
47
  ],
47
48
  },
48
49
  function: 'example.com/function/subscription-test',
50
+ enabled: true,
49
51
  spec: {
50
52
  type: 'subscription',
51
53
  filter: [
@@ -74,7 +76,7 @@ describe('trigger registry', () => {
74
76
 
75
77
  describe('register', () => {
76
78
  test('creates new triggers', async () => {
77
- const client = (await createInitializedClients(testBuilder))[0];
79
+ const [client] = await createInitializedClients(testBuilder);
78
80
  const registry = createRegistry(client);
79
81
  const space = await client.spaces.create();
80
82
  await registry.register(space, manifest);
@@ -84,11 +86,24 @@ describe('trigger registry', () => {
84
86
  const expected = manifest.triggers?.map((trigger) => trigger.function).sort();
85
87
  expect(objects.map((object: FunctionTrigger) => object.function).sort()).to.deep.eq(expected);
86
88
  });
89
+
90
+ test('set meta', () => {
91
+ const trigger = create(FunctionTrigger, {
92
+ function: 'example.com/function/webhook-test',
93
+ spec: {
94
+ type: 'webhook',
95
+ method: 'GET',
96
+ },
97
+ });
98
+
99
+ (trigger.meta ??= {}).test = 100;
100
+ expect(trigger.meta.test).to.eq(100);
101
+ });
87
102
  });
88
103
 
89
104
  describe('activate', () => {
90
105
  test('invokes the provided callback', async () => {
91
- const client = (await createInitializedClients(testBuilder))[0];
106
+ const [client] = await createInitializedClients(testBuilder);
92
107
  const space = await client.spaces.create();
93
108
  const registry = createRegistry(client);
94
109
  await registry.register(space, manifest);
@@ -98,7 +113,7 @@ describe('trigger registry', () => {
98
113
  const callbackInvoked = new Trigger();
99
114
  const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
100
115
  const webhookTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'webhook')!;
101
- await registry.activate({ space }, webhookTrigger, async () => {
116
+ await registry.activate(space, webhookTrigger, async () => {
102
117
  callbackInvoked.wake();
103
118
  return 200;
104
119
  });
@@ -108,7 +123,7 @@ describe('trigger registry', () => {
108
123
  });
109
124
 
110
125
  test('removes from inactive list', async () => {
111
- const client = (await createInitializedClients(testBuilder))[0];
126
+ const [client] = await createInitializedClients(testBuilder);
112
127
  const space = await client.spaces.create();
113
128
  const registry = createRegistry(client);
114
129
  await registry.register(space, manifest);
@@ -116,17 +131,19 @@ describe('trigger registry', () => {
116
131
  await waitForInactiveTriggers(registry, space);
117
132
 
118
133
  const inactiveTrigger = registry.getInactiveTriggers(space)[0];
119
- await registry.activate({ space }, inactiveTrigger, async () => 200);
134
+ await registry.activate(space, inactiveTrigger, async () => 200);
120
135
 
121
136
  const updatedInactiveList = registry.getInactiveTriggers(space);
122
137
  expect(updatedInactiveList.find((trigger: FunctionTrigger) => trigger.function === inactiveTrigger.function)).to
123
138
  .be.undefined;
124
139
  });
140
+
141
+ // TODO(burdon): Test enable/disable trigger.
125
142
  });
126
143
 
127
144
  describe('deactivate', () => {
128
145
  test('trigger object deletion deactivates a trigger', async () => {
129
- const client = (await createInitializedClients(testBuilder))[0];
146
+ const [client] = await createInitializedClients(testBuilder);
130
147
  const space = await client.spaces.create();
131
148
  const registry = createRegistry(client);
132
149
  await registry.register(space, manifest);
@@ -136,7 +153,7 @@ describe('trigger registry', () => {
136
153
  const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
137
154
  const echoTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'subscription')!;
138
155
  let count = 0;
139
- await registry.activate({ space }, echoTrigger, async () => {
156
+ await registry.activate(space, echoTrigger, async () => {
140
157
  count++;
141
158
  return 200;
142
159
  });
@@ -152,7 +169,7 @@ describe('trigger registry', () => {
152
169
  });
153
170
 
154
171
  test('registry closing deactivates a trigger', async () => {
155
- const client = (await createInitializedClients(testBuilder))[0];
172
+ const [client] = await createInitializedClients(testBuilder);
156
173
  const space = await client.spaces.create();
157
174
  const registry = createRegistry(client);
158
175
  await registry.register(space, manifest);
@@ -162,7 +179,7 @@ describe('trigger registry', () => {
162
179
  const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
163
180
  const echoTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'subscription')!;
164
181
  let count = 0;
165
- await registry.activate({ space }, echoTrigger, async () => {
182
+ await registry.activate(space, echoTrigger, async () => {
166
183
  count++;
167
184
  return 200;
168
185
  });
@@ -176,8 +193,8 @@ describe('trigger registry', () => {
176
193
  });
177
194
 
178
195
  describe('trigger events', () => {
179
- test.only('event fired when all registered when opened', async () => {
180
- const client = (await createInitializedClients(testBuilder))[0];
196
+ test('event fired when all registered when opened', async () => {
197
+ const [client] = await createInitializedClients(testBuilder);
181
198
  const registry = createRegistry(client);
182
199
  const triggers = createTriggers(client.spaces.default, 3);
183
200
 
@@ -194,7 +211,7 @@ describe('trigger registry', () => {
194
211
  });
195
212
 
196
213
  test('event fired when a new trigger is added', async () => {
197
- const client = (await createInitializedClients(testBuilder))[0];
214
+ const [client] = await createInitializedClients(testBuilder);
198
215
  const registry = createRegistry(client);
199
216
  const space = await client.spaces.create();
200
217
 
@@ -210,7 +227,7 @@ describe('trigger registry', () => {
210
227
  });
211
228
 
212
229
  test('event fired when a new trigger is removed', async () => {
213
- const client = (await createInitializedClients(testBuilder))[0];
230
+ const [client] = await createInitializedClients(testBuilder);
214
231
  const registry = createRegistry(client);
215
232
  const space = await client.spaces.create();
216
233
  const triggers = createTriggers(space, 3);