@dxos/edge-client 0.8.4-main.ae835ea → 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-VESGVCLQ.mjs → neutral/chunk-ZIQ5T3A7.mjs} +6 -40
- package/dist/lib/{browser/chunk-VESGVCLQ.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 +208 -360
- 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 +69 -31
- 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 +1 -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 +2 -2
- 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 +2 -2
- 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 +29 -32
- package/src/edge-client.test.ts +16 -11
- package/src/edge-client.ts +19 -3
- package/src/edge-http-client.test.ts +3 -2
- package/src/edge-http-client.ts +210 -66
- package/src/edge-ws-connection.ts +2 -1
- package/src/http-client.test.ts +3 -2
- package/src/http-client.ts +5 -1
- package/src/testing/test-utils.ts +4 -4
- 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-JTBFRYNM.mjs +0 -303
- package/dist/lib/node-esm/chunk-JTBFRYNM.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 -1342
- 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
|
@@ -8,7 +8,8 @@ import * as Effect from 'effect/Effect';
|
|
|
8
8
|
import * as Function from 'effect/Function';
|
|
9
9
|
|
|
10
10
|
import { sleep } from '@dxos/async';
|
|
11
|
-
import { Context } from '@dxos/context';
|
|
11
|
+
import { Context, TRACE_SPAN_ATTRIBUTE, type TraceContextData } from '@dxos/context';
|
|
12
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
12
13
|
import { invariant } from '@dxos/invariant';
|
|
13
14
|
import { type PublicKey, type SpaceId } from '@dxos/keys';
|
|
14
15
|
import { log } from '@dxos/log';
|
|
@@ -17,14 +18,18 @@ import {
|
|
|
17
18
|
type CreateAgentResponseBody,
|
|
18
19
|
type CreateSpaceRequest,
|
|
19
20
|
type CreateSpaceResponseBody,
|
|
21
|
+
EDGE_CLIENT_TAG_HEADER,
|
|
20
22
|
EdgeAuthChallengeError,
|
|
21
|
-
type EdgeBody,
|
|
22
23
|
EdgeCallFailedError,
|
|
24
|
+
type EdgeFailure,
|
|
23
25
|
type EdgeStatus,
|
|
24
26
|
type ExecuteWorkflowResponseBody,
|
|
25
27
|
type ExportBundleRequest,
|
|
26
28
|
type ExportBundleResponse,
|
|
29
|
+
type FeedProtocol,
|
|
27
30
|
type GetAgentStatusResponseBody,
|
|
31
|
+
type GetPluginVersionsResponseBody,
|
|
32
|
+
type GetPluginsResponseBody,
|
|
28
33
|
type GetNotarizationResponseBody,
|
|
29
34
|
type ImportBundleRequest,
|
|
30
35
|
type InitiateOAuthFlowRequest,
|
|
@@ -33,19 +38,32 @@ import {
|
|
|
33
38
|
type JoinSpaceResponseBody,
|
|
34
39
|
type ObjectId,
|
|
35
40
|
type PostNotarizationRequestBody,
|
|
36
|
-
type QueryResult,
|
|
37
|
-
type QueueQuery,
|
|
38
41
|
type RecoverIdentityRequest,
|
|
39
42
|
type RecoverIdentityResponseBody,
|
|
40
43
|
type UploadFunctionRequest,
|
|
41
44
|
type UploadFunctionResponseBody,
|
|
42
45
|
} from '@dxos/protocols';
|
|
46
|
+
import {
|
|
47
|
+
type QueryRequest as QueryRequestProto,
|
|
48
|
+
type QueryResponse as QueryResponseProto,
|
|
49
|
+
} from '@dxos/protocols/proto/dxos/echo/query';
|
|
43
50
|
import { createUrl } from '@dxos/util';
|
|
44
51
|
|
|
45
52
|
import { type EdgeIdentity, handleAuthChallenge } from './edge-identity';
|
|
46
53
|
import { HttpConfig, encodeAuthHeader, withLogging, withRetryConfig } from './http-client';
|
|
47
54
|
import { getEdgeUrlWithProtocol } from './utils';
|
|
48
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
|
+
|
|
49
67
|
const DEFAULT_RETRY_TIMEOUT = 1500;
|
|
50
68
|
const DEFAULT_RETRY_JITTER = 500;
|
|
51
69
|
const DEFAULT_MAX_RETRIES_COUNT = 3;
|
|
@@ -68,7 +86,6 @@ export type RetryConfig = {
|
|
|
68
86
|
|
|
69
87
|
type EdgeHttpRequestArgs = {
|
|
70
88
|
method: string;
|
|
71
|
-
context?: Context;
|
|
72
89
|
retry?: RetryConfig;
|
|
73
90
|
body?: any;
|
|
74
91
|
/**
|
|
@@ -77,17 +94,30 @@ type EdgeHttpRequestArgs = {
|
|
|
77
94
|
json?: boolean;
|
|
78
95
|
|
|
79
96
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
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.
|
|
82
100
|
*/
|
|
83
|
-
|
|
101
|
+
auth?: boolean;
|
|
84
102
|
};
|
|
85
103
|
|
|
86
|
-
export type
|
|
87
|
-
|
|
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
|
+
};
|
|
88
117
|
|
|
89
118
|
export class EdgeHttpClient {
|
|
90
119
|
private readonly _baseUrl: string;
|
|
120
|
+
private readonly _clientTag: string | undefined;
|
|
91
121
|
|
|
92
122
|
private _edgeIdentity: EdgeIdentity | undefined;
|
|
93
123
|
|
|
@@ -96,8 +126,9 @@ export class EdgeHttpClient {
|
|
|
96
126
|
*/
|
|
97
127
|
private _authHeader: string | undefined;
|
|
98
128
|
|
|
99
|
-
constructor(baseUrl: string) {
|
|
129
|
+
constructor(baseUrl: string, options?: EdgeHttpClientOptions) {
|
|
100
130
|
this._baseUrl = getEdgeUrlWithProtocol(baseUrl, 'http');
|
|
131
|
+
this._clientTag = options?.clientTag;
|
|
101
132
|
log('created', { url: this._baseUrl });
|
|
102
133
|
}
|
|
103
134
|
|
|
@@ -116,23 +147,28 @@ export class EdgeHttpClient {
|
|
|
116
147
|
// Status
|
|
117
148
|
//
|
|
118
149
|
|
|
119
|
-
public async getStatus(args?:
|
|
120
|
-
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 });
|
|
121
152
|
}
|
|
122
153
|
|
|
123
154
|
//
|
|
124
155
|
// Agents
|
|
125
156
|
//
|
|
126
157
|
|
|
127
|
-
public createAgent(
|
|
128
|
-
|
|
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 });
|
|
129
164
|
}
|
|
130
165
|
|
|
131
166
|
public getAgentStatus(
|
|
167
|
+
ctx: Context,
|
|
132
168
|
request: { ownerIdentityKey: PublicKey },
|
|
133
|
-
args?:
|
|
169
|
+
args?: EdgeHttpCallArgs,
|
|
134
170
|
): Promise<GetAgentStatusResponseBody> {
|
|
135
|
-
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), {
|
|
136
172
|
...args,
|
|
137
173
|
method: 'GET',
|
|
138
174
|
});
|
|
@@ -142,16 +178,21 @@ export class EdgeHttpClient {
|
|
|
142
178
|
// Credentials
|
|
143
179
|
//
|
|
144
180
|
|
|
145
|
-
public getCredentialsForNotarization(
|
|
146
|
-
|
|
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' });
|
|
147
187
|
}
|
|
148
188
|
|
|
149
189
|
public async notarizeCredentials(
|
|
190
|
+
ctx: Context,
|
|
150
191
|
spaceId: SpaceId,
|
|
151
192
|
body: PostNotarizationRequestBody,
|
|
152
|
-
args?:
|
|
193
|
+
args?: EdgeHttpCallArgs,
|
|
153
194
|
): Promise<void> {
|
|
154
|
-
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' });
|
|
155
196
|
}
|
|
156
197
|
|
|
157
198
|
//
|
|
@@ -159,10 +200,11 @@ export class EdgeHttpClient {
|
|
|
159
200
|
//
|
|
160
201
|
|
|
161
202
|
public async recoverIdentity(
|
|
203
|
+
ctx: Context,
|
|
162
204
|
body: RecoverIdentityRequest,
|
|
163
|
-
args?:
|
|
205
|
+
args?: EdgeHttpCallArgs,
|
|
164
206
|
): Promise<RecoverIdentityResponseBody> {
|
|
165
|
-
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' });
|
|
166
208
|
}
|
|
167
209
|
|
|
168
210
|
//
|
|
@@ -170,11 +212,12 @@ export class EdgeHttpClient {
|
|
|
170
212
|
//
|
|
171
213
|
|
|
172
214
|
public async joinSpaceByInvitation(
|
|
215
|
+
ctx: Context,
|
|
173
216
|
spaceId: SpaceId,
|
|
174
217
|
body: JoinSpaceRequest,
|
|
175
|
-
args?:
|
|
218
|
+
args?: EdgeHttpCallArgs,
|
|
176
219
|
): Promise<JoinSpaceResponseBody> {
|
|
177
|
-
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' });
|
|
178
221
|
}
|
|
179
222
|
|
|
180
223
|
//
|
|
@@ -182,18 +225,19 @@ export class EdgeHttpClient {
|
|
|
182
225
|
//
|
|
183
226
|
|
|
184
227
|
public async initiateOAuthFlow(
|
|
228
|
+
ctx: Context,
|
|
185
229
|
body: InitiateOAuthFlowRequest,
|
|
186
|
-
args?:
|
|
230
|
+
args?: EdgeHttpCallArgs,
|
|
187
231
|
): Promise<InitiateOAuthFlowResponse> {
|
|
188
|
-
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' });
|
|
189
233
|
}
|
|
190
234
|
|
|
191
235
|
//
|
|
192
236
|
// Spaces
|
|
193
237
|
//
|
|
194
238
|
|
|
195
|
-
async createSpace(body: CreateSpaceRequest, args?:
|
|
196
|
-
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' });
|
|
197
241
|
}
|
|
198
242
|
|
|
199
243
|
//
|
|
@@ -201,13 +245,16 @@ export class EdgeHttpClient {
|
|
|
201
245
|
//
|
|
202
246
|
|
|
203
247
|
public async queryQueue(
|
|
248
|
+
ctx: Context,
|
|
204
249
|
subspaceTag: string,
|
|
205
250
|
spaceId: SpaceId,
|
|
206
|
-
query: QueueQuery,
|
|
207
|
-
args?:
|
|
208
|
-
): Promise<
|
|
209
|
-
const
|
|
251
|
+
query: FeedProtocol.QueueQuery,
|
|
252
|
+
args?: EdgeHttpCallArgs,
|
|
253
|
+
): Promise<EdgeQueryQueueResponse> {
|
|
254
|
+
const queueId = query.queueIds?.[0];
|
|
255
|
+
invariant(queueId, 'queueId required');
|
|
210
256
|
return this._call(
|
|
257
|
+
ctx,
|
|
211
258
|
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}/query`, this.baseUrl), {
|
|
212
259
|
after: query.after,
|
|
213
260
|
before: query.before,
|
|
@@ -223,13 +270,14 @@ export class EdgeHttpClient {
|
|
|
223
270
|
}
|
|
224
271
|
|
|
225
272
|
public async insertIntoQueue(
|
|
273
|
+
ctx: Context,
|
|
226
274
|
subspaceTag: string,
|
|
227
275
|
spaceId: SpaceId,
|
|
228
276
|
queueId: ObjectId,
|
|
229
277
|
objects: unknown[],
|
|
230
|
-
args?:
|
|
278
|
+
args?: EdgeHttpCallArgs,
|
|
231
279
|
): Promise<void> {
|
|
232
|
-
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), {
|
|
233
281
|
...args,
|
|
234
282
|
body: { objects },
|
|
235
283
|
method: 'POST',
|
|
@@ -237,13 +285,15 @@ export class EdgeHttpClient {
|
|
|
237
285
|
}
|
|
238
286
|
|
|
239
287
|
public async deleteFromQueue(
|
|
288
|
+
ctx: Context,
|
|
240
289
|
subspaceTag: string,
|
|
241
290
|
spaceId: SpaceId,
|
|
242
291
|
queueId: ObjectId,
|
|
243
292
|
objectIds: ObjectId[],
|
|
244
|
-
args?:
|
|
293
|
+
args?: EdgeHttpCallArgs,
|
|
245
294
|
): Promise<void> {
|
|
246
295
|
return this._call(
|
|
296
|
+
ctx,
|
|
247
297
|
createUrl(new URL(`/spaces/${subspaceTag}/${spaceId}/queue/${queueId}`, this.baseUrl), {
|
|
248
298
|
ids: objectIds.join(','),
|
|
249
299
|
}),
|
|
@@ -259,15 +309,17 @@ export class EdgeHttpClient {
|
|
|
259
309
|
//
|
|
260
310
|
|
|
261
311
|
public async uploadFunction(
|
|
312
|
+
ctx: Context,
|
|
262
313
|
pathParts: { functionId?: string },
|
|
263
314
|
body: UploadFunctionRequest,
|
|
264
|
-
args?:
|
|
315
|
+
args?: EdgeHttpCallArgs,
|
|
265
316
|
): Promise<UploadFunctionResponseBody> {
|
|
266
317
|
const formData = new FormData();
|
|
267
318
|
formData.append('name', body.name ?? '');
|
|
268
319
|
formData.append('version', body.version);
|
|
269
320
|
formData.append('ownerPublicKey', body.ownerPublicKey);
|
|
270
321
|
formData.append('entryPoint', body.entryPoint);
|
|
322
|
+
body.runtime && formData.append('runtime', body.runtime);
|
|
271
323
|
for (const [filename, content] of Object.entries(body.assets)) {
|
|
272
324
|
formData.append(
|
|
273
325
|
'assets',
|
|
@@ -277,7 +329,7 @@ export class EdgeHttpClient {
|
|
|
277
329
|
}
|
|
278
330
|
|
|
279
331
|
const path = ['functions', ...(pathParts.functionId ? [pathParts.functionId] : [])].join('/');
|
|
280
|
-
return this._call(new URL(path, this.baseUrl), {
|
|
332
|
+
return this._call(ctx, new URL(path, this.baseUrl), {
|
|
281
333
|
...args,
|
|
282
334
|
body: formData,
|
|
283
335
|
method: 'PUT',
|
|
@@ -285,11 +337,12 @@ export class EdgeHttpClient {
|
|
|
285
337
|
});
|
|
286
338
|
}
|
|
287
339
|
|
|
288
|
-
public async listFunctions(args?:
|
|
289
|
-
return this._call(new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
|
|
340
|
+
public async listFunctions(ctx: Context, args?: EdgeHttpCallArgs): Promise<any> {
|
|
341
|
+
return this._call(ctx, new URL('/functions', this.baseUrl), { ...args, method: 'GET' });
|
|
290
342
|
}
|
|
291
343
|
|
|
292
344
|
public async invokeFunction(
|
|
345
|
+
ctx: Context,
|
|
293
346
|
params: {
|
|
294
347
|
functionId: string;
|
|
295
348
|
version?: string;
|
|
@@ -298,7 +351,7 @@ export class EdgeHttpClient {
|
|
|
298
351
|
subrequestsLimit?: number;
|
|
299
352
|
},
|
|
300
353
|
input: unknown,
|
|
301
|
-
args?:
|
|
354
|
+
args?: EdgeHttpCallArgs,
|
|
302
355
|
): Promise<any> {
|
|
303
356
|
const url = new URL(`/functions/${params.functionId}`, this.baseUrl);
|
|
304
357
|
if (params.version) {
|
|
@@ -314,11 +367,10 @@ export class EdgeHttpClient {
|
|
|
314
367
|
url.searchParams.set('subrequestsLimit', params.subrequestsLimit.toString());
|
|
315
368
|
}
|
|
316
369
|
|
|
317
|
-
return this._call(url, {
|
|
370
|
+
return this._call(ctx, url, {
|
|
318
371
|
...args,
|
|
319
372
|
body: input,
|
|
320
373
|
method: 'POST',
|
|
321
|
-
rawResponse: true,
|
|
322
374
|
});
|
|
323
375
|
}
|
|
324
376
|
|
|
@@ -327,12 +379,13 @@ export class EdgeHttpClient {
|
|
|
327
379
|
//
|
|
328
380
|
|
|
329
381
|
public async executeWorkflow(
|
|
382
|
+
ctx: Context,
|
|
330
383
|
spaceId: SpaceId,
|
|
331
384
|
graphId: ObjectId,
|
|
332
385
|
input: any,
|
|
333
|
-
args?:
|
|
386
|
+
args?: EdgeHttpCallArgs,
|
|
334
387
|
): Promise<ExecuteWorkflowResponseBody> {
|
|
335
|
-
return this._call(new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
388
|
+
return this._call(ctx, new URL(`/workflows/${spaceId}/${graphId}`, this.baseUrl), {
|
|
336
389
|
...args,
|
|
337
390
|
body: input,
|
|
338
391
|
method: 'POST',
|
|
@@ -343,8 +396,62 @@ export class EdgeHttpClient {
|
|
|
343
396
|
// Triggers
|
|
344
397
|
//
|
|
345
398
|
|
|
346
|
-
public async getCronTriggers(spaceId: SpaceId) {
|
|
347
|
-
return this._call(new URL(`/
|
|
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
|
+
});
|
|
348
455
|
}
|
|
349
456
|
|
|
350
457
|
//
|
|
@@ -352,19 +459,21 @@ export class EdgeHttpClient {
|
|
|
352
459
|
//
|
|
353
460
|
|
|
354
461
|
public async importBundle(
|
|
355
|
-
|
|
462
|
+
ctx: Context,
|
|
463
|
+
spaceId: SpaceId,
|
|
356
464
|
body: ImportBundleRequest,
|
|
357
|
-
args?:
|
|
465
|
+
args?: EdgeHttpCallArgs,
|
|
358
466
|
): Promise<void> {
|
|
359
|
-
return this._call(new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });
|
|
467
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/import`, this.baseUrl), { ...args, body, method: 'PUT' });
|
|
360
468
|
}
|
|
361
469
|
|
|
362
470
|
public async exportBundle(
|
|
471
|
+
ctx: Context,
|
|
363
472
|
spaceId: SpaceId,
|
|
364
473
|
body: ExportBundleRequest,
|
|
365
|
-
args?:
|
|
474
|
+
args?: EdgeHttpCallArgs,
|
|
366
475
|
): Promise<ExportBundleResponse> {
|
|
367
|
-
return this._call(new URL(`/spaces/${spaceId}/export`, this.baseUrl), {
|
|
476
|
+
return this._call(ctx, new URL(`/spaces/${spaceId}/export`, this.baseUrl), {
|
|
368
477
|
...args,
|
|
369
478
|
body,
|
|
370
479
|
method: 'POST',
|
|
@@ -375,7 +484,7 @@ export class EdgeHttpClient {
|
|
|
375
484
|
// Internal
|
|
376
485
|
//
|
|
377
486
|
|
|
378
|
-
private async _fetch<T>(url: URL,
|
|
487
|
+
private async _fetch<T>(url: URL, _args: EdgeHttpRequestArgs): Promise<T> {
|
|
379
488
|
return Function.pipe(
|
|
380
489
|
HttpClient.get(url),
|
|
381
490
|
withLogging,
|
|
@@ -383,29 +492,35 @@ export class EdgeHttpClient {
|
|
|
383
492
|
Effect.provide(FetchHttpClient.layer),
|
|
384
493
|
Effect.provide(HttpConfig.default),
|
|
385
494
|
Effect.withSpan('EdgeHttpClient'),
|
|
386
|
-
|
|
495
|
+
runAndForwardErrors,
|
|
387
496
|
) as T;
|
|
388
497
|
}
|
|
389
498
|
|
|
390
499
|
// TODO(burdon): Refactor with effect (see edge-http-client.test.ts).
|
|
391
|
-
private async _call<T>(url: URL, args: EdgeHttpRequestArgs): Promise<T> {
|
|
500
|
+
private async _call<T>(ctx: Context, url: URL, args: EdgeHttpRequestArgs): Promise<T> {
|
|
392
501
|
const shouldRetry = createRetryHandler(args);
|
|
393
|
-
const requestContext = args.context ?? Context.default();
|
|
394
502
|
log('fetch', { url, request: args.body });
|
|
395
503
|
|
|
504
|
+
const traceHeaders = getTraceHeaders(ctx);
|
|
505
|
+
|
|
396
506
|
let handledAuth = false;
|
|
507
|
+
const tryCount = 1;
|
|
397
508
|
while (true) {
|
|
398
509
|
let processingError: EdgeCallFailedError | undefined = undefined;
|
|
399
510
|
try {
|
|
400
|
-
|
|
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 });
|
|
401
520
|
const response = await fetch(url, request);
|
|
402
|
-
const body: EdgeBody<T> | undefined =
|
|
403
|
-
response.headers.get('Content-Type') === 'application/json' ? await response.clone().json() : undefined;
|
|
404
521
|
|
|
405
522
|
if (response.ok) {
|
|
406
|
-
|
|
407
|
-
return body as any;
|
|
408
|
-
}
|
|
523
|
+
const body = await response.clone().json();
|
|
409
524
|
invariant(body, 'Expected body to be present');
|
|
410
525
|
if (!('success' in body)) {
|
|
411
526
|
return body;
|
|
@@ -419,10 +534,13 @@ export class EdgeHttpClient {
|
|
|
419
534
|
continue;
|
|
420
535
|
}
|
|
421
536
|
|
|
537
|
+
const body: EdgeFailure =
|
|
538
|
+
response.headers.get('Content-Type') === 'application/json' ? await response.clone().json() : undefined;
|
|
539
|
+
|
|
422
540
|
invariant(!body?.success, 'Expected body to not be a failure response or undefined.');
|
|
423
541
|
|
|
424
|
-
if (body?.
|
|
425
|
-
processingError = new EdgeAuthChallengeError(body.
|
|
542
|
+
if (body?.data?.type === 'auth_challenge' && typeof body?.data?.challenge === 'string') {
|
|
543
|
+
processingError = new EdgeAuthChallengeError(body.data.challenge, body.data);
|
|
426
544
|
} else if (body?.success === false) {
|
|
427
545
|
processingError = EdgeCallFailedError.fromUnsuccessfulResponse(response, body);
|
|
428
546
|
} else {
|
|
@@ -433,8 +551,8 @@ export class EdgeHttpClient {
|
|
|
433
551
|
processingError = EdgeCallFailedError.fromProcessingFailureCause(error);
|
|
434
552
|
}
|
|
435
553
|
|
|
436
|
-
if (processingError?.isRetryable && (await shouldRetry(
|
|
437
|
-
log('retrying edge request', { url, processingError });
|
|
554
|
+
if (processingError?.isRetryable && (await shouldRetry(ctx, processingError.retryAfterMs))) {
|
|
555
|
+
log.verbose('retrying edge request', { url, processingError });
|
|
438
556
|
} else {
|
|
439
557
|
throw processingError!;
|
|
440
558
|
}
|
|
@@ -455,6 +573,8 @@ export class EdgeHttpClient {
|
|
|
455
573
|
const createRequest = (
|
|
456
574
|
{ method, body, json = true }: EdgeHttpRequestArgs,
|
|
457
575
|
authHeader: string | undefined,
|
|
576
|
+
traceHeaders?: Record<string, string>,
|
|
577
|
+
clientTag?: string,
|
|
458
578
|
): RequestInit => {
|
|
459
579
|
let requestBody: BodyInit | undefined;
|
|
460
580
|
const headers: HeadersInit = {};
|
|
@@ -474,6 +594,14 @@ const createRequest = (
|
|
|
474
594
|
headers['Authorization'] = authHeader;
|
|
475
595
|
}
|
|
476
596
|
|
|
597
|
+
if (traceHeaders) {
|
|
598
|
+
Object.assign(headers, traceHeaders);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (clientTag) {
|
|
602
|
+
headers[EDGE_CLIENT_TAG_HEADER] = clientTag;
|
|
603
|
+
}
|
|
604
|
+
|
|
477
605
|
return {
|
|
478
606
|
method,
|
|
479
607
|
body: requestBody,
|
|
@@ -481,6 +609,22 @@ const createRequest = (
|
|
|
481
609
|
};
|
|
482
610
|
};
|
|
483
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
|
+
|
|
484
628
|
/**
|
|
485
629
|
* @deprecated
|
|
486
630
|
*/
|
|
@@ -50,7 +50,7 @@ export class EdgeWsConnection extends Resource {
|
|
|
50
50
|
|
|
51
51
|
constructor(
|
|
52
52
|
private readonly _identity: EdgeIdentity,
|
|
53
|
-
private readonly _connectionInfo: { url: URL; protocolHeader?: string },
|
|
53
|
+
private readonly _connectionInfo: { url: URL; protocolHeader?: string; headers?: Record<string, string> },
|
|
54
54
|
private readonly _callbacks: EdgeWsConnectionCallbacks,
|
|
55
55
|
) {
|
|
56
56
|
super();
|
|
@@ -121,6 +121,7 @@ export class EdgeWsConnection extends Resource {
|
|
|
121
121
|
this._connectionInfo.protocolHeader
|
|
122
122
|
? [...baseProtocols, this._connectionInfo.protocolHeader]
|
|
123
123
|
: [...baseProtocols],
|
|
124
|
+
this._connectionInfo.headers ? { headers: this._connectionInfo.headers } : undefined,
|
|
124
125
|
);
|
|
125
126
|
const muxer = new WebSocketMuxer(this._ws);
|
|
126
127
|
this._wsMuxer = muxer;
|
package/src/http-client.test.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as Effect from 'effect/Effect';
|
|
|
8
8
|
import * as Function from 'effect/Function';
|
|
9
9
|
import { afterEach, beforeEach, describe, it } from 'vitest';
|
|
10
10
|
|
|
11
|
+
import { runAndForwardErrors } from '@dxos/effect';
|
|
11
12
|
import { invariant } from '@dxos/invariant';
|
|
12
13
|
|
|
13
14
|
import { HttpConfig, withLogging, withRetry, withRetryConfig } from './http-client';
|
|
@@ -36,7 +37,7 @@ describe('HttpClient', () => {
|
|
|
36
37
|
withRetry(HttpClient.get(server.url)),
|
|
37
38
|
Effect.provide(FetchHttpClient.layer),
|
|
38
39
|
Effect.withSpan('EdgeHttpClient'),
|
|
39
|
-
|
|
40
|
+
runAndForwardErrors,
|
|
40
41
|
);
|
|
41
42
|
expect(result).toMatchObject({ success: true, data: { value: 100 } });
|
|
42
43
|
}
|
|
@@ -49,7 +50,7 @@ describe('HttpClient', () => {
|
|
|
49
50
|
Effect.provide(FetchHttpClient.layer),
|
|
50
51
|
Effect.provide(HttpConfig.default), // TODO(burdon): Swap out to mock.
|
|
51
52
|
Effect.withSpan('EdgeHttpClient'), // TODO(burdon): OTEL.
|
|
52
|
-
|
|
53
|
+
runAndForwardErrors,
|
|
53
54
|
);
|
|
54
55
|
expect(result).toMatchObject({ success: true, data: { value: 100 } });
|
|
55
56
|
}
|
package/src/http-client.ts
CHANGED
|
@@ -61,7 +61,11 @@ export const withRetryConfig = (
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
export const withLogging = <A extends HttpClientResponse.HttpClientResponse, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
64
|
-
effect.pipe(
|
|
64
|
+
effect.pipe(
|
|
65
|
+
Effect.tap((res) => {
|
|
66
|
+
log.info('response', { status: res.status });
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
65
69
|
|
|
66
70
|
/**
|
|
67
71
|
*
|
|
@@ -16,13 +16,13 @@ import { toUint8Array } from '../protocol';
|
|
|
16
16
|
|
|
17
17
|
export const DEFAULT_PORT = 8080;
|
|
18
18
|
|
|
19
|
-
type
|
|
19
|
+
type TestEdgeWsServerProps = {
|
|
20
20
|
admitConnection?: Trigger;
|
|
21
21
|
payloadDecoder?: (payload: Uint8Array) => any;
|
|
22
22
|
messageHandler?: (payload: any) => Promise<Uint8Array | undefined>;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
export const createTestEdgeWsServer = async (port = DEFAULT_PORT, params?:
|
|
25
|
+
export const createTestEdgeWsServer = async (port = DEFAULT_PORT, params?: TestEdgeWsServerProps) => {
|
|
26
26
|
const wsServer = new WebSocket.Server({
|
|
27
27
|
port,
|
|
28
28
|
verifyClient: createConnectionDelayHandler(params),
|
|
@@ -86,7 +86,7 @@ export const createTestEdgeWsServer = async (port = DEFAULT_PORT, params?: TestE
|
|
|
86
86
|
};
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
const createConnectionDelayHandler = (params:
|
|
89
|
+
const createConnectionDelayHandler = (params: TestEdgeWsServerProps | undefined) => {
|
|
90
90
|
return (_: any, callback: (admit: boolean) => void) => {
|
|
91
91
|
if (params?.admitConnection) {
|
|
92
92
|
log('delaying edge connection admission');
|
|
@@ -116,7 +116,7 @@ const createResponseSender = (connection: () => WebSocketMuxer) => {
|
|
|
116
116
|
};
|
|
117
117
|
};
|
|
118
118
|
|
|
119
|
-
const decodePayload = async (request: Message, params:
|
|
119
|
+
const decodePayload = async (request: Message, params: TestEdgeWsServerProps | undefined) => {
|
|
120
120
|
const requestPayload = params?.payloadDecoder
|
|
121
121
|
? params.payloadDecoder(request.payload!.value!)
|
|
122
122
|
: protocol.getPayload(request, TextMessageSchema);
|