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