@durable-streams/client-conformance-tests 0.1.0 → 0.1.2
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 +21 -0
- package/dist/adapters/typescript-adapter.cjs +588 -0
- package/dist/adapters/typescript-adapter.d.cts +1 -0
- package/dist/adapters/typescript-adapter.js +0 -0
- package/dist/benchmark-runner-CLAR9oLd.cjs +1400 -0
- package/dist/chunk-BCwAaXi7.cjs +31 -0
- package/dist/cli.cjs +266 -0
- package/dist/cli.d.cts +1 -0
- package/dist/index.cjs +22 -0
- package/dist/index.d.cts +508 -0
- package/dist/protocol-3cf94Xyb.d.cts +472 -0
- package/dist/protocol-XeAOKBD-.cjs +175 -0
- package/dist/protocol.cjs +11 -0
- package/dist/protocol.d.cts +2 -0
- package/package.json +60 -31
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 durable-stream
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const require_chunk = require('../chunk-BCwAaXi7.cjs');
|
|
4
|
+
const require_protocol = require('../protocol-XeAOKBD-.cjs');
|
|
5
|
+
const node_readline = require_chunk.__toESM(require("node:readline"));
|
|
6
|
+
const __durable_streams_client = require_chunk.__toESM(require("@durable-streams/client"));
|
|
7
|
+
|
|
8
|
+
//#region src/adapters/typescript-adapter.ts
|
|
9
|
+
const CLIENT_VERSION = `0.0.1`;
|
|
10
|
+
let serverUrl = ``;
|
|
11
|
+
const streamContentTypes = new Map();
|
|
12
|
+
const dynamicHeaders = new Map();
|
|
13
|
+
const dynamicParams = new Map();
|
|
14
|
+
/** Resolve dynamic headers, returning both the header function map and tracked values */
|
|
15
|
+
function resolveDynamicHeaders() {
|
|
16
|
+
const headers = {};
|
|
17
|
+
const values = {};
|
|
18
|
+
for (const [name, config] of dynamicHeaders.entries()) {
|
|
19
|
+
let value;
|
|
20
|
+
switch (config.type) {
|
|
21
|
+
case `counter`:
|
|
22
|
+
config.counter++;
|
|
23
|
+
value = config.counter.toString();
|
|
24
|
+
break;
|
|
25
|
+
case `timestamp`:
|
|
26
|
+
value = Date.now().toString();
|
|
27
|
+
break;
|
|
28
|
+
case `token`:
|
|
29
|
+
value = config.tokenValue ?? ``;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
values[name] = value;
|
|
33
|
+
const capturedValue = value;
|
|
34
|
+
headers[name] = () => capturedValue;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
headers,
|
|
38
|
+
values
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Resolve dynamic params */
|
|
42
|
+
function resolveDynamicParams() {
|
|
43
|
+
const params = {};
|
|
44
|
+
const values = {};
|
|
45
|
+
for (const [name, config] of dynamicParams.entries()) {
|
|
46
|
+
let value;
|
|
47
|
+
switch (config.type) {
|
|
48
|
+
case `counter`:
|
|
49
|
+
config.counter++;
|
|
50
|
+
value = config.counter.toString();
|
|
51
|
+
break;
|
|
52
|
+
case `timestamp`:
|
|
53
|
+
value = Date.now().toString();
|
|
54
|
+
break;
|
|
55
|
+
default: value = ``;
|
|
56
|
+
}
|
|
57
|
+
values[name] = value;
|
|
58
|
+
const capturedValue = value;
|
|
59
|
+
params[name] = () => capturedValue;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
params,
|
|
63
|
+
values
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function handleCommand(command) {
|
|
67
|
+
switch (command.type) {
|
|
68
|
+
case `init`: {
|
|
69
|
+
serverUrl = command.serverUrl;
|
|
70
|
+
streamContentTypes.clear();
|
|
71
|
+
dynamicHeaders.clear();
|
|
72
|
+
dynamicParams.clear();
|
|
73
|
+
return {
|
|
74
|
+
type: `init`,
|
|
75
|
+
success: true,
|
|
76
|
+
clientName: `@durable-streams/client`,
|
|
77
|
+
clientVersion: CLIENT_VERSION,
|
|
78
|
+
features: {
|
|
79
|
+
batching: true,
|
|
80
|
+
sse: true,
|
|
81
|
+
longPoll: true,
|
|
82
|
+
streaming: true,
|
|
83
|
+
dynamicHeaders: true
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
case `create`: try {
|
|
88
|
+
const url = `${serverUrl}${command.path}`;
|
|
89
|
+
const contentType = command.contentType ?? `application/octet-stream`;
|
|
90
|
+
let alreadyExists = false;
|
|
91
|
+
try {
|
|
92
|
+
await __durable_streams_client.DurableStream.head({ url });
|
|
93
|
+
alreadyExists = true;
|
|
94
|
+
} catch {}
|
|
95
|
+
const ds = await __durable_streams_client.DurableStream.create({
|
|
96
|
+
url,
|
|
97
|
+
contentType,
|
|
98
|
+
ttlSeconds: command.ttlSeconds,
|
|
99
|
+
expiresAt: command.expiresAt,
|
|
100
|
+
headers: command.headers
|
|
101
|
+
});
|
|
102
|
+
streamContentTypes.set(command.path, contentType);
|
|
103
|
+
const head = await ds.head();
|
|
104
|
+
return {
|
|
105
|
+
type: `create`,
|
|
106
|
+
success: true,
|
|
107
|
+
status: alreadyExists ? 200 : 201,
|
|
108
|
+
offset: head.offset
|
|
109
|
+
};
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return errorResult(`create`, err);
|
|
112
|
+
}
|
|
113
|
+
case `connect`: try {
|
|
114
|
+
const url = `${serverUrl}${command.path}`;
|
|
115
|
+
const ds = await __durable_streams_client.DurableStream.connect({
|
|
116
|
+
url,
|
|
117
|
+
headers: command.headers
|
|
118
|
+
});
|
|
119
|
+
const head = await ds.head();
|
|
120
|
+
if (head.contentType) streamContentTypes.set(command.path, head.contentType);
|
|
121
|
+
return {
|
|
122
|
+
type: `connect`,
|
|
123
|
+
success: true,
|
|
124
|
+
status: 200,
|
|
125
|
+
offset: head.offset
|
|
126
|
+
};
|
|
127
|
+
} catch (err) {
|
|
128
|
+
return errorResult(`connect`, err);
|
|
129
|
+
}
|
|
130
|
+
case `append`: try {
|
|
131
|
+
const url = `${serverUrl}${command.path}`;
|
|
132
|
+
const contentType = streamContentTypes.get(command.path) ?? `application/octet-stream`;
|
|
133
|
+
const { headers: dynamicHdrs, values: headersSent } = resolveDynamicHeaders();
|
|
134
|
+
const { values: paramsSent } = resolveDynamicParams();
|
|
135
|
+
const mergedHeaders = {
|
|
136
|
+
...dynamicHdrs,
|
|
137
|
+
...command.headers
|
|
138
|
+
};
|
|
139
|
+
const ds = new __durable_streams_client.DurableStream({
|
|
140
|
+
url,
|
|
141
|
+
headers: mergedHeaders,
|
|
142
|
+
contentType
|
|
143
|
+
});
|
|
144
|
+
let body;
|
|
145
|
+
if (command.binary) body = require_protocol.decodeBase64(command.data);
|
|
146
|
+
else body = command.data;
|
|
147
|
+
await ds.append(body, { seq: command.seq?.toString() });
|
|
148
|
+
const head = await ds.head();
|
|
149
|
+
return {
|
|
150
|
+
type: `append`,
|
|
151
|
+
success: true,
|
|
152
|
+
status: 200,
|
|
153
|
+
offset: head.offset,
|
|
154
|
+
headersSent: Object.keys(headersSent).length > 0 ? headersSent : void 0,
|
|
155
|
+
paramsSent: Object.keys(paramsSent).length > 0 ? paramsSent : void 0
|
|
156
|
+
};
|
|
157
|
+
} catch (err) {
|
|
158
|
+
return errorResult(`append`, err);
|
|
159
|
+
}
|
|
160
|
+
case `read`: try {
|
|
161
|
+
const url = `${serverUrl}${command.path}`;
|
|
162
|
+
const { headers: dynamicHdrs, values: headersSent } = resolveDynamicHeaders();
|
|
163
|
+
const { values: paramsSent } = resolveDynamicParams();
|
|
164
|
+
const mergedHeaders = {
|
|
165
|
+
...dynamicHdrs,
|
|
166
|
+
...command.headers
|
|
167
|
+
};
|
|
168
|
+
let live;
|
|
169
|
+
if (command.live === `long-poll`) live = `long-poll`;
|
|
170
|
+
else if (command.live === `sse`) live = `sse`;
|
|
171
|
+
else live = false;
|
|
172
|
+
const abortController = new AbortController();
|
|
173
|
+
const timeoutMs = command.timeoutMs ?? 5e3;
|
|
174
|
+
const timeoutId = setTimeout(() => {
|
|
175
|
+
abortController.abort();
|
|
176
|
+
}, timeoutMs);
|
|
177
|
+
let response;
|
|
178
|
+
try {
|
|
179
|
+
response = await (0, __durable_streams_client.stream)({
|
|
180
|
+
url,
|
|
181
|
+
offset: command.offset,
|
|
182
|
+
live,
|
|
183
|
+
headers: mergedHeaders,
|
|
184
|
+
signal: abortController.signal
|
|
185
|
+
});
|
|
186
|
+
} catch (err) {
|
|
187
|
+
clearTimeout(timeoutId);
|
|
188
|
+
if (abortController.signal.aborted) return {
|
|
189
|
+
type: `read`,
|
|
190
|
+
success: true,
|
|
191
|
+
status: 200,
|
|
192
|
+
chunks: [],
|
|
193
|
+
offset: command.offset ?? `-1`,
|
|
194
|
+
upToDate: true,
|
|
195
|
+
headersSent: Object.keys(headersSent).length > 0 ? headersSent : void 0,
|
|
196
|
+
paramsSent: Object.keys(paramsSent).length > 0 ? paramsSent : void 0
|
|
197
|
+
};
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
clearTimeout(timeoutId);
|
|
201
|
+
const chunks = [];
|
|
202
|
+
let finalOffset = command.offset ?? response.offset;
|
|
203
|
+
let upToDate = response.upToDate;
|
|
204
|
+
const maxChunks = command.maxChunks ?? 100;
|
|
205
|
+
if (!live) try {
|
|
206
|
+
const data = await response.body();
|
|
207
|
+
if (data.length > 0) chunks.push({
|
|
208
|
+
data: new TextDecoder().decode(data),
|
|
209
|
+
offset: response.offset
|
|
210
|
+
});
|
|
211
|
+
finalOffset = response.offset;
|
|
212
|
+
upToDate = response.upToDate;
|
|
213
|
+
} catch {}
|
|
214
|
+
else {
|
|
215
|
+
const decoder = new TextDecoder();
|
|
216
|
+
const startTime = Date.now();
|
|
217
|
+
let chunkCount = 0;
|
|
218
|
+
let done = false;
|
|
219
|
+
await new Promise((resolve) => {
|
|
220
|
+
const subscriptionTimeoutId = setTimeout(() => {
|
|
221
|
+
done = true;
|
|
222
|
+
abortController.abort();
|
|
223
|
+
upToDate = response.upToDate || true;
|
|
224
|
+
finalOffset = response.offset;
|
|
225
|
+
resolve();
|
|
226
|
+
}, timeoutMs);
|
|
227
|
+
const unsubscribe = response.subscribeBytes(async (chunk) => {
|
|
228
|
+
if (done || chunkCount >= maxChunks) return;
|
|
229
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
230
|
+
done = true;
|
|
231
|
+
resolve();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const hasData = chunk.data.length > 0;
|
|
235
|
+
if (hasData) {
|
|
236
|
+
chunks.push({
|
|
237
|
+
data: decoder.decode(chunk.data),
|
|
238
|
+
offset: chunk.offset
|
|
239
|
+
});
|
|
240
|
+
chunkCount++;
|
|
241
|
+
}
|
|
242
|
+
finalOffset = chunk.offset;
|
|
243
|
+
upToDate = chunk.upToDate;
|
|
244
|
+
if (command.waitForUpToDate && chunk.upToDate) {
|
|
245
|
+
done = true;
|
|
246
|
+
clearTimeout(subscriptionTimeoutId);
|
|
247
|
+
resolve();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (chunkCount >= maxChunks) {
|
|
251
|
+
done = true;
|
|
252
|
+
clearTimeout(subscriptionTimeoutId);
|
|
253
|
+
resolve();
|
|
254
|
+
}
|
|
255
|
+
await Promise.resolve();
|
|
256
|
+
});
|
|
257
|
+
response.closed.then(() => {
|
|
258
|
+
if (!done) {
|
|
259
|
+
done = true;
|
|
260
|
+
clearTimeout(subscriptionTimeoutId);
|
|
261
|
+
upToDate = response.upToDate;
|
|
262
|
+
finalOffset = response.offset;
|
|
263
|
+
resolve();
|
|
264
|
+
}
|
|
265
|
+
}).catch(() => {
|
|
266
|
+
if (!done) {
|
|
267
|
+
done = true;
|
|
268
|
+
clearTimeout(subscriptionTimeoutId);
|
|
269
|
+
resolve();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
response.cancel();
|
|
275
|
+
return {
|
|
276
|
+
type: `read`,
|
|
277
|
+
success: true,
|
|
278
|
+
status: 200,
|
|
279
|
+
chunks,
|
|
280
|
+
offset: finalOffset,
|
|
281
|
+
upToDate,
|
|
282
|
+
headersSent: Object.keys(headersSent).length > 0 ? headersSent : void 0,
|
|
283
|
+
paramsSent: Object.keys(paramsSent).length > 0 ? paramsSent : void 0
|
|
284
|
+
};
|
|
285
|
+
} catch (err) {
|
|
286
|
+
return errorResult(`read`, err);
|
|
287
|
+
}
|
|
288
|
+
case `head`: try {
|
|
289
|
+
const url = `${serverUrl}${command.path}`;
|
|
290
|
+
const result = await __durable_streams_client.DurableStream.head({
|
|
291
|
+
url,
|
|
292
|
+
headers: command.headers
|
|
293
|
+
});
|
|
294
|
+
if (result.contentType) streamContentTypes.set(command.path, result.contentType);
|
|
295
|
+
return {
|
|
296
|
+
type: `head`,
|
|
297
|
+
success: true,
|
|
298
|
+
status: 200,
|
|
299
|
+
offset: result.offset,
|
|
300
|
+
contentType: result.contentType
|
|
301
|
+
};
|
|
302
|
+
} catch (err) {
|
|
303
|
+
return errorResult(`head`, err);
|
|
304
|
+
}
|
|
305
|
+
case `delete`: try {
|
|
306
|
+
const url = `${serverUrl}${command.path}`;
|
|
307
|
+
await __durable_streams_client.DurableStream.delete({
|
|
308
|
+
url,
|
|
309
|
+
headers: command.headers
|
|
310
|
+
});
|
|
311
|
+
streamContentTypes.delete(command.path);
|
|
312
|
+
return {
|
|
313
|
+
type: `delete`,
|
|
314
|
+
success: true,
|
|
315
|
+
status: 200
|
|
316
|
+
};
|
|
317
|
+
} catch (err) {
|
|
318
|
+
return errorResult(`delete`, err);
|
|
319
|
+
}
|
|
320
|
+
case `shutdown`: return {
|
|
321
|
+
type: `shutdown`,
|
|
322
|
+
success: true
|
|
323
|
+
};
|
|
324
|
+
case `benchmark`: return handleBenchmark(command);
|
|
325
|
+
case `set-dynamic-header`: {
|
|
326
|
+
dynamicHeaders.set(command.name, {
|
|
327
|
+
type: command.valueType,
|
|
328
|
+
counter: 0,
|
|
329
|
+
tokenValue: command.initialValue
|
|
330
|
+
});
|
|
331
|
+
return {
|
|
332
|
+
type: `set-dynamic-header`,
|
|
333
|
+
success: true
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
case `set-dynamic-param`: {
|
|
337
|
+
dynamicParams.set(command.name, {
|
|
338
|
+
type: command.valueType,
|
|
339
|
+
counter: 0
|
|
340
|
+
});
|
|
341
|
+
return {
|
|
342
|
+
type: `set-dynamic-param`,
|
|
343
|
+
success: true
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
case `clear-dynamic`: {
|
|
347
|
+
dynamicHeaders.clear();
|
|
348
|
+
dynamicParams.clear();
|
|
349
|
+
return {
|
|
350
|
+
type: `clear-dynamic`,
|
|
351
|
+
success: true
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
default: return {
|
|
355
|
+
type: `error`,
|
|
356
|
+
success: false,
|
|
357
|
+
commandType: command.type,
|
|
358
|
+
errorCode: require_protocol.ErrorCodes.NOT_SUPPORTED,
|
|
359
|
+
message: `Unknown command type: ${command.type}`
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function errorResult(commandType, err) {
|
|
364
|
+
if (err instanceof __durable_streams_client.DurableStreamError) {
|
|
365
|
+
let errorCode = require_protocol.ErrorCodes.INTERNAL_ERROR;
|
|
366
|
+
let status;
|
|
367
|
+
if (err.code === `NOT_FOUND`) {
|
|
368
|
+
errorCode = require_protocol.ErrorCodes.NOT_FOUND;
|
|
369
|
+
status = 404;
|
|
370
|
+
} else if (err.code === `CONFLICT_EXISTS`) {
|
|
371
|
+
errorCode = require_protocol.ErrorCodes.CONFLICT;
|
|
372
|
+
status = 409;
|
|
373
|
+
} else if (err.code === `CONFLICT_SEQ`) {
|
|
374
|
+
errorCode = require_protocol.ErrorCodes.SEQUENCE_CONFLICT;
|
|
375
|
+
status = 409;
|
|
376
|
+
} else if (err.code === `BAD_REQUEST`) {
|
|
377
|
+
errorCode = require_protocol.ErrorCodes.INVALID_OFFSET;
|
|
378
|
+
status = 400;
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
type: `error`,
|
|
382
|
+
success: false,
|
|
383
|
+
commandType,
|
|
384
|
+
status,
|
|
385
|
+
errorCode,
|
|
386
|
+
message: err.message
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (err instanceof __durable_streams_client.FetchError) {
|
|
390
|
+
let errorCode;
|
|
391
|
+
const msg = err.message.toLowerCase();
|
|
392
|
+
if (err.status === 404) errorCode = require_protocol.ErrorCodes.NOT_FOUND;
|
|
393
|
+
else if (err.status === 409) if (msg.includes(`sequence`)) errorCode = require_protocol.ErrorCodes.SEQUENCE_CONFLICT;
|
|
394
|
+
else errorCode = require_protocol.ErrorCodes.CONFLICT;
|
|
395
|
+
else if (err.status === 400) if (msg.includes(`offset`) || msg.includes(`invalid`)) errorCode = require_protocol.ErrorCodes.INVALID_OFFSET;
|
|
396
|
+
else errorCode = require_protocol.ErrorCodes.UNEXPECTED_STATUS;
|
|
397
|
+
else errorCode = require_protocol.ErrorCodes.UNEXPECTED_STATUS;
|
|
398
|
+
return {
|
|
399
|
+
type: `error`,
|
|
400
|
+
success: false,
|
|
401
|
+
commandType,
|
|
402
|
+
status: err.status,
|
|
403
|
+
errorCode,
|
|
404
|
+
message: err.message
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (err instanceof Error) {
|
|
408
|
+
if (err.message.includes(`ECONNREFUSED`) || err.message.includes(`fetch`)) return {
|
|
409
|
+
type: `error`,
|
|
410
|
+
success: false,
|
|
411
|
+
commandType,
|
|
412
|
+
errorCode: require_protocol.ErrorCodes.NETWORK_ERROR,
|
|
413
|
+
message: err.message
|
|
414
|
+
};
|
|
415
|
+
return {
|
|
416
|
+
type: `error`,
|
|
417
|
+
success: false,
|
|
418
|
+
commandType,
|
|
419
|
+
errorCode: require_protocol.ErrorCodes.INTERNAL_ERROR,
|
|
420
|
+
message: err.message
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
type: `error`,
|
|
425
|
+
success: false,
|
|
426
|
+
commandType,
|
|
427
|
+
errorCode: require_protocol.ErrorCodes.INTERNAL_ERROR,
|
|
428
|
+
message: String(err)
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Handle benchmark commands with high-resolution timing.
|
|
433
|
+
*/
|
|
434
|
+
async function handleBenchmark(command) {
|
|
435
|
+
const { iterationId, operation } = command;
|
|
436
|
+
try {
|
|
437
|
+
const startTime = process.hrtime.bigint();
|
|
438
|
+
const metrics = {};
|
|
439
|
+
switch (operation.op) {
|
|
440
|
+
case `append`: {
|
|
441
|
+
const url = `${serverUrl}${operation.path}`;
|
|
442
|
+
const contentType = streamContentTypes.get(operation.path) ?? `application/octet-stream`;
|
|
443
|
+
const ds = new __durable_streams_client.DurableStream({
|
|
444
|
+
url,
|
|
445
|
+
contentType
|
|
446
|
+
});
|
|
447
|
+
const payload = new Uint8Array(operation.size).fill(42);
|
|
448
|
+
await ds.append(payload);
|
|
449
|
+
metrics.bytesTransferred = operation.size;
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case `read`: {
|
|
453
|
+
const url = `${serverUrl}${operation.path}`;
|
|
454
|
+
const res = await (0, __durable_streams_client.stream)({
|
|
455
|
+
url,
|
|
456
|
+
offset: operation.offset,
|
|
457
|
+
live: false
|
|
458
|
+
});
|
|
459
|
+
const data = await res.body();
|
|
460
|
+
metrics.bytesTransferred = data.length;
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case `roundtrip`: {
|
|
464
|
+
const url = `${serverUrl}${operation.path}`;
|
|
465
|
+
const contentType = operation.contentType ?? `application/octet-stream`;
|
|
466
|
+
const ds = await __durable_streams_client.DurableStream.create({
|
|
467
|
+
url,
|
|
468
|
+
contentType
|
|
469
|
+
});
|
|
470
|
+
const payload = new Uint8Array(operation.size).fill(42);
|
|
471
|
+
const readPromise = (async () => {
|
|
472
|
+
const res = await ds.stream({ live: operation.live ?? `long-poll` });
|
|
473
|
+
return new Promise((resolve) => {
|
|
474
|
+
const unsubscribe = res.subscribeBytes(async (chunk) => {
|
|
475
|
+
if (chunk.data.length > 0) {
|
|
476
|
+
unsubscribe();
|
|
477
|
+
res.cancel();
|
|
478
|
+
resolve(chunk.data);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
})();
|
|
483
|
+
await ds.append(payload);
|
|
484
|
+
const readData = await readPromise;
|
|
485
|
+
metrics.bytesTransferred = operation.size + readData.length;
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
case `create`: {
|
|
489
|
+
const url = `${serverUrl}${operation.path}`;
|
|
490
|
+
await __durable_streams_client.DurableStream.create({
|
|
491
|
+
url,
|
|
492
|
+
contentType: operation.contentType ?? `application/octet-stream`
|
|
493
|
+
});
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
case `throughput_append`: {
|
|
497
|
+
const url = `${serverUrl}${operation.path}`;
|
|
498
|
+
const contentType = streamContentTypes.get(operation.path) ?? `application/octet-stream`;
|
|
499
|
+
try {
|
|
500
|
+
await __durable_streams_client.DurableStream.create({
|
|
501
|
+
url,
|
|
502
|
+
contentType
|
|
503
|
+
});
|
|
504
|
+
} catch {}
|
|
505
|
+
const ds = new __durable_streams_client.DurableStream({
|
|
506
|
+
url,
|
|
507
|
+
contentType
|
|
508
|
+
});
|
|
509
|
+
const payload = new Uint8Array(operation.size).fill(42);
|
|
510
|
+
await Promise.all(Array.from({ length: operation.count }, () => ds.append(payload)));
|
|
511
|
+
metrics.bytesTransferred = operation.count * operation.size;
|
|
512
|
+
metrics.messagesProcessed = operation.count;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case `throughput_read`: {
|
|
516
|
+
const url = `${serverUrl}${operation.path}`;
|
|
517
|
+
const res = await (0, __durable_streams_client.stream)({
|
|
518
|
+
url,
|
|
519
|
+
live: false
|
|
520
|
+
});
|
|
521
|
+
let count = 0;
|
|
522
|
+
let bytes = 0;
|
|
523
|
+
for await (const msg of res.jsonStream()) {
|
|
524
|
+
count++;
|
|
525
|
+
bytes += JSON.stringify(msg).length;
|
|
526
|
+
}
|
|
527
|
+
metrics.bytesTransferred = bytes;
|
|
528
|
+
metrics.messagesProcessed = count;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
default: return {
|
|
532
|
+
type: `error`,
|
|
533
|
+
success: false,
|
|
534
|
+
commandType: `benchmark`,
|
|
535
|
+
errorCode: require_protocol.ErrorCodes.NOT_SUPPORTED,
|
|
536
|
+
message: `Unknown benchmark operation: ${operation.op}`
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
const endTime = process.hrtime.bigint();
|
|
540
|
+
const durationNs = endTime - startTime;
|
|
541
|
+
return {
|
|
542
|
+
type: `benchmark`,
|
|
543
|
+
success: true,
|
|
544
|
+
iterationId,
|
|
545
|
+
durationNs: durationNs.toString(),
|
|
546
|
+
metrics
|
|
547
|
+
};
|
|
548
|
+
} catch (err) {
|
|
549
|
+
return {
|
|
550
|
+
type: `error`,
|
|
551
|
+
success: false,
|
|
552
|
+
commandType: `benchmark`,
|
|
553
|
+
errorCode: require_protocol.ErrorCodes.INTERNAL_ERROR,
|
|
554
|
+
message: err instanceof Error ? err.message : String(err)
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function main() {
|
|
559
|
+
const rl = (0, node_readline.createInterface)({
|
|
560
|
+
input: process.stdin,
|
|
561
|
+
output: process.stdout,
|
|
562
|
+
terminal: false
|
|
563
|
+
});
|
|
564
|
+
for await (const line of rl) {
|
|
565
|
+
if (!line.trim()) continue;
|
|
566
|
+
try {
|
|
567
|
+
const command = require_protocol.parseCommand(line);
|
|
568
|
+
const result = await handleCommand(command);
|
|
569
|
+
console.log(require_protocol.serializeResult(result));
|
|
570
|
+
if (command.type === `shutdown`) break;
|
|
571
|
+
} catch (err) {
|
|
572
|
+
console.log(require_protocol.serializeResult({
|
|
573
|
+
type: `error`,
|
|
574
|
+
success: false,
|
|
575
|
+
commandType: `init`,
|
|
576
|
+
errorCode: require_protocol.ErrorCodes.PARSE_ERROR,
|
|
577
|
+
message: `Failed to parse command: ${err}`
|
|
578
|
+
}));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
process.exit(0);
|
|
582
|
+
}
|
|
583
|
+
main().catch((err) => {
|
|
584
|
+
console.error(`Fatal error:`, err);
|
|
585
|
+
process.exit(1);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
File without changes
|