@esportsplus/web-storage 0.4.0 → 0.5.0

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/tests/index.ts CHANGED
@@ -1460,3 +1460,412 @@ describe('Migration Callbacks (Memory driver)', () => {
1460
1460
  expect(await v2.get('role')).toBe('guest');
1461
1461
  });
1462
1462
  });
1463
+
1464
+
1465
+ describe('Local (SessionStorage driver)', () => {
1466
+ let store: Local<TestData>;
1467
+
1468
+ beforeEach(() => {
1469
+ sessionStorage.clear();
1470
+ store = createLocal<TestData>({ driver: DriverType.SessionStorage, name: 'ss-test', version: 1 });
1471
+ });
1472
+
1473
+
1474
+ describe('with encryption', () => {
1475
+ let encrypted: Local<TestData>;
1476
+
1477
+ beforeEach(() => {
1478
+ encrypted = createLocal<TestData>({ driver: DriverType.SessionStorage, name: 'ss-enc', version: 1 }, 'test-secret');
1479
+ });
1480
+
1481
+
1482
+ it('set / get — round-trip with secret', async () => {
1483
+ await encrypted.set('name', 'alice');
1484
+
1485
+ expect(await encrypted.get('name')).toBe('alice');
1486
+ });
1487
+ });
1488
+
1489
+
1490
+ describe('without encryption', () => {
1491
+ it('all — returns all entries', async () => {
1492
+ await store.set('age', 30);
1493
+ await store.set('name', 'alice');
1494
+ await store.set('tags', ['a', 'b']);
1495
+
1496
+ let result = await store.all();
1497
+
1498
+ expect(result).toEqual({ age: 30, name: 'alice', tags: ['a', 'b'] });
1499
+ });
1500
+
1501
+ it('clear — removes all entries', async () => {
1502
+ await store.set('age', 25);
1503
+ await store.set('name', 'alice');
1504
+ await store.clear();
1505
+
1506
+ expect(await store.count()).toBe(0);
1507
+ expect(await store.all()).toEqual({});
1508
+ });
1509
+
1510
+ it('count — returns correct count', async () => {
1511
+ await store.set('age', 25);
1512
+ await store.set('name', 'alice');
1513
+
1514
+ expect(await store.count()).toBe(2);
1515
+ });
1516
+
1517
+ it('delete — removes specified keys', async () => {
1518
+ await store.set('age', 30);
1519
+ await store.set('name', 'alice');
1520
+ await store.set('tags', ['a']);
1521
+ await store.delete('name', 'tags');
1522
+
1523
+ expect(await store.get('name')).toBeUndefined();
1524
+ expect(await store.get('tags')).toBeUndefined();
1525
+ expect(await store.get('age')).toBe(30);
1526
+ });
1527
+
1528
+ it('filter — filters entries by predicate', async () => {
1529
+ await store.set('age', 30);
1530
+ await store.set('name', 'alice');
1531
+ await store.set('tags', ['a', 'b']);
1532
+
1533
+ let result = await store.filter(({ key }) => key === 'name' || key === 'tags');
1534
+
1535
+ expect(result).toEqual({ name: 'alice', tags: ['a', 'b'] });
1536
+ });
1537
+
1538
+ it('get — returns undefined for non-existent key', async () => {
1539
+ expect(await store.get('name')).toBeUndefined();
1540
+ });
1541
+
1542
+ it('keys — returns all keys', async () => {
1543
+ await store.set('age', 25);
1544
+ await store.set('name', 'alice');
1545
+
1546
+ let result = await store.keys();
1547
+
1548
+ expect(result.sort()).toEqual(['age', 'name']);
1549
+ });
1550
+
1551
+ it('length — returns correct count', async () => {
1552
+ await store.set('age', 25);
1553
+ await store.set('name', 'alice');
1554
+
1555
+ expect(await store.length()).toBe(2);
1556
+ });
1557
+
1558
+ it('map — iterates all entries', async () => {
1559
+ await store.set('age', 25);
1560
+ await store.set('name', 'alice');
1561
+
1562
+ let entries: { i: number; key: keyof TestData; value: TestData[keyof TestData] }[] = [];
1563
+
1564
+ await store.map((value, key, i) => {
1565
+ entries.push({ i, key, value });
1566
+ });
1567
+
1568
+ expect(entries).toHaveLength(2);
1569
+
1570
+ entries.sort((a, b) => (a.key as string).localeCompare(b.key as string));
1571
+
1572
+ expect(entries[0]).toEqual({ i: expect.any(Number), key: 'age', value: 25 });
1573
+ expect(entries[1]).toEqual({ i: expect.any(Number), key: 'name', value: 'alice' });
1574
+ });
1575
+
1576
+ it('only — returns subset of entries', async () => {
1577
+ await store.set('age', 30);
1578
+ await store.set('name', 'alice');
1579
+ await store.set('tags', ['a']);
1580
+
1581
+ let result = await store.only('name', 'tags');
1582
+
1583
+ expect(result).toEqual({ name: 'alice', tags: ['a'] });
1584
+ });
1585
+
1586
+ it('replace — batch replace returns empty failed array', async () => {
1587
+ let failed = await store.replace({ age: 25, name: 'bob', tags: ['x', 'y'] });
1588
+
1589
+ expect(failed).toEqual([]);
1590
+ expect(await store.get('age')).toBe(25);
1591
+ expect(await store.get('name')).toBe('bob');
1592
+ expect(await store.get('tags')).toEqual(['x', 'y']);
1593
+ });
1594
+
1595
+ it('set / get — basic round-trip', async () => {
1596
+ await store.set('name', 'bob');
1597
+
1598
+ expect(await store.get('name')).toBe('bob');
1599
+ });
1600
+ });
1601
+ });
1602
+
1603
+
1604
+ describe('Memory driver — encryption', () => {
1605
+ let encrypted: Local<TestData>;
1606
+
1607
+ beforeEach(async () => {
1608
+ encrypted = createLocal<TestData>({ driver: DriverType.Memory, name: 'mem-enc', version: 1 }, 'test-secret');
1609
+ await encrypted.clear();
1610
+ });
1611
+
1612
+
1613
+ it('set / get — round-trip with secret', async () => {
1614
+ await encrypted.set('name', 'alice');
1615
+
1616
+ expect(await encrypted.get('name')).toBe('alice');
1617
+ });
1618
+
1619
+ it('all — decrypts all values', async () => {
1620
+ await encrypted.set('age', 30);
1621
+ await encrypted.set('name', 'alice');
1622
+
1623
+ let result = await encrypted.all();
1624
+
1625
+ expect(result).toEqual({ age: 30, name: 'alice' });
1626
+ });
1627
+ });
1628
+
1629
+
1630
+ describe('TTL / Expiration (Memory driver)', () => {
1631
+ let now: number;
1632
+
1633
+ beforeEach(() => {
1634
+ now = Date.now();
1635
+ vi.spyOn(Date, 'now').mockImplementation(() => now);
1636
+ });
1637
+
1638
+ afterEach(() => {
1639
+ vi.restoreAllMocks();
1640
+ });
1641
+
1642
+
1643
+ it('get — returns value before TTL expires', async () => {
1644
+ let store = createLocal<TestData>({ driver: DriverType.Memory, name: 'ttl-mem-1', version: 1 });
1645
+
1646
+ await store.set('name', 'alice', { ttl: 60000 });
1647
+
1648
+ expect(await store.get('name')).toBe('alice');
1649
+ });
1650
+
1651
+ it('get — returns undefined after TTL expires', async () => {
1652
+ let store = createLocal<TestData>({ driver: DriverType.Memory, name: 'ttl-mem-2', version: 1 });
1653
+
1654
+ await store.set('name', 'alice', { ttl: 60000 });
1655
+
1656
+ now += 60001;
1657
+
1658
+ expect(await store.get('name')).toBeUndefined();
1659
+ });
1660
+
1661
+ it('ttl — returns remaining ms', async () => {
1662
+ let store = createLocal<TestData>({ driver: DriverType.Memory, name: 'ttl-mem-3', version: 1 });
1663
+
1664
+ await store.set('name', 'alice', { ttl: 60000 });
1665
+
1666
+ now += 10000;
1667
+
1668
+ let remaining = await store.ttl('name');
1669
+
1670
+ expect(remaining).toBe(50000);
1671
+ });
1672
+
1673
+ it('persist — removes TTL, value still accessible', async () => {
1674
+ let store = createLocal<TestData>({ driver: DriverType.Memory, name: 'ttl-mem-4', version: 1 });
1675
+
1676
+ await store.set('name', 'alice', { ttl: 60000 });
1677
+
1678
+ await store.persist('name');
1679
+
1680
+ now += 120000;
1681
+
1682
+ expect(await store.get('name')).toBe('alice');
1683
+ expect(await store.ttl('name')).toBe(-1);
1684
+ });
1685
+
1686
+ it('cleanup — removes all expired entries', async () => {
1687
+ let store = createLocal<TestData>({ driver: DriverType.Memory, name: 'ttl-mem-5', version: 1 });
1688
+
1689
+ await store.set('name', 'alice', { ttl: 10000 });
1690
+ await store.set('age', 30, { ttl: 10000 });
1691
+ await store.set('tags', ['a']);
1692
+
1693
+ now += 10001;
1694
+
1695
+ await store.cleanup();
1696
+
1697
+ expect(await store.count()).toBe(1);
1698
+ expect(await store.get('tags')).toEqual(['a']);
1699
+ });
1700
+ });
1701
+
1702
+
1703
+ describe('get(key, factory) — Memory driver', () => {
1704
+ let now: number;
1705
+
1706
+ beforeEach(() => {
1707
+ now = Date.now();
1708
+ vi.spyOn(Date, 'now').mockImplementation(() => now);
1709
+ });
1710
+
1711
+ afterEach(() => {
1712
+ vi.restoreAllMocks();
1713
+ });
1714
+
1715
+
1716
+ it('returns factory value when key is missing', async () => {
1717
+ let store = createLocal<TestData>({ driver: DriverType.Memory, name: 'factory-mem-1', version: 1 });
1718
+
1719
+ let result = await store.get('name', () => 'default');
1720
+
1721
+ expect(result).toBe('default');
1722
+ });
1723
+
1724
+ it('returns stored value when key exists — factory NOT called', async () => {
1725
+ let called = false,
1726
+ store = createLocal<TestData>({ driver: DriverType.Memory, name: 'factory-mem-2', version: 1 });
1727
+
1728
+ await store.set('name', 'alice');
1729
+
1730
+ let result = await store.get('name', () => {
1731
+ called = true;
1732
+ return 'default';
1733
+ });
1734
+
1735
+ expect(result).toBe('alice');
1736
+ expect(called).toBe(false);
1737
+ });
1738
+ });
1739
+
1740
+
1741
+ describe('Cross-feature edge cases', () => {
1742
+ let now: number;
1743
+
1744
+ beforeEach(() => {
1745
+ now = Date.now();
1746
+ vi.spyOn(Date, 'now').mockImplementation(() => now);
1747
+ });
1748
+
1749
+ afterEach(() => {
1750
+ vi.restoreAllMocks();
1751
+ });
1752
+
1753
+
1754
+ it('persist — returns false for non-existent key', async () => {
1755
+ let store = createLocal<TestData>({ driver: DriverType.Memory, name: 'edge-persist-1', version: 1 });
1756
+
1757
+ expect(await store.persist('name')).toBe(false);
1758
+ });
1759
+
1760
+ it('persist — returns true for already-permanent key', async () => {
1761
+ let store = createLocal<TestData>({ driver: DriverType.Memory, name: 'edge-persist-2', version: 1 });
1762
+
1763
+ await store.set('name', 'alice');
1764
+
1765
+ expect(await store.persist('name')).toBe(true);
1766
+ });
1767
+
1768
+ it('cleanup — fires subscription notifications for expired keys', async () => {
1769
+ let calls: { key: unknown; newValue: unknown; oldValue: unknown }[] = [],
1770
+ store = createLocal<TestData>({ driver: DriverType.Memory, name: 'edge-cleanup-sub', version: 1 });
1771
+
1772
+ await store.set('name', 'alice', { ttl: 10000 });
1773
+ await store.set('age', 30);
1774
+
1775
+ store.subscribe((key, newValue, oldValue) => {
1776
+ calls.push({ key, newValue, oldValue });
1777
+ });
1778
+
1779
+ now += 10001;
1780
+
1781
+ await store.cleanup();
1782
+
1783
+ expect(calls).toHaveLength(1);
1784
+ expect(calls[0]).toEqual({ key: 'name', newValue: undefined, oldValue: 'alice' });
1785
+ });
1786
+
1787
+ it('count — excludes __version__ key when migrations active', async () => {
1788
+ type V1 = { name: string };
1789
+ type V2 = { name: string; role: string };
1790
+
1791
+ let store1 = createLocal<V1>({ driver: DriverType.Memory, name: 'edge-count-ver', version: 1 });
1792
+
1793
+ await store1.set('name', 'alice');
1794
+
1795
+ let store2 = createLocal<V2>({
1796
+ driver: DriverType.Memory,
1797
+ migrations: {
1798
+ 2: async (old) => {
1799
+ let all = await old.all();
1800
+
1801
+ return { ...all, role: 'admin' };
1802
+ }
1803
+ },
1804
+ name: 'edge-count-ver',
1805
+ version: 2
1806
+ });
1807
+
1808
+ expect(await store2.count()).toBe(2);
1809
+ });
1810
+
1811
+ it('keys — excludes __version__ key when migrations active', async () => {
1812
+ type V1 = { name: string };
1813
+ type V2 = { name: string; role: string };
1814
+
1815
+ let store1 = createLocal<V1>({ driver: DriverType.Memory, name: 'edge-keys-ver', version: 1 });
1816
+
1817
+ await store1.set('name', 'alice');
1818
+
1819
+ let store2 = createLocal<V2>({
1820
+ driver: DriverType.Memory,
1821
+ migrations: {
1822
+ 2: async (old) => {
1823
+ let all = await old.all();
1824
+
1825
+ return { ...all, role: 'admin' };
1826
+ }
1827
+ },
1828
+ name: 'edge-keys-ver',
1829
+ version: 2
1830
+ });
1831
+
1832
+ let keys = await store2.keys();
1833
+
1834
+ expect(keys.sort()).toEqual(['name', 'role']);
1835
+ expect(keys).not.toContain('__version__');
1836
+ });
1837
+
1838
+ it('get with factory — fires subscription notification', async () => {
1839
+ let called = false,
1840
+ store = createLocal<TestData>({ driver: DriverType.Memory, name: 'edge-factory-sub', version: 1 });
1841
+
1842
+ store.subscribe('name', () => { called = true; });
1843
+
1844
+ await store.get('name', () => 'lazy');
1845
+
1846
+ // Allow fire-and-forget set to complete
1847
+ await new Promise((r) => setTimeout(r, 50));
1848
+
1849
+ expect(called).toBe(true);
1850
+ });
1851
+ });
1852
+
1853
+
1854
+ describe('Compression + Encryption (LocalStorage)', () => {
1855
+
1856
+ beforeEach(() => {
1857
+ localStorage.clear();
1858
+ });
1859
+
1860
+
1861
+ it('large value round-trips with encryption enabled', async () => {
1862
+ type LargeData = { payload: string };
1863
+
1864
+ let store = createLocal<LargeData>({ driver: DriverType.LocalStorage, name: 'comp-enc', version: 1 }, 'test-secret'),
1865
+ largeValue = 'a'.repeat(200);
1866
+
1867
+ await store.set('payload', largeValue);
1868
+
1869
+ expect(await store.get('payload')).toBe(largeValue);
1870
+ });
1871
+ });