@durable-streams/client-conformance-tests 0.1.6 → 0.1.7

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.
Files changed (29) hide show
  1. package/dist/adapters/typescript-adapter.cjs +75 -3
  2. package/dist/adapters/typescript-adapter.js +76 -4
  3. package/dist/{benchmark-runner-D-YSAvRy.js → benchmark-runner-CrE6JkbX.js} +86 -8
  4. package/dist/{benchmark-runner-BlKqhoXE.cjs → benchmark-runner-Db4he452.cjs} +87 -8
  5. package/dist/cli.cjs +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +106 -6
  9. package/dist/index.d.ts +106 -6
  10. package/dist/index.js +1 -1
  11. package/dist/{protocol-3cf94Xyb.d.cts → protocol-D37G3c4e.d.cts} +80 -4
  12. package/dist/{protocol-DyEvTHPF.d.ts → protocol-Mcbiq3nQ.d.ts} +80 -4
  13. package/dist/protocol.d.cts +2 -2
  14. package/dist/protocol.d.ts +2 -2
  15. package/package.json +3 -3
  16. package/src/adapters/typescript-adapter.ts +127 -6
  17. package/src/protocol.ts +85 -1
  18. package/src/runner.ts +178 -13
  19. package/src/test-cases.ts +110 -3
  20. package/test-cases/consumer/error-handling.yaml +42 -0
  21. package/test-cases/consumer/offset-handling.yaml +209 -0
  22. package/test-cases/producer/idempotent/autoclaim.yaml +214 -0
  23. package/test-cases/producer/idempotent/batching.yaml +98 -0
  24. package/test-cases/producer/idempotent/concurrent-requests.yaml +100 -0
  25. package/test-cases/producer/idempotent/epoch-management.yaml +333 -0
  26. package/test-cases/producer/idempotent/error-handling.yaml +194 -0
  27. package/test-cases/producer/idempotent/multi-producer.yaml +322 -0
  28. package/test-cases/producer/idempotent/sequence-validation.yaml +339 -0
  29. package/test-cases/producer/idempotent-json-batching.yaml +134 -0
