@dxos/functions 0.5.3-main.2ad0afc → 0.5.3-main.3456876
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.
- package/dist/lib/browser/index.mjs +490 -712
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +483 -695
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/handler.d.ts +12 -32
- package/dist/types/src/handler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.d.ts +10 -7
- package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
- package/dist/types/src/runtime/scheduler.d.ts +59 -11
- package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
- package/dist/types/src/testing/test/handler.d.ts +0 -1
- package/dist/types/src/testing/test/handler.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +111 -131
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +14 -30
- package/schema/functions.json +112 -140
- package/src/handler.ts +27 -50
- package/src/index.ts +0 -2
- package/src/runtime/dev-server.test.ts +35 -15
- package/src/runtime/dev-server.ts +24 -41
- package/src/runtime/scheduler.test.ts +75 -54
- package/src/runtime/scheduler.ts +300 -75
- package/src/testing/test/handler.ts +2 -8
- package/src/types.ts +40 -58
- package/dist/lib/browser/chunk-366QG6IX.mjs +0 -81
- package/dist/lib/browser/chunk-366QG6IX.mjs.map +0 -7
- package/dist/lib/browser/types.mjs +0 -12
- package/dist/lib/browser/types.mjs.map +0 -7
- package/dist/lib/node/chunk-3VSJ57ZZ.cjs +0 -97
- package/dist/lib/node/chunk-3VSJ57ZZ.cjs.map +0 -7
- package/dist/lib/node/types.cjs +0 -33
- package/dist/lib/node/types.cjs.map +0 -7
- package/dist/types/src/function/function-registry.d.ts +0 -24
- package/dist/types/src/function/function-registry.d.ts.map +0 -1
- package/dist/types/src/function/function-registry.test.d.ts +0 -2
- package/dist/types/src/function/function-registry.test.d.ts.map +0 -1
- package/dist/types/src/function/index.d.ts +0 -2
- package/dist/types/src/function/index.d.ts.map +0 -1
- package/dist/types/src/testing/functions-integration.test.d.ts +0 -2
- package/dist/types/src/testing/functions-integration.test.d.ts.map +0 -1
- package/dist/types/src/testing/index.d.ts +0 -4
- package/dist/types/src/testing/index.d.ts.map +0 -1
- package/dist/types/src/testing/setup.d.ts +0 -5
- package/dist/types/src/testing/setup.d.ts.map +0 -1
- package/dist/types/src/testing/types.d.ts +0 -9
- package/dist/types/src/testing/types.d.ts.map +0 -1
- package/dist/types/src/testing/util.d.ts +0 -3
- package/dist/types/src/testing/util.d.ts.map +0 -1
- package/dist/types/src/trigger/index.d.ts +0 -2
- package/dist/types/src/trigger/index.d.ts.map +0 -1
- package/dist/types/src/trigger/trigger-registry.d.ts +0 -40
- package/dist/types/src/trigger/trigger-registry.d.ts.map +0 -1
- package/dist/types/src/trigger/trigger-registry.test.d.ts +0 -2
- package/dist/types/src/trigger/trigger-registry.test.d.ts.map +0 -1
- package/dist/types/src/trigger/type/index.d.ts +0 -5
- package/dist/types/src/trigger/type/index.d.ts.map +0 -1
- package/dist/types/src/trigger/type/subscription-trigger.d.ts +0 -4
- package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +0 -1
- package/dist/types/src/trigger/type/timer-trigger.d.ts +0 -4
- package/dist/types/src/trigger/type/timer-trigger.d.ts.map +0 -1
- package/dist/types/src/trigger/type/webhook-trigger.d.ts +0 -4
- package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +0 -1
- package/dist/types/src/trigger/type/websocket-trigger.d.ts +0 -13
- package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +0 -1
- package/dist/types/src/util.d.ts +0 -15
- package/dist/types/src/util.d.ts.map +0 -1
- package/dist/types/src/util.test.d.ts +0 -2
- package/dist/types/src/util.test.d.ts.map +0 -1
- package/src/function/function-registry.test.ts +0 -105
- package/src/function/function-registry.ts +0 -90
- package/src/function/index.ts +0 -5
- package/src/testing/functions-integration.test.ts +0 -99
- package/src/testing/index.ts +0 -7
- package/src/testing/setup.ts +0 -45
- package/src/testing/types.ts +0 -9
- package/src/testing/util.ts +0 -16
- package/src/trigger/index.ts +0 -5
- package/src/trigger/trigger-registry.test.ts +0 -255
- package/src/trigger/trigger-registry.ts +0 -189
- package/src/trigger/type/index.ts +0 -8
- package/src/trigger/type/subscription-trigger.ts +0 -80
- package/src/trigger/type/timer-trigger.ts +0 -44
- package/src/trigger/type/webhook-trigger.ts +0 -47
- package/src/trigger/type/websocket-trigger.ts +0 -91
- package/src/util.test.ts +0 -43
- package/src/util.ts +0 -48
package/schema/functions.json
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"type": "object",
|
|
4
|
-
"required": [
|
|
4
|
+
"required": [
|
|
5
|
+
"functions"
|
|
6
|
+
],
|
|
5
7
|
"properties": {
|
|
6
8
|
"functions": {
|
|
7
9
|
"type": "array",
|
|
8
10
|
"items": {
|
|
9
11
|
"type": "object",
|
|
10
12
|
"required": [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
+
"id",
|
|
14
|
+
"path",
|
|
13
15
|
"handler"
|
|
14
16
|
],
|
|
15
17
|
"properties": {
|
|
16
|
-
"
|
|
18
|
+
"id": {
|
|
17
19
|
"type": "string",
|
|
18
20
|
"description": "a string",
|
|
19
21
|
"title": "string"
|
|
20
22
|
},
|
|
21
|
-
"
|
|
23
|
+
"path": {
|
|
22
24
|
"type": "string",
|
|
23
25
|
"description": "a string",
|
|
24
26
|
"title": "string"
|
|
@@ -42,165 +44,135 @@
|
|
|
42
44
|
"items": {
|
|
43
45
|
"type": "object",
|
|
44
46
|
"required": [
|
|
45
|
-
"function"
|
|
46
|
-
"spec"
|
|
47
|
+
"function"
|
|
47
48
|
],
|
|
48
49
|
"properties": {
|
|
49
50
|
"function": {
|
|
50
51
|
"type": "string",
|
|
51
|
-
"description": "Function URI.",
|
|
52
|
+
"description": "Function ID/URI.",
|
|
52
53
|
"title": "string"
|
|
53
54
|
},
|
|
54
|
-
"
|
|
55
|
-
"
|
|
55
|
+
"context": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"required": [],
|
|
58
|
+
"properties": {},
|
|
59
|
+
"additionalProperties": {
|
|
60
|
+
"$id": "/schemas/any",
|
|
61
|
+
"title": "any"
|
|
62
|
+
}
|
|
56
63
|
},
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
64
|
+
"timer": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"required": [
|
|
67
|
+
"cron"
|
|
68
|
+
],
|
|
69
|
+
"properties": {
|
|
70
|
+
"cron": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"description": "a string",
|
|
73
|
+
"title": "string"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
},
|
|
78
|
+
"webhook": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"required": [
|
|
81
|
+
"port"
|
|
82
|
+
],
|
|
83
|
+
"properties": {
|
|
84
|
+
"port": {
|
|
85
|
+
"type": "number",
|
|
86
|
+
"description": "a number",
|
|
87
|
+
"title": "number"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"additionalProperties": false
|
|
91
|
+
},
|
|
92
|
+
"websocket": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"required": [
|
|
95
|
+
"url"
|
|
96
|
+
],
|
|
97
|
+
"properties": {
|
|
98
|
+
"url": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "a string",
|
|
101
|
+
"title": "string"
|
|
102
|
+
},
|
|
103
|
+
"init": {
|
|
60
104
|
"type": "object",
|
|
61
|
-
"required": [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
105
|
+
"required": [],
|
|
106
|
+
"properties": {},
|
|
107
|
+
"additionalProperties": {
|
|
108
|
+
"$id": "/schemas/any",
|
|
109
|
+
"title": "any"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"additionalProperties": false
|
|
114
|
+
},
|
|
115
|
+
"subscription": {
|
|
116
|
+
"type": "object",
|
|
117
|
+
"required": [
|
|
118
|
+
"filter"
|
|
119
|
+
],
|
|
120
|
+
"properties": {
|
|
121
|
+
"spaceKey": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"description": "a string",
|
|
124
|
+
"title": "string"
|
|
125
|
+
},
|
|
126
|
+
"filter": {
|
|
127
|
+
"type": "array",
|
|
128
|
+
"items": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"required": [
|
|
131
|
+
"type"
|
|
132
|
+
],
|
|
133
|
+
"properties": {
|
|
134
|
+
"type": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"description": "a string",
|
|
137
|
+
"title": "string"
|
|
138
|
+
},
|
|
139
|
+
"props": {
|
|
140
|
+
"type": "object",
|
|
141
|
+
"required": [],
|
|
142
|
+
"properties": {},
|
|
143
|
+
"additionalProperties": {
|
|
144
|
+
"$id": "/schemas/any",
|
|
145
|
+
"title": "any"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
68
148
|
},
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
"description": "a string",
|
|
72
|
-
"title": "string"
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
"additionalProperties": false
|
|
149
|
+
"additionalProperties": false
|
|
150
|
+
}
|
|
76
151
|
},
|
|
77
|
-
{
|
|
152
|
+
"options": {
|
|
78
153
|
"type": "object",
|
|
79
|
-
"required": [
|
|
80
|
-
"type",
|
|
81
|
-
"method"
|
|
82
|
-
],
|
|
154
|
+
"required": [],
|
|
83
155
|
"properties": {
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"type": "string",
|
|
89
|
-
"description": "a string",
|
|
90
|
-
"title": "string"
|
|
156
|
+
"deep": {
|
|
157
|
+
"type": "boolean",
|
|
158
|
+
"description": "a boolean",
|
|
159
|
+
"title": "boolean"
|
|
91
160
|
},
|
|
92
|
-
"
|
|
161
|
+
"delay": {
|
|
93
162
|
"type": "number",
|
|
94
163
|
"description": "a number",
|
|
95
164
|
"title": "number"
|
|
96
165
|
}
|
|
97
166
|
},
|
|
98
167
|
"additionalProperties": false
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
"type": "object",
|
|
102
|
-
"required": [
|
|
103
|
-
"type",
|
|
104
|
-
"url"
|
|
105
|
-
],
|
|
106
|
-
"properties": {
|
|
107
|
-
"type": {
|
|
108
|
-
"const": "websocket"
|
|
109
|
-
},
|
|
110
|
-
"url": {
|
|
111
|
-
"type": "string",
|
|
112
|
-
"description": "a string",
|
|
113
|
-
"title": "string"
|
|
114
|
-
},
|
|
115
|
-
"init": {
|
|
116
|
-
"type": "object",
|
|
117
|
-
"required": [],
|
|
118
|
-
"properties": {},
|
|
119
|
-
"additionalProperties": {
|
|
120
|
-
"$id": "/schemas/any",
|
|
121
|
-
"title": "any"
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
"additionalProperties": false
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
"type": "object",
|
|
129
|
-
"required": [
|
|
130
|
-
"type",
|
|
131
|
-
"filter"
|
|
132
|
-
],
|
|
133
|
-
"properties": {
|
|
134
|
-
"type": {
|
|
135
|
-
"const": "subscription"
|
|
136
|
-
},
|
|
137
|
-
"filter": {
|
|
138
|
-
"type": "array",
|
|
139
|
-
"items": {
|
|
140
|
-
"type": "object",
|
|
141
|
-
"required": [
|
|
142
|
-
"type"
|
|
143
|
-
],
|
|
144
|
-
"properties": {
|
|
145
|
-
"type": {
|
|
146
|
-
"type": "string",
|
|
147
|
-
"description": "a string",
|
|
148
|
-
"title": "string"
|
|
149
|
-
},
|
|
150
|
-
"props": {
|
|
151
|
-
"type": "object",
|
|
152
|
-
"required": [],
|
|
153
|
-
"properties": {},
|
|
154
|
-
"additionalProperties": {
|
|
155
|
-
"$id": "/schemas/any",
|
|
156
|
-
"title": "any"
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
"additionalProperties": false
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
"options": {
|
|
164
|
-
"type": "object",
|
|
165
|
-
"required": [],
|
|
166
|
-
"properties": {
|
|
167
|
-
"deep": {
|
|
168
|
-
"type": "boolean",
|
|
169
|
-
"description": "a boolean",
|
|
170
|
-
"title": "boolean"
|
|
171
|
-
},
|
|
172
|
-
"delay": {
|
|
173
|
-
"type": "number",
|
|
174
|
-
"description": "a number",
|
|
175
|
-
"title": "number"
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
"additionalProperties": false
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
"additionalProperties": false
|
|
182
168
|
}
|
|
183
|
-
|
|
169
|
+
},
|
|
170
|
+
"additionalProperties": false
|
|
184
171
|
}
|
|
185
172
|
},
|
|
186
173
|
"additionalProperties": false
|
|
187
174
|
}
|
|
188
175
|
}
|
|
189
176
|
},
|
|
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
|
-
}
|
|
177
|
+
"additionalProperties": false
|
|
206
178
|
}
|
package/src/handler.ts
CHANGED
|
@@ -8,61 +8,38 @@ import { type EchoReactiveObject } from '@dxos/echo-schema';
|
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
9
|
import { nonNullable } from '@dxos/util';
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
// https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
|
|
11
|
+
// Lambda-like function definitions.
|
|
13
12
|
// https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/#functions
|
|
14
13
|
// https://www.npmjs.com/package/aws-lambda
|
|
14
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
context: FunctionContext;
|
|
21
|
-
event: FunctionEvent<TData, TMeta>;
|
|
22
|
-
response: FunctionResponse;
|
|
23
|
-
}) => Promise<FunctionResponse | void>;
|
|
16
|
+
// TODO(burdon): No response?
|
|
17
|
+
export interface Response {
|
|
18
|
+
status(code: number): Response;
|
|
19
|
+
}
|
|
24
20
|
|
|
25
|
-
/**
|
|
26
|
-
* Function context.
|
|
27
|
-
*/
|
|
28
21
|
export interface FunctionContext {
|
|
29
22
|
// TODO(burdon): Limit access to individual space.
|
|
30
23
|
client: Client;
|
|
31
24
|
// TODO(burdon): Replace with storage service abstraction.
|
|
32
25
|
dataDir?: string;
|
|
26
|
+
// Data passed to function invocation.
|
|
27
|
+
data?: any;
|
|
33
28
|
}
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Metadata from trigger.
|
|
44
|
-
*/
|
|
45
|
-
export type FunctionEventMeta<TMeta = {}> = {
|
|
46
|
-
meta: TMeta;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Function response.
|
|
51
|
-
*/
|
|
52
|
-
export interface FunctionResponse {
|
|
53
|
-
status(code: number): FunctionResponse;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
// Subscription utils.
|
|
58
|
-
//
|
|
30
|
+
// TODO(burdon): Model after http request. Ref Lambda/OpenFaaS.
|
|
31
|
+
export type FunctionHandler<T extends {}> = (params: {
|
|
32
|
+
event: T;
|
|
33
|
+
context: FunctionContext;
|
|
34
|
+
response: Response;
|
|
35
|
+
}) => Promise<Response | void>;
|
|
59
36
|
|
|
60
|
-
export type
|
|
37
|
+
export type RawSubscriptionEvent = {
|
|
61
38
|
spaceKey?: string;
|
|
62
39
|
objects?: string[];
|
|
63
40
|
};
|
|
64
41
|
|
|
65
|
-
export type
|
|
42
|
+
export type SubscriptionEvent = {
|
|
66
43
|
space?: Space;
|
|
67
44
|
objects?: EchoReactiveObject<any>[];
|
|
68
45
|
};
|
|
@@ -77,22 +54,22 @@ export type SubscriptionData = {
|
|
|
77
54
|
*
|
|
78
55
|
* NOTE: Get space key from devtools or `dx space list --json`
|
|
79
56
|
*/
|
|
80
|
-
export const subscriptionHandler =
|
|
81
|
-
handler: FunctionHandler<
|
|
82
|
-
): FunctionHandler<
|
|
83
|
-
return ({ event
|
|
57
|
+
export const subscriptionHandler = (
|
|
58
|
+
handler: FunctionHandler<SubscriptionEvent>,
|
|
59
|
+
): FunctionHandler<RawSubscriptionEvent> => {
|
|
60
|
+
return ({ event, context, ...rest }) => {
|
|
84
61
|
const { client } = context;
|
|
85
|
-
const space =
|
|
86
|
-
const objects =
|
|
87
|
-
|
|
88
|
-
|
|
62
|
+
const space = event.spaceKey ? client.spaces.get(PublicKey.from(event.spaceKey)) : undefined;
|
|
63
|
+
const objects =
|
|
64
|
+
space &&
|
|
65
|
+
event.objects?.map<EchoReactiveObject<any> | undefined>((id) => space!.db.getObjectById(id)).filter(nonNullable);
|
|
89
66
|
|
|
90
|
-
if (!!
|
|
91
|
-
log.warn('invalid space', {
|
|
67
|
+
if (!!event.spaceKey && !space) {
|
|
68
|
+
log.warn('invalid space', { event });
|
|
92
69
|
} else {
|
|
93
70
|
log.info('handler', { space: space?.key.truncate(), objects: objects?.length });
|
|
94
71
|
}
|
|
95
72
|
|
|
96
|
-
return handler({ event: {
|
|
73
|
+
return handler({ event: { space, objects }, context, ...rest });
|
|
97
74
|
};
|
|
98
75
|
};
|
package/src/index.ts
CHANGED
|
@@ -5,14 +5,12 @@
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { FunctionsPlugin } from '@dxos/agent';
|
|
9
|
+
import { Client, Config } from '@dxos/client';
|
|
10
10
|
import { TestBuilder } from '@dxos/client/testing';
|
|
11
|
-
import { describe, test } from '@dxos/test';
|
|
11
|
+
import { describe, openAndClose, test } from '@dxos/test';
|
|
12
12
|
|
|
13
13
|
import { DevServer } from './dev-server';
|
|
14
|
-
import { FunctionRegistry } from '../function';
|
|
15
|
-
import { createFunctionRuntime } from '../testing';
|
|
16
14
|
import { type FunctionManifest } from '../types';
|
|
17
15
|
|
|
18
16
|
describe('dev server', () => {
|
|
@@ -20,10 +18,35 @@ describe('dev server', () => {
|
|
|
20
18
|
let testBuilder: TestBuilder;
|
|
21
19
|
before(async () => {
|
|
22
20
|
testBuilder = new TestBuilder();
|
|
23
|
-
|
|
21
|
+
const config = new Config({
|
|
22
|
+
runtime: {
|
|
23
|
+
agent: {
|
|
24
|
+
plugins: [
|
|
25
|
+
{
|
|
26
|
+
id: 'dxos.org/agent/plugin/functions',
|
|
27
|
+
config: {
|
|
28
|
+
port: 8080,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const services = testBuilder.createLocalClientServices();
|
|
37
|
+
client = new Client({ config, services });
|
|
38
|
+
|
|
39
|
+
await client.initialize();
|
|
40
|
+
await client.halo.createIdentity();
|
|
41
|
+
testBuilder.ctx.onDispose(() => client.destroy());
|
|
42
|
+
|
|
43
|
+
// TODO(burdon): Better way to configure plugin? (Rationalize chess.test).
|
|
44
|
+
const functionsPlugin = new FunctionsPlugin();
|
|
45
|
+
await functionsPlugin.initialize({ client, clientServices: services });
|
|
46
|
+
await openAndClose(functionsPlugin);
|
|
47
|
+
|
|
24
48
|
expect(client.services.services.FunctionRegistryService).to.exist;
|
|
25
49
|
});
|
|
26
|
-
|
|
27
50
|
after(async () => {
|
|
28
51
|
await testBuilder.destroy();
|
|
29
52
|
});
|
|
@@ -32,19 +55,18 @@ describe('dev server', () => {
|
|
|
32
55
|
const manifest: FunctionManifest = {
|
|
33
56
|
functions: [
|
|
34
57
|
{
|
|
35
|
-
|
|
36
|
-
|
|
58
|
+
id: 'example.com/function/test',
|
|
59
|
+
path: 'test',
|
|
37
60
|
handler: 'test',
|
|
38
61
|
},
|
|
39
62
|
],
|
|
40
63
|
};
|
|
41
64
|
|
|
42
|
-
const
|
|
43
|
-
|
|
65
|
+
const server = new DevServer(client, {
|
|
66
|
+
manifest,
|
|
44
67
|
baseDir: path.join(__dirname, '../testing'),
|
|
45
68
|
});
|
|
46
|
-
|
|
47
|
-
await registry.register(space, manifest.functions);
|
|
69
|
+
await server.initialize();
|
|
48
70
|
await server.start();
|
|
49
71
|
|
|
50
72
|
// TODO(burdon): Doesn't shut down cleanly.
|
|
@@ -52,8 +74,6 @@ describe('dev server', () => {
|
|
|
52
74
|
testBuilder.ctx.onDispose(() => server.stop());
|
|
53
75
|
expect(server).to.exist;
|
|
54
76
|
|
|
55
|
-
await waitForCondition({ condition: () => server.functions.length > 0 });
|
|
56
|
-
|
|
57
77
|
await server.invoke('test', {});
|
|
58
78
|
expect(server.stats.seq).to.eq(1);
|
|
59
79
|
});
|
|
@@ -9,15 +9,14 @@ import { join } from 'node:path';
|
|
|
9
9
|
|
|
10
10
|
import { Event, Trigger } from '@dxos/async';
|
|
11
11
|
import { type Client } from '@dxos/client';
|
|
12
|
-
import { Context } from '@dxos/context';
|
|
13
12
|
import { invariant } from '@dxos/invariant';
|
|
14
13
|
import { log } from '@dxos/log';
|
|
15
14
|
|
|
16
|
-
import { type
|
|
17
|
-
import { type
|
|
18
|
-
import { type FunctionDef } from '../types';
|
|
15
|
+
import { type FunctionContext, type FunctionHandler, type Response } from '../handler';
|
|
16
|
+
import { type FunctionDef, type FunctionManifest } from '../types';
|
|
19
17
|
|
|
20
18
|
export type DevServerOptions = {
|
|
19
|
+
manifest: FunctionManifest;
|
|
21
20
|
baseDir: string;
|
|
22
21
|
port?: number;
|
|
23
22
|
reload?: boolean;
|
|
@@ -28,8 +27,6 @@ export type DevServerOptions = {
|
|
|
28
27
|
* Functions dev server provides a local HTTP server for testing functions.
|
|
29
28
|
*/
|
|
30
29
|
export class DevServer {
|
|
31
|
-
private _ctx = createContext();
|
|
32
|
-
|
|
33
30
|
// Function handlers indexed by name (URL path).
|
|
34
31
|
private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};
|
|
35
32
|
|
|
@@ -41,17 +38,11 @@ export class DevServer {
|
|
|
41
38
|
|
|
42
39
|
public readonly update = new Event<number>();
|
|
43
40
|
|
|
41
|
+
// prettier-ignore
|
|
44
42
|
constructor(
|
|
45
43
|
private readonly _client: Client,
|
|
46
|
-
private readonly _functionsRegistry: FunctionRegistry,
|
|
47
44
|
private readonly _options: DevServerOptions,
|
|
48
|
-
) {
|
|
49
|
-
this._functionsRegistry.registered.on(async ({ added }) => {
|
|
50
|
-
added.forEach((def) => this._load(def));
|
|
51
|
-
await this._safeUpdateRegistration();
|
|
52
|
-
log('new functions loaded', { added });
|
|
53
|
-
});
|
|
54
|
-
}
|
|
45
|
+
) {}
|
|
55
46
|
|
|
56
47
|
get stats() {
|
|
57
48
|
return {
|
|
@@ -72,10 +63,19 @@ export class DevServer {
|
|
|
72
63
|
return Object.values(this._handlers);
|
|
73
64
|
}
|
|
74
65
|
|
|
66
|
+
async initialize() {
|
|
67
|
+
for (const def of this._options.manifest.functions) {
|
|
68
|
+
try {
|
|
69
|
+
await this._load(def);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
log.error('parsing function (check manifest)', err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
75
76
|
async start() {
|
|
76
77
|
invariant(!this._server);
|
|
77
78
|
log.info('starting...');
|
|
78
|
-
this._ctx = createContext();
|
|
79
79
|
|
|
80
80
|
// TODO(burdon): Move to hono.
|
|
81
81
|
const app = express();
|
|
@@ -107,14 +107,12 @@ export class DevServer {
|
|
|
107
107
|
// Register functions.
|
|
108
108
|
const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({
|
|
109
109
|
endpoint: this.endpoint,
|
|
110
|
+
functions: this.functions.map(({ def: { id, path } }) => ({ id, path })),
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
log.info('registered', { endpoint });
|
|
113
114
|
this._proxy = endpoint;
|
|
114
115
|
this._functionServiceRegistration = registrationId;
|
|
115
|
-
|
|
116
|
-
// Open after registration, so that it can be updated with the list of function definitions.
|
|
117
|
-
await this._functionsRegistry.open(this._ctx);
|
|
118
116
|
} catch (err: any) {
|
|
119
117
|
await this.stop();
|
|
120
118
|
throw new Error('FunctionRegistryService not available (check plugin is configured).');
|
|
@@ -157,10 +155,10 @@ export class DevServer {
|
|
|
157
155
|
/**
|
|
158
156
|
* Load function.
|
|
159
157
|
*/
|
|
160
|
-
private async _load(def: FunctionDef, force
|
|
161
|
-
const {
|
|
158
|
+
private async _load(def: FunctionDef, force = false) {
|
|
159
|
+
const { id, path, handler } = def;
|
|
162
160
|
const filePath = join(this._options.baseDir, handler);
|
|
163
|
-
log.info('loading', {
|
|
161
|
+
log.info('loading', { id, force });
|
|
164
162
|
|
|
165
163
|
// Remove from cache.
|
|
166
164
|
if (force) {
|
|
@@ -171,26 +169,13 @@ export class DevServer {
|
|
|
171
169
|
});
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
// TODO(burdon): Import types.
|
|
175
172
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
176
173
|
const module = require(filePath);
|
|
177
174
|
if (typeof module.default !== 'function') {
|
|
178
|
-
throw new Error(`Handler must export default function: ${
|
|
175
|
+
throw new Error(`Handler must export default function: ${id}`);
|
|
179
176
|
}
|
|
180
177
|
|
|
181
|
-
this._handlers[
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
private async _safeUpdateRegistration(): Promise<void> {
|
|
185
|
-
invariant(this._functionServiceRegistration);
|
|
186
|
-
try {
|
|
187
|
-
await this._client.services.services.FunctionRegistryService!.updateRegistration({
|
|
188
|
-
registrationId: this._functionServiceRegistration,
|
|
189
|
-
functions: this.functions.map(({ def: { id, route } }) => ({ id, route })),
|
|
190
|
-
});
|
|
191
|
-
} catch (e) {
|
|
192
|
-
log.catch(e);
|
|
193
|
-
}
|
|
178
|
+
this._handlers[path] = { def, handler: module.default };
|
|
194
179
|
}
|
|
195
180
|
|
|
196
181
|
/**
|
|
@@ -201,14 +186,14 @@ export class DevServer {
|
|
|
201
186
|
const now = Date.now();
|
|
202
187
|
|
|
203
188
|
log.info('req', { seq, path });
|
|
204
|
-
const statusCode = await this._invoke(path,
|
|
189
|
+
const statusCode = await this._invoke(path, data);
|
|
205
190
|
|
|
206
191
|
log.info('res', { seq, path, statusCode, duration: Date.now() - now });
|
|
207
192
|
this.update.emit(statusCode);
|
|
208
193
|
return statusCode;
|
|
209
194
|
}
|
|
210
195
|
|
|
211
|
-
private async _invoke(path: string, event:
|
|
196
|
+
private async _invoke(path: string, event: any) {
|
|
212
197
|
const { handler } = this._handlers[path] ?? {};
|
|
213
198
|
invariant(handler, `invalid path: ${path}`);
|
|
214
199
|
|
|
@@ -218,7 +203,7 @@ export class DevServer {
|
|
|
218
203
|
};
|
|
219
204
|
|
|
220
205
|
let statusCode = 200;
|
|
221
|
-
const response:
|
|
206
|
+
const response: Response = {
|
|
222
207
|
status: (code: number) => {
|
|
223
208
|
statusCode = code;
|
|
224
209
|
return response;
|
|
@@ -229,5 +214,3 @@ export class DevServer {
|
|
|
229
214
|
return statusCode;
|
|
230
215
|
}
|
|
231
216
|
}
|
|
232
|
-
|
|
233
|
-
const createContext = () => new Context({ name: 'DevServer' });
|