@dxos/functions 0.5.3-main.d7fe7b5 → 0.5.3-main.eb56347

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-P3HPDHNI.mjs +86 -0
  2. package/dist/lib/browser/chunk-P3HPDHNI.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +327 -310
  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 +14 -0
  7. package/dist/lib/browser/types.mjs.map +7 -0
  8. package/dist/lib/node/chunk-KTLM3JNV.cjs +103 -0
  9. package/dist/lib/node/chunk-KTLM3JNV.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +332 -309
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/types.cjs +35 -0
  14. package/dist/lib/node/types.cjs.map +7 -0
  15. package/dist/types/src/browser/index.d.ts +2 -0
  16. package/dist/types/src/browser/index.d.ts.map +1 -0
  17. package/dist/types/src/{registry → function}/function-registry.d.ts +4 -4
  18. package/dist/types/src/function/function-registry.d.ts.map +1 -0
  19. package/dist/types/src/function/function-registry.test.d.ts.map +1 -0
  20. package/dist/types/src/function/index.d.ts.map +1 -0
  21. package/dist/types/src/index.d.ts +1 -1
  22. package/dist/types/src/index.d.ts.map +1 -1
  23. package/dist/types/src/runtime/dev-server.d.ts +1 -1
  24. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  25. package/dist/types/src/runtime/scheduler.d.ts +2 -1
  26. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  27. package/dist/types/src/trigger/trigger-registry.d.ts.map +1 -1
  28. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +1 -1
  29. package/dist/types/src/types.d.ts +64 -49
  30. package/dist/types/src/types.d.ts.map +1 -1
  31. package/package.json +31 -18
  32. package/schema/functions.json +18 -9
  33. package/src/browser/index.ts +5 -0
  34. package/src/{registry → function}/function-registry.test.ts +10 -10
  35. package/src/function/function-registry.ts +90 -0
  36. package/src/index.ts +1 -1
  37. package/src/runtime/dev-server.test.ts +2 -2
  38. package/src/runtime/dev-server.ts +5 -6
  39. package/src/runtime/scheduler.test.ts +1 -1
  40. package/src/runtime/scheduler.ts +21 -8
  41. package/src/testing/functions-integration.test.ts +1 -1
  42. package/src/testing/setup.ts +1 -1
  43. package/src/trigger/trigger-registry.test.ts +60 -34
  44. package/src/trigger/trigger-registry.ts +38 -13
  45. package/src/trigger/type/subscription-trigger.ts +17 -10
  46. package/src/types.ts +47 -36
  47. package/dist/types/src/registry/function-registry.d.ts.map +0 -1
  48. package/dist/types/src/registry/function-registry.test.d.ts.map +0 -1
  49. package/dist/types/src/registry/index.d.ts.map +0 -1
  50. package/src/registry/function-registry.ts +0 -84
  51. /package/dist/types/src/{registry → function}/function-registry.test.d.ts +0 -0
  52. /package/dist/types/src/{registry → function}/index.d.ts +0 -0
  53. /package/src/{registry → function}/index.ts +0 -0
