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