@dxos/edge-client 0.8.3 → 0.8.4-main.1068cf700f
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/chunk-VHS3XEIX.mjs → neutral/chunk-VESGVCLQ.mjs} +15 -11
- package/dist/lib/{browser/chunk-VHS3XEIX.mjs.map → neutral/chunk-VESGVCLQ.mjs.map} +3 -3
- package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs +1 -1
- package/dist/lib/{browser → neutral}/index.mjs +694 -354
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/{browser → neutral}/testing/index.mjs +61 -16
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/edge-client.d.ts +15 -15
- package/dist/types/src/edge-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.d.ts +59 -31
- package/dist/types/src/edge-http-client.d.ts.map +1 -1
- package/dist/types/src/edge-http-client.test.d.ts +2 -0
- package/dist/types/src/edge-http-client.test.d.ts.map +1 -0
- package/dist/types/src/edge-ws-connection.d.ts +20 -0
- package/dist/types/src/edge-ws-connection.d.ts.map +1 -1
- package/dist/types/src/edge-ws-muxer.d.ts.map +1 -1
- package/dist/types/src/http-client.d.ts +25 -0
- package/dist/types/src/http-client.d.ts.map +1 -0
- package/dist/types/src/http-client.test.d.ts +2 -0
- package/dist/types/src/http-client.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/test-server.d.ts +9 -0
- package/dist/types/src/testing/test-server.d.ts.map +1 -0
- package/dist/types/src/testing/test-utils.d.ts +3 -3
- package/dist/types/src/testing/test-utils.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +30 -21
- package/src/edge-client.test.ts +4 -4
- package/src/edge-client.ts +73 -42
- package/src/edge-http-client.test.ts +22 -0
- package/src/edge-http-client.ts +368 -152
- package/src/edge-ws-connection.ts +129 -8
- package/src/edge-ws-muxer.ts +1 -1
- package/src/http-client.test.ts +58 -0
- package/src/http-client.ts +77 -0
- package/src/index.ts +4 -3
- package/src/testing/index.ts +1 -0
- package/src/testing/test-server.ts +45 -0
- package/src/testing/test-utils.ts +9 -9
- package/src/websocket.test.ts +1 -1
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node/chunk-XNHBUTNB.cjs +0 -317
- package/dist/lib/node/chunk-XNHBUTNB.cjs.map +0 -7
- package/dist/lib/node/edge-ws-muxer.cjs +0 -33
- package/dist/lib/node/edge-ws-muxer.cjs.map +0 -7
- package/dist/lib/node/index.cjs +0 -1060
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
- package/dist/lib/node/testing/index.cjs +0 -169
- package/dist/lib/node/testing/index.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-HGQUUFIJ.mjs +0 -299
- package/dist/lib/node-esm/chunk-HGQUUFIJ.mjs.map +0 -7
- package/dist/lib/node-esm/edge-ws-muxer.mjs +0 -12
- package/dist/lib/node-esm/edge-ws-muxer.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -1035
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -141
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- /package/dist/lib/{browser → neutral}/edge-ws-muxer.mjs.map +0 -0
package/src/edge-http-client.ts
CHANGED
|
@@ -2,41 +2,94 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
|
|
6
|
+
import * as HttpClient from '@effect/platform/HttpClient';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Function from 'effect/Function';
|
|
9
|
+
|
|
5
10
|
import { sleep } from '@dxos/async';
|
|
6
11
|
import { Context } from '@dxos/context';
|
|
12
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
13
|
+
import { invariant } from '@dxos/invariant';
|
|
7
14
|
import { type PublicKey, type SpaceId } from '@dxos/keys';
|
|
8
15
|
import { log } from '@dxos/log';
|
|
9
16
|
import {
|
|
17
|
+
type CreateAgentRequestBody,
|
|
18
|
+
type CreateAgentResponseBody,
|
|
19
|
+
type CreateSpaceRequest,
|
|
20
|
+
type CreateSpaceResponseBody,
|
|
21
|
+
EdgeAuthChallengeError,
|
|
10
22
|
EdgeCallFailedError,
|
|
11
|
-
type
|
|
23
|
+
type EdgeFailure,
|
|
24
|
+
type EdgeStatus,
|
|
25
|
+
type ExecuteWorkflowResponseBody,
|
|
26
|
+
type ExportBundleRequest,
|
|
27
|
+
type ExportBundleResponse,
|
|
28
|
+
type FeedProtocol,
|
|
29
|
+
type GetAgentStatusResponseBody,
|
|
12
30
|
type GetNotarizationResponseBody,
|
|
13
|
-
type
|
|
31
|
+
type ImportBundleRequest,
|
|
32
|
+
type InitiateOAuthFlowRequest,
|
|
33
|
+
type InitiateOAuthFlowResponse,
|
|
14
34
|
type JoinSpaceRequest,
|
|
15
35
|
type JoinSpaceResponseBody,
|
|
16
|
-
|
|
17
|
-
type
|
|
18
|
-
type CreateAgentRequestBody,
|
|
19
|
-
type GetAgentStatusResponseBody,
|
|
36
|
+
type ObjectId,
|
|
37
|
+
type PostNotarizationRequestBody,
|
|
20
38
|
type RecoverIdentityRequest,
|
|
21
39
|
type RecoverIdentityResponseBody,
|
|
22
40
|
type UploadFunctionRequest,
|
|
23
41
|
type UploadFunctionResponseBody,
|
|
24
|
-
type ObjectId,
|
|
25
|
-
type ExecuteWorkflowResponseBody,
|
|
26
|
-
type QueueQuery,
|
|
27
|
-
type QueryResult,
|
|
28
|
-
type InitiateOAuthFlowRequest,
|
|
29
|
-
type InitiateOAuthFlowResponse,
|
|
30
|
-
type CreateSpaceRequest,
|
|
31
|
-
type CreateSpaceResponseBody,
|
|
32
42
|
} from '@dxos/protocols';
|
|
43
|
+
import { createUrl } from '@dxos/util';
|
|
33
44
|
|
|
34
45
|
import { type EdgeIdentity, handleAuthChallenge } from './edge-identity';
|
|
46
|
+
import { HttpConfig, encodeAuthHeader, withLogging, withRetryConfig } from './http-client';
|
|
35
47
|
import { getEdgeUrlWithProtocol } from './utils';
|
|
36
48
|
|
|
37
49
|
const DEFAULT_RETRY_TIMEOUT = 1500;
|
|
38
50
|
const DEFAULT_RETRY_JITTER = 500;
|
|
39
51
|
const DEFAULT_MAX_RETRIES_COUNT = 3;
|
|
52
|
+
const WARNING_BODY_SIZE = 10 * 1024 * 1024; // 10MB
|
|
53
|
+
|
|
54
|
+
export type RetryConfig = {
|
|
55
|
+
/**
|
|
56
|
+
* A number of call retries, not counting the initial request.
|
|
57
|
+
*/
|
|
58
|
+
count: number;
|
|
59
|
+
/**
|
|
60
|
+
* Delay before retries in ms.
|
|
61
|
+
*/
|
|
62
|
+
timeout?: number;
|
|
63
|
+
/**
|
|
64
|
+
* A random amount of time before retrying to help prevent large bursts of requests.
|
|
65
|
+
*/
|
|
66
|
+
jitter?: number;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type EdgeHttpRequestArgs = {
|
|
70
|
+
method: string;
|
|
71
|
+
context?: Context;
|
|
72
|
+
retry?: RetryConfig;
|
|
73
|
+
body?: any;
|
|
74
|
+
/**
|
|
75
|
+
* @default true
|
|
76
|
+
*/
|
|
77
|
+
json?: boolean;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Force authentication.
|
|
81
|
+
* This should be used for requests with large bodies to avoid sending the body twice.
|
|
82
|
+
* The client will call /auth endpoint to generate the auth header.
|
|
83
|
+
*/
|
|
84
|
+
auth?: boolean;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type EdgeHttpGetArgs = Pick<EdgeHttpRequestArgs, 'context' | 'retry' | 'auth'>;
|
|
88
|
+
export type EdgeHttpPostArgs = Pick<EdgeHttpRequestArgs, 'context' | 'retry' | 'body' | 'auth'>;
|
|
89
|
+
|
|
90
|
+
export type GetCronTriggersResponse = {
|
|
91
|
+
cronIds: string[];
|
|
92
|
+
};
|
|
40
93
|
|
|
41
94
|
export class EdgeHttpClient {
|
|
42
95
|
private readonly _baseUrl: string;
|
|
@@ -64,19 +117,38 @@ export class EdgeHttpClient {
|
|
|
64
117
|
}
|
|
65
118
|
}
|
|
66
119
|
|
|
120
|
+
//
|
|
121
|
+
// Status
|
|
122
|
+
//
|
|
123
|
+
|
|
124
|
+
public async getStatus(args?: EdgeHttpGetArgs): Promise<EdgeStatus> {
|
|
125
|
+
return this._call(new URL('/status', this.baseUrl), { ...args, method: 'GET', auth: true });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//
|
|
129
|
+
// Agents
|
|
130
|
+
//
|
|
131
|
+
|
|
67
132
|
public createAgent(body: CreateAgentRequestBody, args?: EdgeHttpGetArgs): Promise<CreateAgentResponseBody> {
|
|
68
|
-
return this._call('/agents/create', { ...args, method: 'POST', body });
|
|
133
|
+
return this._call(new URL('/agents/create', this.baseUrl), { ...args, method: 'POST', body });
|
|
69
134
|
}
|
|
70
135
|
|
|
71
136
|
public getAgentStatus(
|
|
72
137
|
request: { ownerIdentityKey: PublicKey },
|
|
73
138
|
args?: EdgeHttpGetArgs,
|
|
74
139
|
): Promise<GetAgentStatusResponseBody> {
|
|
75
|
-
return this._call(`/users/${request.ownerIdentityKey.toHex()}/agent/status`,
|
|
140
|
+
return this._call(new URL(`/users/${request.ownerIdentityKey.toHex()}/agent/status`, this.baseUrl), {
|
|
141
|
+
...args,
|
|
142
|
+
method: 'GET',
|
|
143
|
+
});
|
|
76
144
|
}
|
|
77
145
|
|
|
146
|
+
//
|
|
147
|
+
// Credentials
|
|
148
|
+
//
|
|
149
|
+
|
|
78
150
|
public getCredentialsForNotarization(spaceId: SpaceId, args?: EdgeHttpGetArgs): Promise<GetNotarizationResponseBody> {
|
|
79
|
-
return this._call(`/spaces/${spaceId}/notarization`, { ...args, method: 'GET' });
|
|
151
|
+
return this._call(new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, method: 'GET' });
|
|
80
152
|
}
|
|
81
153
|
|
|
82
154
|
public async notarizeCredentials(
|
|
@@ -84,76 +156,76 @@ export class EdgeHttpClient {
|
|
|
84
156
|
body: PostNotarizationRequestBody,
|
|
85
157
|
args?: EdgeHttpGetArgs,
|
|
86
158
|
): Promise<void> {
|
|
87
|
-
await this._call(`/spaces/${spaceId}/notarization`, { ...args, body, method: 'POST' });
|
|
159
|
+
await this._call(new URL(`/spaces/${spaceId}/notarization`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
88
160
|
}
|
|
89
161
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
args?: EdgeHttpGetArgs,
|
|
94
|
-
): Promise<JoinSpaceResponseBody> {
|
|
95
|
-
return this._call(`/spaces/${spaceId}/join`, { ...args, body, method: 'POST' });
|
|
96
|
-
}
|
|
162
|
+
//
|
|
163
|
+
// Identity
|
|
164
|
+
//
|
|
97
165
|
|
|
98
166
|
public async recoverIdentity(
|
|
99
167
|
body: RecoverIdentityRequest,
|
|
100
168
|
args?: EdgeHttpGetArgs,
|
|
101
169
|
): Promise<RecoverIdentityResponseBody> {
|
|
102
|
-
return this._call('/identity/recover', { ...args, body, method: 'POST' });
|
|
170
|
+
return this._call(new URL('/identity/recover', this.baseUrl), { ...args, body, method: 'POST' });
|
|
103
171
|
}
|
|
104
172
|
|
|
105
|
-
|
|
173
|
+
//
|
|
174
|
+
// Invitations
|
|
175
|
+
//
|
|
176
|
+
|
|
177
|
+
public async joinSpaceByInvitation(
|
|
106
178
|
spaceId: SpaceId,
|
|
107
|
-
|
|
108
|
-
input: any,
|
|
179
|
+
body: JoinSpaceRequest,
|
|
109
180
|
args?: EdgeHttpGetArgs,
|
|
110
|
-
): Promise<
|
|
111
|
-
return this._call(`/
|
|
181
|
+
): Promise<JoinSpaceResponseBody> {
|
|
182
|
+
return this._call(new URL(`/spaces/${spaceId}/join`, this.baseUrl), { ...args, body, method: 'POST' });
|
|
112
183
|
}
|
|
113
184
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
args?: EdgeHttpGetArgs,
|
|
118
|
-
): Promise<UploadFunctionResponseBody> {
|
|
119
|
-
const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
|
|
120
|
-
return this._call(path, { ...args, body, method: 'PUT' });
|
|
121
|
-
}
|
|
185
|
+
//
|
|
186
|
+
// OAuth and credentials
|
|
187
|
+
//
|
|
122
188
|
|
|
123
189
|
public async initiateOAuthFlow(
|
|
124
190
|
body: InitiateOAuthFlowRequest,
|
|
125
191
|
args?: EdgeHttpGetArgs,
|
|
126
192
|
): Promise<InitiateOAuthFlowResponse> {
|
|
127
|
-
return this._call('/oauth/initiate', { ...args, body, method: 'POST' });
|
|
193
|
+
return this._call(new URL('/oauth/initiate', this.baseUrl), { ...args, body, method: 'POST' });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
//
|
|
197
|
+
// Spaces
|
|
198
|
+
//
|
|
199
|
+
|
|
200
|
+
async createSpace(body: CreateSpaceRequest, args?: EdgeHttpGetArgs): Promise<CreateSpaceResponseBody> {
|
|
201
|
+
return this._call(new URL('/spaces/create', this.baseUrl), { ...args, body, method: 'POST' });
|
|
128
202
|
}
|
|
129
203
|
|
|
204
|
+
//
|
|
205
|
+
// Queues
|
|
206
|
+
//
|
|
207
|
+
|
|
130
208
|
public async queryQueue(
|
|
131
209
|
subspaceTag: string,
|
|
132
210
|
spaceId: SpaceId,
|
|
133
|
-
query: QueueQuery,
|
|
211
|
+
query: FeedProtocol.QueueQuery,
|
|
134
212
|
args?: EdgeHttpGetArgs,
|
|
135
|
-
): Promise<QueryResult> {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
queryParams.set('objectIds', query.objectIds.join(','));
|
|
152
|
-
}
|
|
153
|
-
return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query?${queryParams.toString()}`, {
|
|
154
|
-
...args,
|
|
155
|
-
method: 'GET',
|
|
156
|
-
});
|
|
213
|
+
): Promise<FeedProtocol.QueryResult> {
|
|
214
|
+
const queueId = query.queueIds?.[0];
|
|
215
|
+
invariant(queueId, 'queueId required');
|
|
216
|
+
return this._call(
|
|
217
|
+
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
|
|
218
|
+
after: query.after,
|
|
219
|
+
before: query.before,
|
|
220
|
+
limit: query.limit,
|
|
221
|
+
reverse: query.reverse,
|
|
222
|
+
objectIds: query.objectIds?.join(','),
|
|
223
|
+
}),
|
|
224
|
+
{
|
|
225
|
+
...args,
|
|
226
|
+
method: 'GET',
|
|
227
|
+
},
|
|
228
|
+
);
|
|
157
229
|
}
|
|
158
230
|
|
|
159
231
|
public async insertIntoQueue(
|
|
@@ -163,110 +235,287 @@ export class EdgeHttpClient {
|
|
|
163
235
|
objects: unknown[],
|
|
164
236
|
args?: EdgeHttpGetArgs,
|
|
165
237
|
): Promise<void> {
|
|
166
|
-
return this._call(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, {
|
|
238
|
+
return this._call(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
167
239
|
...args,
|
|
168
240
|
body: { objects },
|
|
169
241
|
method: 'POST',
|
|
170
242
|
});
|
|
171
243
|
}
|
|
172
244
|
|
|
173
|
-
async deleteFromQueue(
|
|
245
|
+
public async deleteFromQueue(
|
|
174
246
|
subspaceTag: string,
|
|
175
247
|
spaceId: SpaceId,
|
|
176
248
|
queueId: ObjectId,
|
|
177
249
|
objectIds: ObjectId[],
|
|
178
250
|
args?: EdgeHttpGetArgs,
|
|
179
251
|
): Promise<void> {
|
|
180
|
-
return this._call(
|
|
252
|
+
return this._call(
|
|
253
|
+
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
254
|
+
ids: objectIds.join(','),
|
|
255
|
+
}),
|
|
256
|
+
{
|
|
257
|
+
...args,
|
|
258
|
+
method: 'DELETE',
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
//
|
|
264
|
+
// Functions
|
|
265
|
+
//
|
|
266
|
+
|
|
267
|
+
public async uploadFunction(
|
|
268
|
+
pathParts: { functionId?: string },
|
|
269
|
+
body: UploadFunctionRequest,
|
|
270
|
+
args?: EdgeHttpGetArgs,
|
|
271
|
+
): Promise<UploadFunctionResponseBody> {
|
|
272
|
+
const formData = new FormData();
|
|
273
|
+
formData.append('name', body.name ?? '');
|
|
274
|
+
formData.append('version', body.version);
|
|
275
|
+
formData.append('ownerPublicKey', body.ownerPublicKey);
|
|
276
|
+
formData.append('entryPoint', body.entryPoint);
|
|
277
|
+
body.runtime && formData.append('runtime', body.runtime);
|
|
278
|
+
for (const [filename, content] of Object.entries(body.assets)) {
|
|
279
|
+
formData.append(
|
|
280
|
+
'assets',
|
|
281
|
+
new Blob([content as Uint8Array<ArrayBuffer>], { type: getFileMimeType(filename) }),
|
|
282
|
+
filename,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
|
|
287
|
+
return this._call(new URL(path, this.baseUrl), {
|
|
181
288
|
...args,
|
|
182
|
-
|
|
183
|
-
method: '
|
|
289
|
+
body: formData,
|
|
290
|
+
method: 'PUT',
|
|
291
|
+
json: false,
|
|
184
292
|
});
|
|
185
293
|
}
|
|
186
294
|
|
|
187
|
-
async
|
|
188
|
-
return this._call('/
|
|
295
|
+
public async listFunctions(args?: EdgeHttpGetArgs): Promise<any> {
|
|
296
|
+
return this._call(new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
|
|
189
297
|
}
|
|
190
298
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
299
|
+
public async invokeFunction(
|
|
300
|
+
params: {
|
|
301
|
+
functionId: string;
|
|
302
|
+
version?: string;
|
|
303
|
+
spaceId?: SpaceId;
|
|
304
|
+
cpuTimeLimit?: number;
|
|
305
|
+
subrequestsLimit?: number;
|
|
306
|
+
},
|
|
307
|
+
input: unknown,
|
|
308
|
+
args?: EdgeHttpGetArgs,
|
|
309
|
+
): Promise<any> {
|
|
310
|
+
const url = new URL(`/functions/${params.functionId}`, this.baseUrl);
|
|
311
|
+
if (params.version) {
|
|
312
|
+
url.searchParams.set('version', params.version);
|
|
202
313
|
}
|
|
314
|
+
if (params.spaceId) {
|
|
315
|
+
url.searchParams.set('spaceId', params.spaceId.toString());
|
|
316
|
+
}
|
|
317
|
+
if (params.cpuTimeLimit) {
|
|
318
|
+
url.searchParams.set('cpuTimeLimit', params.cpuTimeLimit.toString());
|
|
319
|
+
}
|
|
320
|
+
if (params.subrequestsLimit) {
|
|
321
|
+
url.searchParams.set('subrequestsLimit', params.subrequestsLimit.toString());
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return this._call(url, {
|
|
325
|
+
...args,
|
|
326
|
+
body: input,
|
|
327
|
+
method: 'POST',
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
//
|
|
332
|
+
// Workflows
|
|
333
|
+
//
|
|
334
|
+
|
|
335
|
+
public async executeWorkflow(
|
|
336
|
+
spaceId: SpaceId,
|
|
337
|
+
graphId: ObjectId,
|
|
338
|
+
input: any,
|
|
339
|
+
args?: EdgeHttpGetArgs,
|
|
340
|
+
): Promise<ExecuteWorkflowResponseBody> {
|
|
341
|
+
return this._call(new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
342
|
+
...args,
|
|
343
|
+
body: input,
|
|
344
|
+
method: 'POST',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
//
|
|
349
|
+
// Triggers
|
|
350
|
+
//
|
|
351
|
+
|
|
352
|
+
public async getCronTriggers(spaceId: SpaceId): Promise<GetCronTriggersResponse> {
|
|
353
|
+
return this._call<GetCronTriggersResponse>(new URL(`/test/functions/${spaceId}/triggers/crons`, this.baseUrl), {
|
|
354
|
+
method: 'GET',
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
public async forceRunCronTrigger(spaceId: SpaceId, triggerId: ObjectId) {
|
|
359
|
+
return this._call(new URL(`/test/functions/${spaceId}/triggers/crons/${triggerId}/run`, this.baseUrl), {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
//
|
|
365
|
+
// Import/Export space.
|
|
366
|
+
//
|
|
367
|
+
|
|
368
|
+
public async importBundle(
|
|
369
|
+
spaceId: SpaceId, //
|
|
370
|
+
body: ImportBundleRequest,
|
|
371
|
+
args?: EdgeHttpGetArgs,
|
|
372
|
+
): Promise<void> {
|
|
373
|
+
return this._call(new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });
|
|
374
|
+
}
|
|
203
375
|
|
|
204
|
-
|
|
376
|
+
public async exportBundle(
|
|
377
|
+
spaceId: SpaceId,
|
|
378
|
+
body: ExportBundleRequest,
|
|
379
|
+
args?: EdgeHttpGetArgs,
|
|
380
|
+
): Promise<ExportBundleResponse> {
|
|
381
|
+
return this._call(new URL(`/spaces/${spaceId}/export`, this.baseUrl), {
|
|
382
|
+
...args,
|
|
383
|
+
body,
|
|
384
|
+
method: 'POST',
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
//
|
|
389
|
+
// Internal
|
|
390
|
+
//
|
|
391
|
+
|
|
392
|
+
private async _fetch<T>(url: URL, _args: EdgeHttpRequestArgs): Promise<T> {
|
|
393
|
+
return Function.pipe(
|
|
394
|
+
HttpClient.get(url),
|
|
395
|
+
withLogging,
|
|
396
|
+
withRetryConfig,
|
|
397
|
+
Effect.provide(FetchHttpClient.layer),
|
|
398
|
+
Effect.provide(HttpConfig.default),
|
|
399
|
+
Effect.withSpan('EdgeHttpClient'),
|
|
400
|
+
runAndForwardErrors,
|
|
401
|
+
) as T;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// TODO(burdon): Refactor with effect (see edge-http-client.test.ts).
|
|
405
|
+
private async _call<T>(url: URL, args: EdgeHttpRequestArgs): Promise<T> {
|
|
406
|
+
const shouldRetry = createRetryHandler(args);
|
|
407
|
+
const requestContext = args.context ?? Context.default();
|
|
408
|
+
log('fetch', { url, request: args.body });
|
|
205
409
|
|
|
206
410
|
let handledAuth = false;
|
|
207
|
-
|
|
411
|
+
const tryCount = 1;
|
|
208
412
|
while (true) {
|
|
209
|
-
let processingError: EdgeCallFailedError;
|
|
210
|
-
let retryAfterHeaderValue: number = Number.NaN;
|
|
413
|
+
let processingError: EdgeCallFailedError | undefined = undefined;
|
|
211
414
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
415
|
+
if (!this._authHeader && args.auth) {
|
|
416
|
+
const response = await fetch(new URL(`/auth`, this.baseUrl));
|
|
417
|
+
if (response.status === 401) {
|
|
418
|
+
this._authHeader = await this._handleUnauthorized(response);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
214
421
|
|
|
215
|
-
|
|
422
|
+
const request = createRequest(args, this._authHeader);
|
|
423
|
+
log('call edge', { url, tryCount, authHeader: !!this._authHeader });
|
|
424
|
+
const response = await fetch(url, request);
|
|
216
425
|
|
|
217
426
|
if (response.ok) {
|
|
218
|
-
const body =
|
|
427
|
+
const body = await response.clone().json();
|
|
428
|
+
invariant(body, 'Expected body to be present');
|
|
429
|
+
if (!('success' in body)) {
|
|
430
|
+
return body;
|
|
431
|
+
}
|
|
219
432
|
if (body.success) {
|
|
220
433
|
return body.data;
|
|
221
434
|
}
|
|
222
|
-
|
|
223
|
-
log('unsuccessful edge response', { path, body });
|
|
224
|
-
|
|
225
|
-
if (body.errorData?.type === 'auth_challenge' && typeof body.errorData?.challenge === 'string') {
|
|
226
|
-
processingError = new EdgeAuthChallengeError(body.errorData.challenge, body.errorData);
|
|
227
|
-
} else {
|
|
228
|
-
processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
|
|
229
|
-
}
|
|
230
435
|
} else if (response.status === 401 && !handledAuth) {
|
|
231
|
-
|
|
436
|
+
this._authHeader = await this._handleUnauthorized(response);
|
|
232
437
|
handledAuth = true;
|
|
233
438
|
continue;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const body: EdgeFailure =
|
|
442
|
+
response.headers.get('Content-Type') === 'application/json' ? await response.clone().json() : undefined;
|
|
443
|
+
|
|
444
|
+
invariant(!body?.success, 'Expected body to not be a failure response or undefined.');
|
|
445
|
+
|
|
446
|
+
if (body?.data?.type === 'auth_challenge' && typeof body?.data?.challenge === 'string') {
|
|
447
|
+
processingError = new EdgeAuthChallengeError(body.data.challenge, body.data);
|
|
448
|
+
} else if (body?.success === false) {
|
|
449
|
+
processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
|
|
234
450
|
} else {
|
|
235
|
-
|
|
451
|
+
invariant(!response.ok, 'Expected response to not be ok.');
|
|
452
|
+
processingError = await EdgeCallFailedError.fromHttpFailure(response);
|
|
236
453
|
}
|
|
237
454
|
} catch (error: any) {
|
|
238
455
|
processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
|
|
239
456
|
}
|
|
240
457
|
|
|
241
|
-
if (processingError
|
|
242
|
-
log('retrying edge request', {
|
|
458
|
+
if (processingError?.isRetryable && (await shouldRetry(requestContext, processingError.retryAfterMs))) {
|
|
459
|
+
log.verbose('retrying edge request', { url, processingError });
|
|
243
460
|
} else {
|
|
244
|
-
throw processingError
|
|
461
|
+
throw processingError!;
|
|
245
462
|
}
|
|
246
463
|
}
|
|
247
464
|
}
|
|
248
465
|
|
|
249
466
|
private async _handleUnauthorized(response: Response): Promise<string> {
|
|
250
467
|
if (!this._edgeIdentity) {
|
|
251
|
-
log.warn('
|
|
252
|
-
throw EdgeCallFailedError.fromHttpFailure(response);
|
|
468
|
+
log.warn('unauthorized response received before identity was set');
|
|
469
|
+
throw await EdgeCallFailedError.fromHttpFailure(response);
|
|
253
470
|
}
|
|
471
|
+
|
|
254
472
|
const challenge = await handleAuthChallenge(response, this._edgeIdentity);
|
|
255
|
-
|
|
256
|
-
log('auth header updated');
|
|
257
|
-
return this._authHeader;
|
|
473
|
+
return encodeAuthHeader(challenge);
|
|
258
474
|
}
|
|
259
475
|
}
|
|
260
476
|
|
|
261
|
-
const
|
|
262
|
-
|
|
477
|
+
const createRequest = (
|
|
478
|
+
{ method, body, json = true }: EdgeHttpRequestArgs,
|
|
479
|
+
authHeader: string | undefined,
|
|
480
|
+
): RequestInit => {
|
|
481
|
+
let requestBody: BodyInit | undefined;
|
|
482
|
+
const headers: HeadersInit = {};
|
|
483
|
+
|
|
484
|
+
if (json) {
|
|
485
|
+
requestBody = body && JSON.stringify(body);
|
|
486
|
+
headers['Content-Type'] = 'application/json';
|
|
487
|
+
} else {
|
|
488
|
+
requestBody = body;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (typeof requestBody === 'string' && requestBody.length > WARNING_BODY_SIZE) {
|
|
492
|
+
log.warn('Request with large body', { bodySize: requestBody.length });
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (authHeader) {
|
|
496
|
+
headers['Authorization'] = authHeader;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
method,
|
|
501
|
+
body: requestBody,
|
|
502
|
+
headers,
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* @deprecated
|
|
508
|
+
*/
|
|
509
|
+
const createRetryHandler = ({ retry }: EdgeHttpRequestArgs) => {
|
|
510
|
+
if (!retry || retry.count < 1) {
|
|
263
511
|
return async () => false;
|
|
264
512
|
}
|
|
513
|
+
|
|
265
514
|
let retries = 0;
|
|
266
|
-
const maxRetries =
|
|
267
|
-
const baseTimeout =
|
|
268
|
-
const jitter =
|
|
269
|
-
return async (ctx: Context, retryAfter
|
|
515
|
+
const maxRetries = retry.count ?? DEFAULT_MAX_RETRIES_COUNT;
|
|
516
|
+
const baseTimeout = retry.timeout ?? DEFAULT_RETRY_TIMEOUT;
|
|
517
|
+
const jitter = retry.jitter ?? DEFAULT_RETRY_JITTER;
|
|
518
|
+
return async (ctx: Context, retryAfter?: number) => {
|
|
270
519
|
if (++retries > maxRetries || ctx.disposed) {
|
|
271
520
|
return false;
|
|
272
521
|
}
|
|
@@ -282,42 +531,9 @@ const createRetryHandler = (args: EdgeHttpCallArgs) => {
|
|
|
282
531
|
};
|
|
283
532
|
};
|
|
284
533
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
* Delay before retries in ms.
|
|
292
|
-
*/
|
|
293
|
-
timeout?: number;
|
|
294
|
-
/**
|
|
295
|
-
* A random amount of time before retrying to help prevent large bursts of requests.
|
|
296
|
-
*/
|
|
297
|
-
jitter?: number;
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
export type EdgeHttpGetArgs = { context?: Context; retry?: RetryConfig };
|
|
301
|
-
|
|
302
|
-
export type EdgeHttpPostArgs = { context?: Context; body?: any; retry?: RetryConfig };
|
|
303
|
-
|
|
304
|
-
type EdgeHttpCallArgs = {
|
|
305
|
-
method: string;
|
|
306
|
-
body?: any;
|
|
307
|
-
context?: Context;
|
|
308
|
-
retry?: RetryConfig;
|
|
309
|
-
query?: Record<string, string>;
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const createRequest = (args: EdgeHttpCallArgs, authHeader: string | undefined): RequestInit => {
|
|
313
|
-
return {
|
|
314
|
-
method: args.method,
|
|
315
|
-
body: args.body && JSON.stringify(args.body),
|
|
316
|
-
headers: authHeader ? { Authorization: authHeader } : undefined,
|
|
317
|
-
};
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
const encodeAuthHeader = (challenge: Uint8Array) => {
|
|
321
|
-
const encodedChallenge = Buffer.from(challenge).toString('base64');
|
|
322
|
-
return `VerifiablePresentation pb;base64,${encodedChallenge}`;
|
|
323
|
-
};
|
|
534
|
+
const getFileMimeType = (filename: string) =>
|
|
535
|
+
['.js', '.mjs'].some((codeExtension) => filename.endsWith(codeExtension))
|
|
536
|
+
? 'application/javascript+module'
|
|
537
|
+
: filename.endsWith('.wasm')
|
|
538
|
+
? 'application/wasm'
|
|
539
|
+
: 'application/octet-stream';
|