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