@rotorsoft/act-tck 1.26.2 → 1.26.4
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/README.md +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/store-tck.d.ts +14 -5
- package/dist/@types/store-tck.d.ts.map +1 -1
- package/dist/index.cjs +215 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +215 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1434,6 +1434,93 @@ var runStoreTck = (options) => {
|
|
|
1434
1434
|
await fresh.dispose();
|
|
1435
1435
|
}
|
|
1436
1436
|
});
|
|
1437
|
+
it7("counts a timed-out lease reclaimed by another worker against the retry budget; ack resets it", async () => {
|
|
1438
|
+
const fresh = await options.factory();
|
|
1439
|
+
try {
|
|
1440
|
+
await fresh.drop();
|
|
1441
|
+
await fresh.seed();
|
|
1442
|
+
const s = `lease-timeout-${uid()}`;
|
|
1443
|
+
await fresh.subscribe([{ stream: s }]);
|
|
1444
|
+
const [e1] = await seed_stream(fresh, s, 1);
|
|
1445
|
+
const a = await fresh.claim(1, 0, `wA-${uid()}`, 0);
|
|
1446
|
+
expect8(a.find((l) => l.stream === s)?.retry).toBe(0);
|
|
1447
|
+
const b = await fresh.claim(1, 0, `wB-${uid()}`, 3e4);
|
|
1448
|
+
const b_lease = b.find((l) => l.stream === s);
|
|
1449
|
+
expect8(b_lease).toBeDefined();
|
|
1450
|
+
expect8(b_lease.retry).toBe(1);
|
|
1451
|
+
await fresh.ack([{ ...b_lease, at: e1.id }]);
|
|
1452
|
+
await seed_stream(fresh, s, 1);
|
|
1453
|
+
const c = await fresh.claim(1, 0, `wC-${uid()}`, 3e4);
|
|
1454
|
+
expect8(c.find((l) => l.stream === s)?.retry).toBe(0);
|
|
1455
|
+
} finally {
|
|
1456
|
+
await fresh.dispose();
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
});
|
|
1460
|
+
describe8("claim source matching", () => {
|
|
1461
|
+
it7("treats source as an exact stream name \u2014 no substring or pattern overmatch", async () => {
|
|
1462
|
+
const fresh = await options.factory();
|
|
1463
|
+
try {
|
|
1464
|
+
await fresh.drop();
|
|
1465
|
+
await fresh.seed();
|
|
1466
|
+
const base = `src-${uid()}`;
|
|
1467
|
+
const sibling = `${base}2`;
|
|
1468
|
+
const target = `agg-${uid()}`;
|
|
1469
|
+
const control = `ctl-${uid()}`;
|
|
1470
|
+
await fresh.subscribe([
|
|
1471
|
+
{ stream: target, source: base },
|
|
1472
|
+
// Control subscription sourced from the sibling — it DOES see
|
|
1473
|
+
// the sibling commit below, so the negative assertion runs
|
|
1474
|
+
// through a populated claim result.
|
|
1475
|
+
{ stream: control, source: sibling }
|
|
1476
|
+
]);
|
|
1477
|
+
const [seeded] = await seed_stream(fresh, base, 1);
|
|
1478
|
+
const [ctl_seeded] = await seed_stream(fresh, sibling, 1);
|
|
1479
|
+
const first = await fresh.claim(100, 0, `w-${uid()}`, 3e4);
|
|
1480
|
+
const mine = first.find((l) => l.stream === target);
|
|
1481
|
+
const ctl = first.find((l) => l.stream === control);
|
|
1482
|
+
expect8(mine).toBeDefined();
|
|
1483
|
+
expect8(ctl).toBeDefined();
|
|
1484
|
+
await fresh.ack([
|
|
1485
|
+
{ ...mine, at: seeded.id },
|
|
1486
|
+
{ ...ctl, at: ctl_seeded.id }
|
|
1487
|
+
]);
|
|
1488
|
+
await seed_stream(fresh, sibling, 1);
|
|
1489
|
+
const second = await fresh.claim(100, 0, `w-${uid()}`, 3e4);
|
|
1490
|
+
expect8(second.find((l) => l.stream === control)).toBeDefined();
|
|
1491
|
+
expect8(second.find((l) => l.stream === target)).toBeUndefined();
|
|
1492
|
+
} finally {
|
|
1493
|
+
await fresh.dispose();
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
it7("receives exactly its source stream's events", async () => {
|
|
1497
|
+
const fresh = await options.factory();
|
|
1498
|
+
try {
|
|
1499
|
+
await fresh.drop();
|
|
1500
|
+
await fresh.seed();
|
|
1501
|
+
const base = `src-${uid()}`;
|
|
1502
|
+
const target = `agg-${uid()}`;
|
|
1503
|
+
await fresh.subscribe([{ stream: target, source: base }]);
|
|
1504
|
+
const [e1] = await seed_stream(fresh, base, 1);
|
|
1505
|
+
const first = await fresh.claim(100, 0, `w-${uid()}`, 3e4);
|
|
1506
|
+
const f = first.find((l) => l.stream === target);
|
|
1507
|
+
expect8(f).toBeDefined();
|
|
1508
|
+
expect8(f.source).toBe(base);
|
|
1509
|
+
await fresh.ack([{ ...f, at: e1.id }]);
|
|
1510
|
+
const [e2] = await seed_stream(fresh, base, 1);
|
|
1511
|
+
const second = await fresh.claim(100, 0, `w-${uid()}`, 3e4);
|
|
1512
|
+
const sec = second.find((l) => l.stream === target);
|
|
1513
|
+
expect8(sec).toBeDefined();
|
|
1514
|
+
expect8(sec.at).toBe(e1.id);
|
|
1515
|
+
const events2 = await collect(fresh, {
|
|
1516
|
+
stream: sec.source,
|
|
1517
|
+
after: sec.at
|
|
1518
|
+
});
|
|
1519
|
+
expect8(events2.map((e) => e.id)).toEqual([e2.id]);
|
|
1520
|
+
} finally {
|
|
1521
|
+
await fresh.dispose();
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1437
1524
|
});
|
|
1438
1525
|
describe8.skipIf(!caps.concurrent_claim)("concurrency (capability)", () => {
|
|
1439
1526
|
it7("never double-leases a stream across concurrent claimers", async () => {
|
|
@@ -1465,6 +1552,51 @@ var runStoreTck = (options) => {
|
|
|
1465
1552
|
await fresh.dispose();
|
|
1466
1553
|
}
|
|
1467
1554
|
});
|
|
1555
|
+
it7("does not hand an unexpired lease to a competing claimer", async () => {
|
|
1556
|
+
const fresh = await options.factory();
|
|
1557
|
+
try {
|
|
1558
|
+
await fresh.drop();
|
|
1559
|
+
await fresh.seed();
|
|
1560
|
+
const s = `unexpired-${uid()}`;
|
|
1561
|
+
const other = `unexpired-other-${uid()}`;
|
|
1562
|
+
await fresh.subscribe([{ stream: s }]);
|
|
1563
|
+
await seed_stream(fresh, s, 1);
|
|
1564
|
+
const a = await fresh.claim(100, 100, `wA-${uid()}`, 6e4);
|
|
1565
|
+
expect8(a.find((l) => l.stream === s)).toBeDefined();
|
|
1566
|
+
await fresh.subscribe([{ stream: other }]);
|
|
1567
|
+
await seed_stream(fresh, other, 1);
|
|
1568
|
+
const b = await fresh.claim(100, 100, `wB-${uid()}`, 6e4);
|
|
1569
|
+
expect8(b.find((l) => l.stream === other)).toBeDefined();
|
|
1570
|
+
expect8(b.find((l) => l.stream === s)).toBeUndefined();
|
|
1571
|
+
} finally {
|
|
1572
|
+
await fresh.dispose();
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
it7("hands an expired lease to exactly one competing claimer, with retry accounting shared across workers", async () => {
|
|
1576
|
+
const fresh = await options.factory();
|
|
1577
|
+
try {
|
|
1578
|
+
await fresh.drop();
|
|
1579
|
+
await fresh.seed();
|
|
1580
|
+
const s = `expired-${uid()}`;
|
|
1581
|
+
await fresh.subscribe([{ stream: s }]);
|
|
1582
|
+
const [e1] = await seed_stream(fresh, s, 1);
|
|
1583
|
+
const dead = await fresh.claim(100, 0, `wDead-${uid()}`, 0);
|
|
1584
|
+
expect8(dead.find((l) => l.stream === s)?.retry).toBe(0);
|
|
1585
|
+
const [b, c] = await Promise.all([
|
|
1586
|
+
fresh.claim(100, 100, `wB-${uid()}`, 6e4),
|
|
1587
|
+
fresh.claim(100, 100, `wC-${uid()}`, 6e4)
|
|
1588
|
+
]);
|
|
1589
|
+
const winners = [...b, ...c].filter((l) => l.stream === s);
|
|
1590
|
+
expect8(winners).toHaveLength(1);
|
|
1591
|
+
expect8(winners[0].retry).toBe(1);
|
|
1592
|
+
await fresh.ack([{ ...winners[0], at: e1.id }]);
|
|
1593
|
+
await seed_stream(fresh, s, 1);
|
|
1594
|
+
const again = await fresh.claim(100, 0, `wNext-${uid()}`, 6e4);
|
|
1595
|
+
expect8(again.find((l) => l.stream === s)?.retry).toBe(0);
|
|
1596
|
+
} finally {
|
|
1597
|
+
await fresh.dispose();
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1468
1600
|
});
|
|
1469
1601
|
describe8("block", () => {
|
|
1470
1602
|
it7("hides blocked streams from claim", async () => {
|
|
@@ -3455,6 +3587,89 @@ var runStoreTck = (options) => {
|
|
|
3455
3587
|
await Promise.resolve(disposer());
|
|
3456
3588
|
}
|
|
3457
3589
|
});
|
|
3590
|
+
it7("does not deliver an instance's own commits (self-filtering)", async () => {
|
|
3591
|
+
const received = [];
|
|
3592
|
+
let resolve_sentinel;
|
|
3593
|
+
const sentinel_arrived = new Promise((res) => {
|
|
3594
|
+
resolve_sentinel = res;
|
|
3595
|
+
});
|
|
3596
|
+
const own = `notify-self-${uid()}`;
|
|
3597
|
+
const remote = `notify-remote-${uid()}`;
|
|
3598
|
+
const sentinel = `notify-sentinel-${uid()}`;
|
|
3599
|
+
const disposer = await store.notify.call(store, (n) => {
|
|
3600
|
+
received.push(n);
|
|
3601
|
+
if (n.stream === sentinel) resolve_sentinel();
|
|
3602
|
+
});
|
|
3603
|
+
const writer = await options.factory();
|
|
3604
|
+
try {
|
|
3605
|
+
await store.commit(
|
|
3606
|
+
own,
|
|
3607
|
+
[inc(1)],
|
|
3608
|
+
make_meta({ stream: own })
|
|
3609
|
+
);
|
|
3610
|
+
await writer.commit(
|
|
3611
|
+
remote,
|
|
3612
|
+
[inc(1)],
|
|
3613
|
+
make_meta({ stream: remote })
|
|
3614
|
+
);
|
|
3615
|
+
await writer.commit(
|
|
3616
|
+
sentinel,
|
|
3617
|
+
[inc(1)],
|
|
3618
|
+
make_meta({ stream: sentinel })
|
|
3619
|
+
);
|
|
3620
|
+
await sentinel_arrived;
|
|
3621
|
+
expect8(received.find((n) => n.stream === own)).toBeUndefined();
|
|
3622
|
+
expect8(received.find((n) => n.stream === remote)).toBeDefined();
|
|
3623
|
+
expect8(received.find((n) => n.stream === sentinel)).toBeDefined();
|
|
3624
|
+
} finally {
|
|
3625
|
+
await writer.dispose();
|
|
3626
|
+
await Promise.resolve(disposer());
|
|
3627
|
+
}
|
|
3628
|
+
});
|
|
3629
|
+
it7("delivers one notification per commit transaction carrying the full event batch", async () => {
|
|
3630
|
+
const received = [];
|
|
3631
|
+
let resolve_sentinel;
|
|
3632
|
+
const sentinel_arrived = new Promise((res) => {
|
|
3633
|
+
resolve_sentinel = res;
|
|
3634
|
+
});
|
|
3635
|
+
const batch = `notify-batch-${uid()}`;
|
|
3636
|
+
const sentinel = `notify-batch-sentinel-${uid()}`;
|
|
3637
|
+
const disposer = await store.notify.call(store, (n) => {
|
|
3638
|
+
received.push(n);
|
|
3639
|
+
if (n.stream === sentinel) resolve_sentinel();
|
|
3640
|
+
});
|
|
3641
|
+
const writer = await options.factory();
|
|
3642
|
+
try {
|
|
3643
|
+
await writer.commit(
|
|
3644
|
+
batch,
|
|
3645
|
+
[inc(1), inc(2), inc(3)],
|
|
3646
|
+
make_meta({ stream: batch })
|
|
3647
|
+
);
|
|
3648
|
+
await writer.commit(
|
|
3649
|
+
sentinel,
|
|
3650
|
+
[inc(1)],
|
|
3651
|
+
make_meta({ stream: sentinel })
|
|
3652
|
+
);
|
|
3653
|
+
await sentinel_arrived;
|
|
3654
|
+
const batch_notifications = received.filter(
|
|
3655
|
+
(n) => n.stream === batch
|
|
3656
|
+
);
|
|
3657
|
+
expect8(batch_notifications).toHaveLength(1);
|
|
3658
|
+
const events2 = batch_notifications[0].events;
|
|
3659
|
+
expect8(events2).toHaveLength(3);
|
|
3660
|
+
expect8(events2.map((e) => e.name)).toEqual([
|
|
3661
|
+
"Incremented",
|
|
3662
|
+
"Incremented",
|
|
3663
|
+
"Incremented"
|
|
3664
|
+
]);
|
|
3665
|
+
expect8([...events2.map((e) => e.id)].sort((a, b) => a - b)).toEqual(
|
|
3666
|
+
events2.map((e) => e.id)
|
|
3667
|
+
);
|
|
3668
|
+
} finally {
|
|
3669
|
+
await writer.dispose();
|
|
3670
|
+
await Promise.resolve(disposer());
|
|
3671
|
+
}
|
|
3672
|
+
});
|
|
3458
3673
|
});
|
|
3459
3674
|
}
|
|
3460
3675
|
});
|