@@ -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,32 +62,30 @@ 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?: {
66
- readonly [x: string]: any;
67
- } | undefined;
65
+ meta?: object | undefined;
68
66
  spec: {
69
- readonly filter: readonly {
67
+ filter: readonly {
70
68
  readonly type: string;
71
69
  readonly props?: {
72
70
  readonly [x: string]: any;
73
71
  } | undefined;
74
72
  }[];
75
- readonly type: "subscription";
76
- readonly options?: {
73
+ type: "subscription";
74
+ options?: {
77
75
  readonly deep?: boolean | undefined;
78
76
  readonly delay?: number | undefined;
79
77
  } | undefined;
80
78
  } | {
81
- readonly type: "timer";
82
- readonly cron: string;
79
+ type: "timer";
80
+ cron: string;
83
81
  } | {
84
82
  port?: number | undefined;
85
83
  type: "webhook";
86
84
  method: string;
87
85
  } | {
88
- readonly type: "websocket";
89
- readonly url: string;
90
- readonly init?: {
86
+ type: "websocket";
87
+ url: string;
88
+ init?: {
91
89
  readonly [x: string]: any;
92
90
  } | undefined;
93
91
  };
@@ -100,89 +98,106 @@ export declare class FunctionTrigger extends FunctionTrigger_base {
100
98
  * Function manifest file.
101
99
  */
102
100
  export declare const FunctionManifestSchema: S.struct<{
103
- functions: S.PropertySignature<"?:", Omit<{
101
+ functions: S.PropertySignature<"?:", ({
104
102
  handler: string;
105
103
  uri: string;
106
104
  description?: string | undefined;
107
105
  route: string;
108
106
  } & {
109
- id: string;
110
- }, "id">[] | undefined, never, "?:", Omit<{
107
+ "@meta"?: {
108
+ readonly keys: {
109
+ readonly id: string;
110
+ readonly source: string;
111
+ }[];
112
+ } | undefined;
113
+ })[] | undefined, never, "?:", ({
111
114
  handler: string;
112
115
  uri: string;
113
116
  description?: string | undefined;
114
117
  route: string;
115
118
  } & {
116
- id: string;
117
- }, "id">[] | undefined, never>;
118
- triggers: S.PropertySignature<"?:", Omit<{
119
- function: string;
120
- meta?: {
121
- readonly [x: string]: any;
119
+ "@meta"?: {
120
+ readonly keys: {
121
+ readonly id: string;
122
+ readonly source: string;
123
+ }[];
122
124
  } | undefined;
125
+ })[] | undefined, never>;
126
+ triggers: S.PropertySignature<"?:", ({
127
+ function: string;
128
+ meta?: object | undefined;
123
129
  spec: {
124
- readonly filter: readonly {
130
+ filter: readonly {
125
131
  readonly type: string;
126
132
  readonly props?: {
127
133
  readonly [x: string]: any;
128
134
  } | undefined;
129
135
  }[];
130
- readonly type: "subscription";
131
- readonly options?: {
136
+ type: "subscription";
137
+ options?: {
132
138
  readonly deep?: boolean | undefined;
133
139
  readonly delay?: number | undefined;
134
140
  } | undefined;
135
141
  } | {
136
- readonly type: "timer";
137
- readonly cron: string;
142
+ type: "timer";
143
+ cron: string;
138
144
  } | {
139
145
  port?: number | undefined;
140
146
  type: "webhook";
141
147
  method: string;
142
148
  } | {
143
- readonly type: "websocket";
144
- readonly url: string;
145
- readonly init?: {
149
+ type: "websocket";
150
+ url: string;
151
+ init?: {
146
152
  readonly [x: string]: any;
147
153
  } | undefined;
148
154
  };
149
155
  } & {
150
- id: string;
151
- }, "id">[] | undefined, never, "?:", Omit<{
152
- function: string;
153
- meta?: {
154
- readonly [x: string]: any;
156
+ "@meta"?: {
157
+ readonly keys: {
158
+ readonly id: string;
159
+ readonly source: string;
160
+ }[];
155
161
  } | undefined;
162
+ })[] | undefined, never, "?:", ({
163
+ function: string;
164
+ meta?: object | undefined;
156
165
  spec: {
157
- readonly filter: readonly {
166
+ filter: readonly {
158
167
  readonly type: string;
159
168
  readonly props?: {
160
169
  readonly [x: string]: any;
161
170
  } | undefined;
162
171
  }[];
163
- readonly type: "subscription";
164
- readonly options?: {
172
+ type: "subscription";
173
+ options?: {
165
174
  readonly deep?: boolean | undefined;
166
175
  readonly delay?: number | undefined;
167
176
  } | undefined;
168
177
  } | {
169
- readonly type: "timer";
170
- readonly cron: string;
178
+ type: "timer";
179
+ cron: string;
171
180
  } | {
172
181
  port?: number | undefined;
173
182
  type: "webhook";
174
183
  method: string;
175
184
  } | {
176
- readonly type: "websocket";
177
- readonly url: string;
178
- readonly init?: {
185
+ type: "websocket";
186
+ url: string;
187
+ init?: {
179
188
  readonly [x: string]: any;
180
189
  } | undefined;
181
190
  };
182
191
  } & {
183
- id: string;
184
- }, "id">[] | undefined, never>;
192
+ "@meta"?: {
193
+ readonly keys: {
194
+ readonly id: string;
195
+ readonly source: string;
196
+ }[];
197
+ } | undefined;
198
+ })[] | undefined, never>;
185
199
  }>;
186
200
  export type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;
201
+ export declare const FUNCTION_SCHEMA: (typeof FunctionDef | typeof FunctionTrigger)[];
187
202
  export {};
188
203
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAO,CAAC,EAAe,MAAM,mBAAmB,CAAC;AAKxD;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAErF,QAAA,MAAM,yBAAyB;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,QAAA,MAAM,kBAAkB;;;EAGtB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,QAAA,MAAM,oBAAoB;;;;GAOzB,CAAC;AACF,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,QAAA,MAAM,sBAAsB;;;;;;;;EAI1B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAQ5E,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,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;AAG5E,eAAO,MAAM,eAAe,iDAAiC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dxos/functions",
3
- "version": "0.5.3-main.d7fe7b5",
4
- "description": "Functions SDK and runtime.",
3
+ "version": "0.5.3-main.eb56347",
4
+ "description": "Functions API and runtime.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
7
  "license": "MIT",
@@ -10,10 +10,24 @@
10
10
  ".": {
11
11
  "browser": "./dist/lib/browser/index.mjs",
12
12
  "node": "./dist/lib/node/index.cjs",
13
- "default": "./dist/lib/node/index.cjs"
13
+ "default": "./dist/lib/node/index.cjs",
14
+ "types": "./dist/types/src/index.d.ts"
15
+ },
16
+ "./types": {
17
+ "browser": "./dist/lib/browser/types.mjs",
18
+ "node": "./dist/lib/node/types.cjs",
19
+ "default": "./dist/lib/node/types.cjs",
20
+ "types": "./dist/types/src/types.d.ts"
14
21
  }
15
22
  },
16
23
  "types": "dist/types/src/index.d.ts",
24
+ "typesVersions": {
25
+ "*": {
26
+ "types": [
27
+ "dist/types/src/types.d.ts"
28
+ ]
29
+ }
30
+ },
17
31
  "files": [
18
32
  "dist",
19
33
  "schema",
@@ -26,29 +40,28 @@
26
40
  "express": "^4.19.2",
27
41
  "get-port-please": "^3.1.1",
28
42
  "ws": "^8.14.2",
29
- "@braneframe/types": "0.5.3-main.d7fe7b5",
30
- "@dxos/async": "0.5.3-main.d7fe7b5",
31
- "@dxos/context": "0.5.3-main.d7fe7b5",
32
- "@dxos/client": "0.5.3-main.d7fe7b5",
33
- "@dxos/echo-db": "0.5.3-main.d7fe7b5",
34
- "@dxos/invariant": "0.5.3-main.d7fe7b5",
35
- "@dxos/keys": "0.5.3-main.d7fe7b5",
36
- "@dxos/echo-schema": "0.5.3-main.d7fe7b5",
37
- "@dxos/log": "0.5.3-main.d7fe7b5",
38
- "@dxos/node-std": "0.5.3-main.d7fe7b5",
39
- "@dxos/util": "0.5.3-main.d7fe7b5",
40
- "@dxos/protocols": "0.5.3-main.d7fe7b5"
43
+ "@braneframe/types": "0.5.3-main.eb56347",
44
+ "@dxos/client": "0.5.3-main.eb56347",
45
+ "@dxos/async": "0.5.3-main.eb56347",
46
+ "@dxos/context": "0.5.3-main.eb56347",
47
+ "@dxos/echo-db": "0.5.3-main.eb56347",
48
+ "@dxos/echo-schema": "0.5.3-main.eb56347",
49
+ "@dxos/invariant": "0.5.3-main.eb56347",
50
+ "@dxos/keys": "0.5.3-main.eb56347",
51
+ "@dxos/log": "0.5.3-main.eb56347",
52
+ "@dxos/node-std": "0.5.3-main.eb56347",
53
+ "@dxos/protocols": "0.5.3-main.eb56347",
54
+ "@dxos/util": "0.5.3-main.eb56347"
41
55
  },
42
56
  "devDependencies": {
43
57
  "@types/express": "^4.17.17",
44
58
  "@types/ws": "^7.4.0",
45
- "@dxos/agent": "0.5.3-main.d7fe7b5"
59
+ "@dxos/agent": "0.5.3-main.eb56347"
46
60
  },
47
61
  "publishConfig": {
48
62
  "access": "public"
49
63
  },
50
64
  "scripts": {
51
- "gen-schema": "ts-node ./tools/schema.ts",
52
- "prebuild": "pnpm gen-schema"
65
+ "gen-schema": "ts-node ./tools/schema.ts"
53
66
  }
54
67
  }
@@ -48,17 +48,11 @@
48
48
  "properties": {
49
49
  "function": {
50
50
  "type": "string",
51
- "description": "Function ID/URI.",
51
+ "description": "Function URI.",
52
52
  "title": "string"
53
53
  },
54
54
  "meta": {
55
- "type": "object",
56
- "required": [],
57
- "properties": {},
58
- "additionalProperties": {
59
- "$id": "/schemas/any",
60
- "title": "any"
61
- }
55
+ "$ref": "#/$defs/object"
62
56
  },
63
57
  "spec": {
64
58
  "anyOf": [
@@ -193,5 +187,20 @@
193
187
  }
194
188
  }
195
189
  },
196
- "additionalProperties": false
190
+ "additionalProperties": false,
191
+ "$defs": {
192
+ "object": {
193
+ "$id": "/schemas/object",
194
+ "oneOf": [
195
+ {
196
+ "type": "object"
197
+ },
198
+ {
199
+ "type": "array"
200
+ }
201
+ ],
202
+ "description": "an object in the TypeScript meaning, i.e. the `object` type",
203
+ "title": "object"
204
+ }
205
+ }
197
206
  }
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from '../types';
@@ -44,7 +44,7 @@ describe('function registry', () => {
44
44
  const client = (await createInitializedClients(testBuilder))[0];
45
45
  const registry = createRegistry(client);
46
46
  const space = await client.spaces.create();
47
- await registry.register(space, testManifest);
47
+ await registry.register(space, testManifest.functions);
48
48
  const { objects: definitions } = await space.db.query(Filter.schema(FunctionDef)).run();
49
49
  expect(definitions.length).to.eq(1);
50
50
  expect(definitions[0].uri).to.eq(testManifest.functions?.[0]?.uri);
@@ -54,8 +54,8 @@ describe('function registry', () => {
54
54
  const client = (await createInitializedClients(testBuilder))[0];
55
55
  const registry = createRegistry(client);
56
56
  const space = await client.spaces.create();
57
- const existing = space.db.add(create(FunctionDef, { ...testManifest.functions![0] }));
58
- await registry.register(space, testManifest);
57
+ const existing = space.db.add(create(FunctionDef, testManifest.functions![0]));
58
+ await registry.register(space, testManifest.functions);
59
59
  const { objects: definitions } = await space.db.query(Filter.schema(FunctionDef)).run();
60
60
  expect(definitions.length).to.eq(1);
61
61
  expect(definitions[0].uri).to.eq(existing.uri);
@@ -67,12 +67,12 @@ describe('function registry', () => {
67
67
  const client = (await createInitializedClients(testBuilder))[0];
68
68
  const registry = createRegistry(client);
69
69
  const space = await client.spaces.create();
70
- const definitions = range(3, () => create(FunctionDef, { ...testManifest.functions![0] }));
70
+ const definitions = range(3, () => create(FunctionDef, testManifest.functions![0]));
71
71
  definitions.forEach((def) => space.db.add(def));
72
72
 
73
73
  const functionRegistered = new Trigger<FunctionDef[]>();
74
- registry.onFunctionsRegistered.on((fn) => {
75
- functionRegistered.wake(fn.newFunctions);
74
+ registry.registered.on((fn) => {
75
+ functionRegistered.wake(fn.added);
76
76
  });
77
77
  void registry.open(ctx);
78
78
  const functions = await functionRegistered.wait();
@@ -86,12 +86,12 @@ describe('function registry', () => {
86
86
  const space = await client.spaces.create();
87
87
 
88
88
  const functionRegistered = new Trigger<FunctionDef>();
89
- registry.onFunctionsRegistered.on((fn) => {
90
- expect(fn.newFunctions.length).to.eq(1);
91
- functionRegistered.wake(fn.newFunctions[0]);
89
+ registry.registered.on((fn) => {
90
+ expect(fn.added.length).to.eq(1);
91
+ functionRegistered.wake(fn.added[0]);
92
92
  });
93
93
  await registry.open(ctx);
94
- await registry.register(space, testManifest);
94
+ await registry.register(space, testManifest.functions);
95
95
  const registered = await functionRegistered.wait();
96
96
  expect(registered.uri).to.eq(testManifest.functions![0].uri);
97
97
  });
@@ -0,0 +1,90 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Event } from '@dxos/async';
6
+ import { type Client } from '@dxos/client';
7
+ import { create, Filter, type Space } from '@dxos/client/echo';
8
+ import { type Context, Resource } from '@dxos/context';
9
+ import { PublicKey } from '@dxos/keys';
10
+ import { log } from '@dxos/log';
11
+ import { ComplexMap, diff } from '@dxos/util';
12
+
13
+ import { FunctionDef, type FunctionManifest } from '../types';
14
+
15
+ export type FunctionsRegisteredEvent = {
16
+ space: Space;
17
+ added: FunctionDef[];
18
+ };
19
+
20
+ export class FunctionRegistry extends Resource {
21
+ private readonly _functionBySpaceKey = new ComplexMap<PublicKey, FunctionDef[]>(PublicKey.hash);
22
+
23
+ public readonly registered = new Event<FunctionsRegisteredEvent>();
24
+
25
+ constructor(private readonly _client: Client) {
26
+ super();
27
+ }
28
+
29
+ public getFunctions(space: Space): FunctionDef[] {
30
+ return this._functionBySpaceKey.get(space.key) ?? [];
31
+ }
32
+
33
+ /**
34
+ * Loads function definitions from the manifest into the space.
35
+ * We first load all the definitions from the space to deduplicate by functionId.
36
+ */
37
+ public async register(space: Space, functions: FunctionManifest['functions']): Promise<void> {
38
+ log('register', { space: space.key, functions: functions?.length ?? 0 });
39
+ if (!functions?.length) {
40
+ return;
41
+ }
42
+ if (!space.db.graph.runtimeSchemaRegistry.hasSchema(FunctionDef)) {
43
+ space.db.graph.runtimeSchemaRegistry.registerSchema(FunctionDef);
44
+ }
45
+
46
+ // Sync definitions.
47
+ const { objects: existing } = await space.db.query(Filter.schema(FunctionDef)).run();
48
+ const { added } = diff(existing, functions, (a, b) => a.uri === b.uri);
49
+ // TODO(burdon): Update existing templates.
50
+ added.forEach((def) => space.db.add(create(FunctionDef, def)));
51
+ }
52
+
53
+ protected override async _open(): Promise<void> {
54
+ log.info('opening...');
55
+ const spacesSubscription = this._client.spaces.subscribe(async (spaces) => {
56
+ for (const space of spaces) {
57
+ if (this._functionBySpaceKey.has(space.key)) {
58
+ continue;
59
+ }
60
+
61
+ const registered: FunctionDef[] = [];
62
+ this._functionBySpaceKey.set(space.key, registered);
63
+ await space.waitUntilReady();
64
+ if (this._ctx.disposed) {
65
+ break;
66
+ }
67
+
68
+ // Subscribe to updates.
69
+ this._ctx.onDispose(
70
+ space.db.query(Filter.schema(FunctionDef)).subscribe(({ objects }) => {
71
+ const { added } = diff(registered, objects, (a, b) => a.uri === b.uri);
72
+ // TODO(burdon): Update and remove.
73
+ if (added.length > 0) {
74
+ registered.push(...added);
75
+ this.registered.emit({ space, added });
76
+ }
77
+ }),
78
+ );
79
+ }
80
+ });
81
+
82
+ // TODO(burdon): API: Normalize unsubscribe methods.
83
+ this._ctx.onDispose(() => spacesSubscription.unsubscribe());
84
+ }
85
+
86
+ protected override async _close(_: Context): Promise<void> {
87
+ log.info('closing...');
88
+ this._functionBySpaceKey.clear();
89
+ }
90
+ }
package/src/index.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './function';
5
6
  export * from './handler';
6
- export * from './registry';
7
7
  export * from './runtime';
8
8
  export * from './trigger';
9
9
  export * from './types';
@@ -11,7 +11,7 @@ import { TestBuilder } from '@dxos/client/testing';
11
11
  import { describe, test } from '@dxos/test';
12
12
 
13
13
  import { DevServer } from './dev-server';
14
- import { FunctionRegistry } from '../registry';
14
+ import { FunctionRegistry } from '../function';
15
15
  import { createFunctionRuntime } from '../testing';
16
16
  import { type FunctionManifest } from '../types';
17
17
 
@@ -44,7 +44,7 @@ describe('dev server', () => {
44
44
  baseDir: path.join(__dirname, '../testing'),
45
45
  });
46
46
  const space = await client.spaces.create();
47
- await registry.register(space, manifest);
47
+ await registry.register(space, manifest.functions);
48
48
  await server.start();
49
49
 
50
50
  // TODO(burdon): Doesn't shut down cleanly.
@@ -13,8 +13,8 @@ import { Context } from '@dxos/context';
13
13
  import { invariant } from '@dxos/invariant';
14
14
  import { log } from '@dxos/log';
15
15
 
16
+ import { type FunctionRegistry } from '../function';
16
17
  import { type FunctionContext, type FunctionEvent, type FunctionHandler, type FunctionResponse } from '../handler';
17
- import { type FunctionRegistry } from '../registry';
18
18
  import { type FunctionDef } from '../types';
19
19
 
20
20
  export type DevServerOptions = {
@@ -41,16 +41,15 @@ export class DevServer {
41
41
 
42
42
  public readonly update = new Event<number>();
43
43
 
44
- // prettier-ignore
45
44
  constructor(
46
45
  private readonly _client: Client,
47
46
  private readonly _functionsRegistry: FunctionRegistry,
48
47
  private readonly _options: DevServerOptions,
49
48
  ) {
50
- this._functionsRegistry.onFunctionsRegistered.on(async ({ newFunctions }) => {
51
- newFunctions.forEach((def) => this._load(def));
49
+ this._functionsRegistry.registered.on(async ({ added }) => {
50
+ added.forEach((def) => this._load(def));
52
51
  await this._safeUpdateRegistration();
53
- log('new functions loaded', { newFunctions });
52
+ log('new functions loaded', { added });
54
53
  });
55
54
  }
56
55
 
@@ -158,7 +157,7 @@ export class DevServer {
158
157
  /**
159
158
  * Load function.
160
159
  */
161
- private async _load(def: FunctionDef, force = false) {
160
+ private async _load(def: FunctionDef, force?: boolean | undefined) {
162
161
  const { uri, route, handler } = def;
163
162
  const filePath = join(this._options.baseDir, handler);
164
163
  log.info('loading', { uri, force });
@@ -12,7 +12,7 @@ import { create } from '@dxos/echo-schema';
12
12
  import { describe, test } from '@dxos/test';
13
13
 
14
14
  import { Scheduler, type SchedulerOptions } from './scheduler';
15
- import { FunctionRegistry } from '../registry';
15
+ import { FunctionRegistry } from '../function';
16
16
  import { createInitializedClients, TestType, triggerWebhook } from '../testing';
17
17
  import { TriggerRegistry } from '../trigger';
18
18
  import { type FunctionManifest } from '../types';
@@ -4,12 +4,13 @@
4
4
 
5
5
  import path from 'node:path';
6
6
 
7
+ import { Mutex } from '@dxos/async';
7
8
  import { type Space } from '@dxos/client/echo';
8
9
  import { Context } from '@dxos/context';
9
10
  import { log } from '@dxos/log';
10
11
 
12
+ import { type FunctionRegistry } from '../function';
11
13
  import { type FunctionEventMeta } from '../handler';
12
- import { type FunctionRegistry } from '../registry';
13
14
  import { type TriggerRegistry } from '../trigger';
14
15
  import { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../types';
15
16
 
@@ -26,13 +27,15 @@ export type SchedulerOptions = {
26
27
  export class Scheduler {
27
28
  private _ctx = createContext();
28
29
 
30
+ private readonly _functionUriToCallMutex = new Map<string, Mutex>();
31
+
29
32
  constructor(
30
33
  public readonly functions: FunctionRegistry,
31
34
  public readonly triggers: TriggerRegistry,
32
35
  private readonly _options: SchedulerOptions = {},
33
36
  ) {
34
- this.functions.onFunctionsRegistered.on(async ({ space, newFunctions }) => {
35
- await this._safeActivateTriggers(space, this.triggers.getInactiveTriggers(space), newFunctions);
37
+ this.functions.registered.on(async ({ space, added }) => {
38
+ await this._safeActivateTriggers(space, this.triggers.getInactiveTriggers(space), added);
36
39
  });
37
40
  this.triggers.registered.on(async ({ space, triggers }) => {
38
41
  await this._safeActivateTriggers(space, triggers, this.functions.getFunctions(space));
@@ -52,8 +55,9 @@ export class Scheduler {
52
55
  await this.triggers.close();
53
56
  }
54
57
 
58
+ // TODO(burdon): Remove and update registries directly.
55
59
  public async register(space: Space, manifest: FunctionManifest) {
56
- await this.functions.register(space, manifest);
60
+ await this.functions.register(space, manifest.functions);
57
61
  await this.triggers.register(space, manifest);
58
62
  }
59
63
 
@@ -76,16 +80,25 @@ export class Scheduler {
76
80
  }
77
81
 
78
82
  await this.triggers.activate({ space }, fnTrigger, async (args) => {
79
- return this._execFunction(definition, {
80
- meta: fnTrigger.meta,
81
- data: { ...args, spaceKey: space.key },
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, fnTrigger, {
90
+ meta: fnTrigger.meta,
91
+ data: { ...args, spaceKey: space.key },
92
+ });
82
93
  });
83
94
  });
95
+
84
96
  log('activated trigger', { space: space.key, trigger: fnTrigger });
85
97
  }
86
98
 
87
99
  private async _execFunction<TData, TMeta>(
88
100
  def: FunctionDef,
101
+ trigger: FunctionTrigger,
89
102
  { data, meta }: { data: TData; meta?: TMeta },
90
103
  ): Promise<number> {
91
104
  let status = 0;
@@ -97,7 +110,7 @@ export class Scheduler {
97
110
  if (endpoint) {
98
111
  // TODO(burdon): Move out of scheduler (generalize as callback).
99
112
  const url = path.join(endpoint, def.route);
100
- log.info('exec', { function: def.uri, url });
113
+ log.info('exec', { function: def.uri, url, triggerType: trigger.spec.type });
101
114
  const response = await fetch(url, {
102
115
  method: 'POST',
103
116
  headers: {