@@ -275,7 +275,7 @@ async function handleCommand(command) {
275
275
  return {
276
276
  type: `read`,
277
277
  success: true,
278
- status: 200,
278
+ status: response.status,
279
279
  chunks,
280
280
  offset: finalOffset,
281
281
  upToDate,
@@ -351,6 +351,73 @@ async function handleCommand(command) {
351
351
  success: true
352
352
  };
353
353
  }
354
+ case `idempotent-append`: try {
355
+ const url = `${serverUrl}${command.path}`;
356
+ const contentType = streamContentTypes.get(command.path) ?? `application/octet-stream`;
357
+ const ds = new __durable_streams_client.DurableStream({
358
+ url,
359
+ contentType
360
+ });
361
+ const producer = new __durable_streams_client.IdempotentProducer(ds, command.producerId, {
362
+ epoch: command.epoch,
363
+ autoClaim: command.autoClaim,
364
+ maxInFlight: 1,
365
+ lingerMs: 0
366
+ });
367
+ const normalizedContentType = contentType.split(`;`)[0]?.trim().toLowerCase();
368
+ const isJson = normalizedContentType === `application/json`;
369
+ const data = isJson ? JSON.parse(command.data) : command.data;
370
+ try {
371
+ producer.append(data);
372
+ await producer.flush();
373
+ await producer.close();
374
+ return {
375
+ type: `idempotent-append`,
376
+ success: true,
377
+ status: 200
378
+ };
379
+ } catch (err) {
380
+ await producer.close();
381
+ throw err;
382
+ }
383
+ } catch (err) {
384
+ return errorResult(`idempotent-append`, err);
385
+ }
386
+ case `idempotent-append-batch`: try {
387
+ const url = `${serverUrl}${command.path}`;
388
+ const contentType = streamContentTypes.get(command.path) ?? `application/octet-stream`;
389
+ const ds = new __durable_streams_client.DurableStream({
390
+ url,
391
+ contentType
392
+ });
393
+ const maxInFlight = command.maxInFlight ?? 1;
394
+ const testingConcurrency = maxInFlight > 1;
395
+ const producer = new __durable_streams_client.IdempotentProducer(ds, command.producerId, {
396
+ epoch: command.epoch,
397
+ autoClaim: command.autoClaim,
398
+ maxInFlight,
399
+ lingerMs: testingConcurrency ? 0 : 1e3,
400
+ maxBatchBytes: testingConcurrency ? 1 : 1024 * 1024
401
+ });
402
+ const normalizedContentType = contentType.split(`;`)[0]?.trim().toLowerCase();
403
+ const isJson = normalizedContentType === `application/json`;
404
+ const items = isJson ? command.items.map((item) => JSON.parse(item)) : command.items;
405
+ try {
406
+ for (const item of items) producer.append(item);
407
+ await producer.flush();
408
+ await producer.close();
409
+ return {
410
+ type: `idempotent-append-batch`,
411
+ success: true,
412
+ status: 200
413
+ };
414
+ } catch (err) {
415
+ await producer.close();
416
+ throw err;
417
+ }
418
+ } catch (err) {
419
+ return errorResult(`idempotent-append-batch`, err);
420
+ }
354
421
  default: return {
355
422
  type: `error`,
356
423
  success: false,
@@ -471,7 +538,7 @@ async function handleBenchmark(command) {
471
538
  const readPromise = (async () => {
472
539
  const res = await ds.stream({ live: operation.live ?? `long-poll` });
473
540
  return new Promise((resolve) => {
474
- const unsubscribe = res.subscribeBytes((chunk) => {
541
+ const unsubscribe = res.subscribeBytes(async (chunk) => {
475
542
  if (chunk.data.length > 0) {
476
543
  unsubscribe();
477
544
  res.cancel();
@@ -508,7 +575,12 @@ async function handleBenchmark(command) {
508
575
  contentType
509
576
  });
510
577
  const payload = new Uint8Array(operation.size).fill(42);
511
- await Promise.all(Array.from({ length: operation.count }, () => ds.append(payload)));
578
+ const producer = new __durable_streams_client.IdempotentProducer(ds, `bench-producer`, {
579
+ lingerMs: 0,
580
+ onError: (err) => console.error(`Batch failed:`, err)
581
+ });
582
+ for (let i = 0; i < operation.count; i++) producer.append(payload);
583
+ await producer.flush();
512
584
  metrics.bytesTransferred = operation.count * operation.size;
513
585
  metrics.messagesProcessed = operation.count;
514
586
  break;
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { ErrorCodes, decodeBase64, parseCommand, serializeResult } from "../protocol-qb83AeUH.js";
3
3
  import { createInterface } from "node:readline";
4
- import { DurableStream, DurableStreamError, FetchError, stream } from "@durable-streams/client";
4
+ import { DurableStream, DurableStreamError, FetchError, IdempotentProducer, stream } from "@durable-streams/client";
5
5
 
6
6
  //#region src/adapters/typescript-adapter.ts
7
7
  const CLIENT_VERSION = `0.0.1`;
@@ -273,7 +273,7 @@ async function handleCommand(command) {
273
273
  return {
274
274
  type: `read`,
275
275
  success: true,
276
- status: 200,
276
+ status: response.status,
277
277
  chunks,
278
278
  offset: finalOffset,
279
279
  upToDate,
@@ -349,6 +349,73 @@ async function handleCommand(command) {
349
349
  success: true
350
350
  };
351
351
  }
352
+ case `idempotent-append`: try {
353
+ const url = `${serverUrl}${command.path}`;
354
+ const contentType = streamContentTypes.get(command.path) ?? `application/octet-stream`;
355
+ const ds = new DurableStream({
356
+ url,
357
+ contentType
358
+ });
359
+ const producer = new IdempotentProducer(ds, command.producerId, {
360
+ epoch: command.epoch,
361
+ autoClaim: command.autoClaim,
362
+ maxInFlight: 1,
363
+ lingerMs: 0
364
+ });
365
+ const normalizedContentType = contentType.split(`;`)[0]?.trim().toLowerCase();
366
+ const isJson = normalizedContentType === `application/json`;
367
+ const data = isJson ? JSON.parse(command.data) : command.data;
368
+ try {
369
+ producer.append(data);
370
+ await producer.flush();
371
+ await producer.close();
372
+ return {
373
+ type: `idempotent-append`,
374
+ success: true,
375
+ status: 200
376
+ };
377
+ } catch (err) {
378
+ await producer.close();
379
+ throw err;
380
+ }
381
+ } catch (err) {
382
+ return errorResult(`idempotent-append`, err);
383
+ }
384
+ case `idempotent-append-batch`: try {
385
+ const url = `${serverUrl}${command.path}`;
386
+ const contentType = streamContentTypes.get(command.path) ?? `application/octet-stream`;
387
+ const ds = new DurableStream({
388
+ url,
389
+ contentType
390
+ });
391
+ const maxInFlight = command.maxInFlight ?? 1;
392
+ const testingConcurrency = maxInFlight > 1;
393
+ const producer = new IdempotentProducer(ds, command.producerId, {
394
+ epoch: command.epoch,
395
+ autoClaim: command.autoClaim,
396
+ maxInFlight,
397
+ lingerMs: testingConcurrency ? 0 : 1e3,
398
+ maxBatchBytes: testingConcurrency ? 1 : 1024 * 1024
399
+ });
400
+ const normalizedContentType = contentType.split(`;`)[0]?.trim().toLowerCase();
401
+ const isJson = normalizedContentType === `application/json`;
402
+ const items = isJson ? command.items.map((item) => JSON.parse(item)) : command.items;
403
+ try {
404
+ for (const item of items) producer.append(item);
405
+ await producer.flush();
406
+ await producer.close();
407
+ return {
408
+ type: `idempotent-append-batch`,
409
+ success: true,
410
+ status: 200
411
+ };
412
+ } catch (err) {
413
+ await producer.close();
414
+ throw err;
415
+ }
416
+ } catch (err) {
417
+ return errorResult(`idempotent-append-batch`, err);
418
+ }
352
419
  default: return {
353
420
  type: `error`,
354
421
  success: false,
@@ -469,7 +536,7 @@ async function handleBenchmark(command) {
469
536
  const readPromise = (async () => {
470
537
  const res = await ds.stream({ live: operation.live ?? `long-poll` });
471
538
  return new Promise((resolve) => {
472
- const unsubscribe = res.subscribeBytes((chunk) => {
539
+ const unsubscribe = res.subscribeBytes(async (chunk) => {
473
540
  if (chunk.data.length > 0) {
474
541
  unsubscribe();
475
542
  res.cancel();
@@ -506,7 +573,12 @@ async function handleBenchmark(command) {
506
573
  contentType
507
574
  });
508
575
  const payload = new Uint8Array(operation.size).fill(42);
509
- await Promise.all(Array.from({ length: operation.count }, () => ds.append(payload)));
576
+ const producer = new IdempotentProducer(ds, `bench-producer`, {
577
+ lingerMs: 0,
578
+ onError: (err) => console.error(`Batch failed:`, err)
579
+ });
580
+ for (let i = 0; i < operation.count; i++) producer.append(payload);
581
+ await producer.flush();
510
582
  metrics.bytesTransferred = operation.count * operation.size;
511
583
  metrics.messagesProcessed = operation.count;
512
584
  break;
@@ -214,6 +214,38 @@ async function executeOperation(op, ctx) {
214
214
  status: allSucceeded ? 200 : 207
215
215
  } };
216
216
  }
217
+ case `idempotent-append`: {
218
+ const path$1 = resolveVariables(op.path, variables);
219
+ const data = resolveVariables(op.data, variables);
220
+ const result = await client.send({
221
+ type: `idempotent-append`,
222
+ path: path$1,
223
+ data,
224
+ producerId: op.producerId,
225
+ epoch: op.epoch ?? 0,
226
+ autoClaim: op.autoClaim ?? false,
227
+ headers: op.headers
228
+ }, commandTimeout);
229
+ if (verbose) console.log(` idempotent-append ${path$1}: ${result.success ? `ok` : `failed`}`);
230
+ if (result.success && result.type === `idempotent-append` && op.expect?.storeOffsetAs) variables.set(op.expect.storeOffsetAs, result.offset ?? ``);
231
+ return { result };
232
+ }
233
+ case `idempotent-append-batch`: {
234
+ const path$1 = resolveVariables(op.path, variables);
235
+ const items = op.items.map((item) => resolveVariables(item.data, variables));
236
+ const result = await client.send({
237
+ type: `idempotent-append-batch`,
238
+ path: path$1,
239
+ items,
240
+ producerId: op.producerId,
241
+ epoch: op.epoch ?? 0,
242
+ autoClaim: op.autoClaim ?? false,
243
+ maxInFlight: op.maxInFlight,
244
+ headers: op.headers
245
+ }, commandTimeout);
246
+ if (verbose) console.log(` idempotent-append-batch ${path$1}: ${result.success ? `ok` : `failed`}`);
247
+ return { result };
248
+ }
217
249
  case `read`: {
218
250
  const path$1 = resolveVariables(op.path, variables);
219
251
  const offset = op.offset ? resolveVariables(op.offset, variables) : void 0;
@@ -316,17 +348,39 @@ async function executeOperation(op, ctx) {
316
348
  try {
317
349
  const headResponse = await fetch(`${ctx.serverUrl}${path$1}`, { method: `HEAD` });
318
350
  const contentType = headResponse.headers.get(`content-type`) ?? `application/octet-stream`;
351
+ const headers = {
352
+ "content-type": contentType,
353
+ ...op.headers
354
+ };
355
+ if (op.producerId !== void 0) headers[`Producer-Id`] = op.producerId;
356
+ if (op.producerEpoch !== void 0) headers[`Producer-Epoch`] = op.producerEpoch.toString();
357
+ if (op.producerSeq !== void 0) headers[`Producer-Seq`] = op.producerSeq.toString();
319
358
  const response = await fetch(`${ctx.serverUrl}${path$1}`, {
320
359
  method: `POST`,
321
360
  body: data,
322
- headers: {
323
- "content-type": contentType,
324
- ...op.headers
325
- }
361
+ headers
326
362
  });
327
- if (verbose) console.log(` server-append ${path$1}: ${response.ok ? `ok` : `failed (${response.status})`}`);
328
- if (!response.ok) return { error: `Server append failed with status ${response.status}` };
329
- return {};
363
+ const status = response.status;
364
+ const offset = response.headers.get(`Stream-Next-Offset`) ?? void 0;
365
+ const duplicate = status === 204;
366
+ const producerEpoch = response.headers.get(`Producer-Epoch`);
367
+ const producerSeq = response.headers.get(`Producer-Seq`);
368
+ const producerExpectedSeq = response.headers.get(`Producer-Expected-Seq`);
369
+ const producerReceivedSeq = response.headers.get(`Producer-Received-Seq`);
370
+ if (verbose) console.log(` server-append ${path$1}: status=${status}${duplicate ? ` (duplicate)` : ``}`);
371
+ const result = {
372
+ type: `append`,
373
+ success: true,
374
+ status,
375
+ offset,
376
+ duplicate,
377
+ producerEpoch: producerEpoch ? parseInt(producerEpoch, 10) : void 0,
378
+ producerSeq: producerSeq ? parseInt(producerSeq, 10) : void 0,
379
+ producerExpectedSeq: producerExpectedSeq ? parseInt(producerExpectedSeq, 10) : void 0,
380
+ producerReceivedSeq: producerReceivedSeq ? parseInt(producerReceivedSeq, 10) : void 0
381
+ };
382
+ if (op.expect?.storeOffsetAs && offset) variables.set(op.expect.storeOffsetAs, offset);
383
+ return { result };
330
384
  } catch (err) {
331
385
  return { error: `Server append failed: ${err instanceof Error ? err.message : String(err)}` };
332
386
  }
@@ -444,6 +498,12 @@ function validateExpectation(result, expect) {
444
498
  const missing = expect.dataContainsAll.filter((s) => !actualData.includes(s));
445
499
  if (missing.length > 0) return `Expected data to contain all of [${expect.dataContainsAll.join(`, `)}], missing: [${missing.join(`, `)}]`;
446
500
  }
501
+ if (expect.dataExact !== void 0 && isReadResult(result)) {
502
+ const expectedMessages = expect.dataExact;
503
+ const actualMessages = result.chunks.map((c) => c.data);
504
+ if (actualMessages.length !== expectedMessages.length) return `Expected ${expectedMessages.length} messages, got ${actualMessages.length}. Expected: [${expectedMessages.join(`, `)}], got: [${actualMessages.join(`, `)}]`;
505
+ for (let i = 0; i < expectedMessages.length; i++) if (actualMessages[i] !== expectedMessages[i]) return `Message ${i} mismatch: expected "${expectedMessages[i]}", got "${actualMessages[i]}"`;
506
+ }
447
507
  if (expect.upToDate !== void 0 && isReadResult(result)) {
448
508
  if (result.upToDate !== expect.upToDate) return `Expected upToDate=${expect.upToDate}, got ${result.upToDate}`;
449
509
  }
@@ -482,6 +542,21 @@ function validateExpectation(result, expect) {
482
542
  if (actualValue !== expectedValue) return `Expected paramsSent[${key}]="${expectedValue}", got "${actualValue ?? `undefined`}"`;
483
543
  }
484
544
  }
545
+ if (expect.duplicate !== void 0 && isAppendResult(result)) {
546
+ if (result.duplicate !== expect.duplicate) return `Expected duplicate=${expect.duplicate}, got ${result.duplicate}`;
547
+ }
548
+ if (expect.producerEpoch !== void 0 && isAppendResult(result)) {
549
+ if (result.producerEpoch !== expect.producerEpoch) return `Expected producerEpoch=${expect.producerEpoch}, got ${result.producerEpoch}`;
550
+ }
551
+ if (expect.producerSeq !== void 0 && isAppendResult(result)) {
552
+ if (result.producerSeq !== expect.producerSeq) return `Expected producerSeq=${expect.producerSeq}, got ${result.producerSeq}`;
553
+ }
554
+ if (expect.producerExpectedSeq !== void 0 && isAppendResult(result)) {
555
+ if (result.producerExpectedSeq !== expect.producerExpectedSeq) return `Expected producerExpectedSeq=${expect.producerExpectedSeq}, got ${result.producerExpectedSeq}`;
556
+ }
557
+ if (expect.producerReceivedSeq !== void 0 && isAppendResult(result)) {
558
+ if (result.producerReceivedSeq !== expect.producerReceivedSeq) return `Expected producerReceivedSeq=${expect.producerReceivedSeq}, got ${result.producerReceivedSeq}`;
559
+ }
485
560
  return null;
486
561
  }
487
562
  /**
@@ -592,7 +667,10 @@ async function runConformanceTests(options) {
592
667
  })).filter((suite) => suite.tests.length > 0);
593
668
  const totalTests = countTests(suites);
594
669
  console.log(`\nRunning ${totalTests} client conformance tests...\n`);
595
- const server = new DurableStreamTestServer({ port: options.serverPort ?? 0 });
670
+ const server = new DurableStreamTestServer({
671
+ port: options.serverPort ?? 0,
672
+ longPollTimeout: 500
673
+ });
596
674
  await server.start();
597
675
  const serverUrl = server.url;
598
676
  console.log(`Reference server started at ${serverUrl}\n`);
@@ -216,6 +216,38 @@ async function executeOperation(op, ctx) {
216
216
  status: allSucceeded ? 200 : 207
217
217
  } };
218
218
  }
219
+ case `idempotent-append`: {
220
+ const path = resolveVariables(op.path, variables);
221
+ const data = resolveVariables(op.data, variables);
222
+ const result = await client.send({
223
+ type: `idempotent-append`,
224
+ path,
225
+ data,
226
+ producerId: op.producerId,
227
+ epoch: op.epoch ?? 0,
228
+ autoClaim: op.autoClaim ?? false,
229
+ headers: op.headers
230
+ }, commandTimeout);
231
+ if (verbose) console.log(` idempotent-append ${path}: ${result.success ? `ok` : `failed`}`);
232
+ if (result.success && result.type === `idempotent-append` && op.expect?.storeOffsetAs) variables.set(op.expect.storeOffsetAs, result.offset ?? ``);
233
+ return { result };
234
+ }
235
+ case `idempotent-append-batch`: {
236
+ const path = resolveVariables(op.path, variables);
237
+ const items = op.items.map((item) => resolveVariables(item.data, variables));
238
+ const result = await client.send({
239
+ type: `idempotent-append-batch`,
240
+ path,
241
+ items,
242
+ producerId: op.producerId,
243
+ epoch: op.epoch ?? 0,
244
+ autoClaim: op.autoClaim ?? false,
245
+ maxInFlight: op.maxInFlight,
246
+ headers: op.headers
247
+ }, commandTimeout);
248
+ if (verbose) console.log(` idempotent-append-batch ${path}: ${result.success ? `ok` : `failed`}`);
249
+ return { result };
250
+ }
219
251
  case `read`: {
220
252
  const path = resolveVariables(op.path, variables);
221
253
  const offset = op.offset ? resolveVariables(op.offset, variables) : void 0;
@@ -318,17 +350,39 @@ async function executeOperation(op, ctx) {
318
350
  try {
319
351
  const headResponse = await fetch(`${ctx.serverUrl}${path}`, { method: `HEAD` });
320
352
  const contentType = headResponse.headers.get(`content-type`) ?? `application/octet-stream`;
353
+ const headers = {
354
+ "content-type": contentType,
355
+ ...op.headers
356
+ };
357
+ if (op.producerId !== void 0) headers[`Producer-Id`] = op.producerId;
358
+ if (op.producerEpoch !== void 0) headers[`Producer-Epoch`] = op.producerEpoch.toString();
359
+ if (op.producerSeq !== void 0) headers[`Producer-Seq`] = op.producerSeq.toString();
321
360
  const response = await fetch(`${ctx.serverUrl}${path}`, {
322
361
  method: `POST`,
323
362
  body: data,
324
- headers: {
325
- "content-type": contentType,
326
- ...op.headers
327
- }
363
+ headers
328
364
  });
329
- if (verbose) console.log(` server-append ${path}: ${response.ok ? `ok` : `failed (${response.status})`}`);
330
- if (!response.ok) return { error: `Server append failed with status ${response.status}` };
331
- return {};
365
+ const status = response.status;
366
+ const offset = response.headers.get(`Stream-Next-Offset`) ?? void 0;
367
+ const duplicate = status === 204;
368
+ const producerEpoch = response.headers.get(`Producer-Epoch`);
369
+ const producerSeq = response.headers.get(`Producer-Seq`);
370
+ const producerExpectedSeq = response.headers.get(`Producer-Expected-Seq`);
371
+ const producerReceivedSeq = response.headers.get(`Producer-Received-Seq`);
372
+ if (verbose) console.log(` server-append ${path}: status=${status}${duplicate ? ` (duplicate)` : ``}`);
373
+ const result = {
374
+ type: `append`,
375
+ success: true,
376
+ status,
377
+ offset,
378
+ duplicate,
379
+ producerEpoch: producerEpoch ? parseInt(producerEpoch, 10) : void 0,
380
+ producerSeq: producerSeq ? parseInt(producerSeq, 10) : void 0,
381
+ producerExpectedSeq: producerExpectedSeq ? parseInt(producerExpectedSeq, 10) : void 0,
382
+ producerReceivedSeq: producerReceivedSeq ? parseInt(producerReceivedSeq, 10) : void 0
383
+ };
384
+ if (op.expect?.storeOffsetAs && offset) variables.set(op.expect.storeOffsetAs, offset);
385
+ return { result };
332
386
  } catch (err) {
333
387
  return { error: `Server append failed: ${err instanceof Error ? err.message : String(err)}` };
334
388
  }
@@ -446,6 +500,12 @@ function validateExpectation(result, expect) {
446
500
  const missing = expect.dataContainsAll.filter((s) => !actualData.includes(s));
447
501
  if (missing.length > 0) return `Expected data to contain all of [${expect.dataContainsAll.join(`, `)}], missing: [${missing.join(`, `)}]`;
448
502
  }
503
+ if (expect.dataExact !== void 0 && isReadResult(result)) {
504
+ const expectedMessages = expect.dataExact;
505
+ const actualMessages = result.chunks.map((c) => c.data);
506
+ if (actualMessages.length !== expectedMessages.length) return `Expected ${expectedMessages.length} messages, got ${actualMessages.length}. Expected: [${expectedMessages.join(`, `)}], got: [${actualMessages.join(`, `)}]`;
507
+ for (let i = 0; i < expectedMessages.length; i++) if (actualMessages[i] !== expectedMessages[i]) return `Message ${i} mismatch: expected "${expectedMessages[i]}", got "${actualMessages[i]}"`;
508
+ }
449
509
  if (expect.upToDate !== void 0 && isReadResult(result)) {
450
510
  if (result.upToDate !== expect.upToDate) return `Expected upToDate=${expect.upToDate}, got ${result.upToDate}`;
451
511
  }
@@ -484,6 +544,21 @@ function validateExpectation(result, expect) {
484
544
  if (actualValue !== expectedValue) return `Expected paramsSent[${key}]="${expectedValue}", got "${actualValue ?? `undefined`}"`;
485
545
  }
486
546
  }
547
+ if (expect.duplicate !== void 0 && isAppendResult(result)) {
548
+ if (result.duplicate !== expect.duplicate) return `Expected duplicate=${expect.duplicate}, got ${result.duplicate}`;
549
+ }
550
+ if (expect.producerEpoch !== void 0 && isAppendResult(result)) {
551
+ if (result.producerEpoch !== expect.producerEpoch) return `Expected producerEpoch=${expect.producerEpoch}, got ${result.producerEpoch}`;
552
+ }
553
+ if (expect.producerSeq !== void 0 && isAppendResult(result)) {
554
+ if (result.producerSeq !== expect.producerSeq) return `Expected producerSeq=${expect.producerSeq}, got ${result.producerSeq}`;
555
+ }
556
+ if (expect.producerExpectedSeq !== void 0 && isAppendResult(result)) {
557
+ if (result.producerExpectedSeq !== expect.producerExpectedSeq) return `Expected producerExpectedSeq=${expect.producerExpectedSeq}, got ${result.producerExpectedSeq}`;
558
+ }
559
+ if (expect.producerReceivedSeq !== void 0 && isAppendResult(result)) {
560
+ if (result.producerReceivedSeq !== expect.producerReceivedSeq) return `Expected producerReceivedSeq=${expect.producerReceivedSeq}, got ${result.producerReceivedSeq}`;
561
+ }
487
562
  return null;
488
563
  }
489
564
  /**
@@ -594,7 +669,10 @@ async function runConformanceTests(options) {
594
669
  })).filter((suite) => suite.tests.length > 0);
595
670
  const totalTests = countTests(suites);
596
671
  console.log(`\nRunning ${totalTests} client conformance tests...\n`);
597
- const server = new __durable_streams_server.DurableStreamTestServer({ port: options.serverPort ?? 0 });
672
+ const server = new __durable_streams_server.DurableStreamTestServer({
673
+ port: options.serverPort ?? 0,
674
+ longPollTimeout: 500
675
+ });
598
676
  await server.start();
599
677
  const serverUrl = server.url;
600
678
  console.log(`Reference server started at ${serverUrl}\n`);
@@ -607,6 +685,7 @@ async function runConformanceTests(options) {
607
685
  // Multi-status
608
686
  // No result yet - will be retrieved via await
609
687
  // Clean up
688
+ // 500ms timeout for testing
610
689
  require("url").pathToFileURL(__filename).href
611
690
  ).pathname];
612
691
  }
package/dist/cli.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  require('./protocol-XeAOKBD-.cjs');
4
- const require_benchmark_runner = require('./benchmark-runner-BlKqhoXE.cjs');
4
+ const require_benchmark_runner = require('./benchmark-runner-Db4he452.cjs');
5
5
 
6
6
  //#region src/cli.ts
7
7
  const HELP = `
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import "./protocol-qb83AeUH.js";
3
- import { runBenchmarks, runConformanceTests } from "./benchmark-runner-D-YSAvRy.js";
3
+ import { runBenchmarks, runConformanceTests } from "./benchmark-runner-CrE6JkbX.js";
4
4
 
5
5
  //#region src/cli.ts
6
6
  const HELP = `
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  const require_protocol = require('./protocol-XeAOKBD-.cjs');
2
- const require_benchmark_runner = require('./benchmark-runner-BlKqhoXE.cjs');
2
+ const require_benchmark_runner = require('./benchmark-runner-Db4he452.cjs');
3
3
 
4
4
  exports.ErrorCodes = require_protocol.ErrorCodes
5
5
  exports.allScenarios = require_benchmark_runner.allScenarios