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