tablinum 0.6.0 → 0.6.1

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/dist/index.js CHANGED
@@ -422,7 +422,7 @@ function buildStructSchema(def, options = {}) {
422
422
  }
423
423
  for (const [name, fieldDef] of Object.entries(def.fields)) {
424
424
  const fieldSchema = fieldDefToSchema(fieldDef);
425
- schemaFields[name] = options.allOptional ? Schema3.optionalKey(fieldSchema) : fieldSchema;
425
+ schemaFields[name] = options.allOptional || fieldDef.isOptional ? Schema3.optionalKey(fieldSchema) : fieldSchema;
426
426
  }
427
427
  return Schema3.Struct(schemaFields);
428
428
  }
@@ -1418,7 +1418,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1418
1418
  const allHaveIds = [];
1419
1419
  const allNeedIds = [];
1420
1420
  const subId = `neg-${Date.now()}`;
1421
- const initialMsg = yield* Effect7.try({
1421
+ const initialMsg = yield* Effect7.tryPromise({
1422
1422
  try: () => neg.initiate(),
1423
1423
  catch: (e) => new SyncError({
1424
1424
  message: `Negentropy initiate failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1430,7 +1430,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1430
1430
  while (currentMsg !== null) {
1431
1431
  const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
1432
1432
  if (response.msgHex === null) break;
1433
- const reconcileResult = yield* Effect7.try({
1433
+ const reconcileResult = yield* Effect7.tryPromise({
1434
1434
  try: () => neg.reconcile(response.msgHex),
1435
1435
  catch: (e) => new SyncError({
1436
1436
  message: `Negentropy reconcile failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1583,30 +1583,44 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1583
1583
  if (!memberRecord) return false;
1584
1584
  return !!memberRecord.removedAt;
1585
1585
  });
1586
- const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
1586
+ const storeGiftWrapShell = (gw) => storage.putGiftWrap({ id: gw.id, event: gw, createdAt: gw.created_at });
1587
+ const unwrapGiftWrap = (remoteGw) => Effect8.gen(function* () {
1587
1588
  const existing = yield* storage.getGiftWrap(remoteGw.id);
1588
1589
  if (existing) return null;
1589
- const unwrapResult = yield* Effect8.result(giftWrapHandle.unwrap(remoteGw));
1590
- if (unwrapResult._tag === "Failure") {
1591
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1590
+ const rumor = yield* giftWrapHandle.unwrap(remoteGw).pipe(
1591
+ Effect8.orElseSucceed(() => null)
1592
+ );
1593
+ if (!rumor) {
1594
+ yield* storeGiftWrapShell(remoteGw);
1592
1595
  return null;
1593
1596
  }
1594
- const rumor = unwrapResult.success;
1595
1597
  const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
1596
1598
  if (!dTag) {
1597
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1599
+ yield* storeGiftWrapShell(remoteGw);
1598
1600
  return null;
1599
1601
  }
1600
1602
  const colonIdx = dTag.indexOf(":");
1601
1603
  if (colonIdx === -1) {
1602
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1604
+ yield* storeGiftWrapShell(remoteGw);
1603
1605
  return null;
1604
1606
  }
1605
- const collectionName = dTag.substring(0, colonIdx);
1606
- const recordId = dTag.substring(colonIdx + 1);
1607
+ const collection2 = dTag.substring(0, colonIdx);
1608
+ if (!knownCollections.has(collection2)) {
1609
+ yield* storeGiftWrapShell(remoteGw);
1610
+ return null;
1611
+ }
1612
+ return {
1613
+ giftWrap: remoteGw,
1614
+ rumor,
1615
+ collection: collection2,
1616
+ recordId: dTag.substring(colonIdx + 1)
1617
+ };
1618
+ });
1619
+ const applyUnwrappedEvent = (uw) => Effect8.gen(function* () {
1620
+ const { giftWrap: remoteGw, rumor, collection: collectionName, recordId } = uw;
1607
1621
  const retention = knownCollections.get(collectionName);
1608
1622
  if (retention === void 0) {
1609
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1623
+ yield* storeGiftWrapShell(remoteGw);
1610
1624
  return null;
1611
1625
  }
1612
1626
  if (rumor.pubkey) {
@@ -1615,20 +1629,20 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1615
1629
  yield* Effect8.logWarning("Rejected write from removed member", {
1616
1630
  author: rumor.pubkey.slice(0, 12)
1617
1631
  });
1618
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1632
+ yield* storeGiftWrapShell(remoteGw);
1619
1633
  return null;
1620
1634
  }
1621
1635
  }
1622
- let data = null;
1623
- let kind = "u";
1624
1636
  const parsed = yield* Effect8.try({
1625
1637
  try: () => JSON.parse(rumor.content),
1626
1638
  catch: () => void 0
1627
1639
  }).pipe(Effect8.orElseSucceed(() => void 0));
1628
1640
  if (parsed === void 0) {
1629
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1641
+ yield* storeGiftWrapShell(remoteGw);
1630
1642
  return null;
1631
1643
  }
1644
+ let data = null;
1645
+ let kind = "u";
1632
1646
  if (parsed === null || parsed._deleted) {
1633
1647
  kind = "d";
1634
1648
  } else {
@@ -1665,6 +1679,78 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1665
1679
  }
1666
1680
  return collectionName;
1667
1681
  });
1682
+ const reconcileRelay = (url, pubKeys) => Effect8.gen(function* () {
1683
+ yield* Effect8.logDebug("Syncing relay", { relay: url });
1684
+ const { haveIds, needIds } = yield* reconcileWithRelay(
1685
+ storage,
1686
+ relay,
1687
+ url,
1688
+ Array.from(pubKeys)
1689
+ ).pipe(
1690
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1691
+ Effect8.orElseSucceed(() => ({ haveIds: [], needIds: [] }))
1692
+ );
1693
+ yield* Effect8.logDebug("Relay reconciliation result", {
1694
+ relay: url,
1695
+ need: needIds.length,
1696
+ have: haveIds.length
1697
+ });
1698
+ const events = needIds.length > 0 ? yield* relay.fetchEvents(needIds, url).pipe(
1699
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1700
+ Effect8.orElseSucceed(() => [])
1701
+ ) : [];
1702
+ return {
1703
+ events,
1704
+ haveIds: haveIds.map((id) => ({ id, url }))
1705
+ };
1706
+ }).pipe(Effect8.withLogSpan("tablinum.reconcileRelay"));
1707
+ const syncAllRelays = (pubKeys, changedCollections) => Effect8.gen(function* () {
1708
+ const results = yield* Effect8.forEach(
1709
+ relayUrls,
1710
+ (url) => reconcileRelay(url, pubKeys),
1711
+ { concurrency: "unbounded" }
1712
+ );
1713
+ const seen = /* @__PURE__ */ new Set();
1714
+ const allGiftWraps = [];
1715
+ for (const { events } of results) {
1716
+ for (const ev of events) {
1717
+ if (!seen.has(ev.id)) {
1718
+ seen.add(ev.id);
1719
+ allGiftWraps.push(ev);
1720
+ }
1721
+ }
1722
+ }
1723
+ const unwrapped = [];
1724
+ for (const gw of allGiftWraps) {
1725
+ const result = yield* unwrapGiftWrap(gw).pipe(Effect8.orElseSucceed(() => null));
1726
+ if (result) unwrapped.push(result);
1727
+ }
1728
+ unwrapped.sort((a, b) => a.rumor.created_at - b.rumor.created_at || (a.rumor.id < b.rumor.id ? -1 : 1));
1729
+ for (const event of unwrapped) {
1730
+ const collection2 = yield* applyUnwrappedEvent(event).pipe(
1731
+ Effect8.orElseSucceed(() => null)
1732
+ );
1733
+ if (collection2) changedCollections.add(collection2);
1734
+ }
1735
+ const allHaveIds = results.flatMap((r) => r.haveIds);
1736
+ yield* Effect8.forEach(
1737
+ allHaveIds,
1738
+ ({ id, url }) => Effect8.gen(function* () {
1739
+ const gw = yield* storage.getGiftWrap(id);
1740
+ if (!gw?.event) return;
1741
+ yield* relay.publish(gw.event, [url]).pipe(
1742
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1743
+ Effect8.ignore
1744
+ );
1745
+ }),
1746
+ { concurrency: "unbounded", discard: true }
1747
+ );
1748
+ }).pipe(Effect8.withLogSpan("tablinum.syncAllRelays"));
1749
+ const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
1750
+ const uw = yield* unwrapGiftWrap(remoteGw);
1751
+ if (!uw) return null;
1752
+ return yield* applyUnwrappedEvent(uw);
1753
+ });
1668
1754
  const processRealtimeGiftWrap = (remoteGw) => Effect8.gen(function* () {
1669
1755
  const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect8.orElseSucceed(() => null));
1670
1756
  if (collection2) {
@@ -1733,55 +1819,8 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1733
1819
  Effect8.ignore
1734
1820
  );
1735
1821
  }),
1736
- { discard: true }
1822
+ { concurrency: "unbounded", discard: true }
1737
1823
  );
1738
- const syncRelay = (url, pubKeys, changedCollections) => Effect8.gen(function* () {
1739
- yield* Effect8.logDebug("Syncing relay", { relay: url });
1740
- const reconcileResult = yield* Effect8.result(
1741
- reconcileWithRelay(storage, relay, url, Array.from(pubKeys))
1742
- );
1743
- if (reconcileResult._tag === "Failure") {
1744
- onSyncError?.(reconcileResult.failure);
1745
- return;
1746
- }
1747
- const { haveIds, needIds } = reconcileResult.success;
1748
- yield* Effect8.logDebug("Relay reconciliation result", {
1749
- relay: url,
1750
- need: needIds.length,
1751
- have: haveIds.length
1752
- });
1753
- if (needIds.length > 0) {
1754
- const fetched = yield* relay.fetchEvents(needIds, url).pipe(
1755
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1756
- Effect8.orElseSucceed(() => [])
1757
- );
1758
- const sorted = [...fetched].sort((a, b) => a.created_at - b.created_at);
1759
- yield* Effect8.forEach(
1760
- sorted,
1761
- (remoteGw) => Effect8.gen(function* () {
1762
- const collection2 = yield* processGiftWrap(remoteGw).pipe(
1763
- Effect8.orElseSucceed(() => null)
1764
- );
1765
- if (collection2) changedCollections.add(collection2);
1766
- }),
1767
- { discard: true }
1768
- );
1769
- }
1770
- if (haveIds.length > 0) {
1771
- yield* Effect8.forEach(
1772
- haveIds,
1773
- (id) => Effect8.gen(function* () {
1774
- const gw = yield* storage.getGiftWrap(id);
1775
- if (!gw?.event) return;
1776
- yield* relay.publish(gw.event, [url]).pipe(
1777
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1778
- Effect8.ignore
1779
- );
1780
- }),
1781
- { discard: true }
1782
- );
1783
- }
1784
- }).pipe(Effect8.withLogSpan("tablinum.syncRelay"));
1785
1824
  let healingActive = false;
1786
1825
  const healingEffect = Effect8.gen(function* () {
1787
1826
  if (!healingActive) return;
@@ -1791,9 +1830,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1791
1830
  yield* Effect8.gen(function* () {
1792
1831
  const pubKeys = getSubscriptionPubKeys();
1793
1832
  const changedCollections = /* @__PURE__ */ new Set();
1794
- yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1795
- discard: true
1796
- });
1833
+ yield* syncAllRelays(pubKeys, changedCollections);
1797
1834
  if (changedCollections.size > 0) {
1798
1835
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1799
1836
  }
@@ -1807,9 +1844,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1807
1844
  const changedCollections = /* @__PURE__ */ new Set();
1808
1845
  yield* Effect8.gen(function* () {
1809
1846
  const pubKeys = getSubscriptionPubKeys();
1810
- yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1811
- discard: true
1812
- });
1847
+ yield* syncAllRelays(pubKeys, changedCollections);
1813
1848
  yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
1814
1849
  }).pipe(
1815
1850
  Effect8.ensuring(
@@ -459,7 +459,7 @@ function buildStructSchema(def, options = {}) {
459
459
  }
460
460
  for (const [name, fieldDef] of Object.entries(def.fields)) {
461
461
  const fieldSchema = fieldDefToSchema(fieldDef);
462
- schemaFields[name] = options.allOptional ? Schema4.optionalKey(fieldSchema) : fieldSchema;
462
+ schemaFields[name] = options.allOptional || fieldDef.isOptional ? Schema4.optionalKey(fieldSchema) : fieldSchema;
463
463
  }
464
464
  return Schema4.Struct(schemaFields);
465
465
  }
@@ -1455,7 +1455,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1455
1455
  const allHaveIds = [];
1456
1456
  const allNeedIds = [];
1457
1457
  const subId = `neg-${Date.now()}`;
1458
- const initialMsg = yield* Effect7.try({
1458
+ const initialMsg = yield* Effect7.tryPromise({
1459
1459
  try: () => neg.initiate(),
1460
1460
  catch: (e) => new SyncError({
1461
1461
  message: `Negentropy initiate failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1467,7 +1467,7 @@ function reconcileWithRelay(storage, relay, relayUrl, publicKeys) {
1467
1467
  while (currentMsg !== null) {
1468
1468
  const response = yield* relay.sendNegMsg(relayUrl, subId, filter, currentMsg);
1469
1469
  if (response.msgHex === null) break;
1470
- const reconcileResult = yield* Effect7.try({
1470
+ const reconcileResult = yield* Effect7.tryPromise({
1471
1471
  try: () => neg.reconcile(response.msgHex),
1472
1472
  catch: (e) => new SyncError({
1473
1473
  message: `Negentropy reconcile failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -1620,30 +1620,44 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1620
1620
  if (!memberRecord) return false;
1621
1621
  return !!memberRecord.removedAt;
1622
1622
  });
1623
- const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
1623
+ const storeGiftWrapShell = (gw) => storage.putGiftWrap({ id: gw.id, event: gw, createdAt: gw.created_at });
1624
+ const unwrapGiftWrap = (remoteGw) => Effect8.gen(function* () {
1624
1625
  const existing = yield* storage.getGiftWrap(remoteGw.id);
1625
1626
  if (existing) return null;
1626
- const unwrapResult = yield* Effect8.result(giftWrapHandle.unwrap(remoteGw));
1627
- if (unwrapResult._tag === "Failure") {
1628
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1627
+ const rumor = yield* giftWrapHandle.unwrap(remoteGw).pipe(
1628
+ Effect8.orElseSucceed(() => null)
1629
+ );
1630
+ if (!rumor) {
1631
+ yield* storeGiftWrapShell(remoteGw);
1629
1632
  return null;
1630
1633
  }
1631
- const rumor = unwrapResult.success;
1632
1634
  const dTag = rumor.tags.find((t) => t[0] === "d")?.[1];
1633
1635
  if (!dTag) {
1634
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1636
+ yield* storeGiftWrapShell(remoteGw);
1635
1637
  return null;
1636
1638
  }
1637
1639
  const colonIdx = dTag.indexOf(":");
1638
1640
  if (colonIdx === -1) {
1639
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1641
+ yield* storeGiftWrapShell(remoteGw);
1640
1642
  return null;
1641
1643
  }
1642
- const collectionName = dTag.substring(0, colonIdx);
1643
- const recordId = dTag.substring(colonIdx + 1);
1644
+ const collection2 = dTag.substring(0, colonIdx);
1645
+ if (!knownCollections.has(collection2)) {
1646
+ yield* storeGiftWrapShell(remoteGw);
1647
+ return null;
1648
+ }
1649
+ return {
1650
+ giftWrap: remoteGw,
1651
+ rumor,
1652
+ collection: collection2,
1653
+ recordId: dTag.substring(colonIdx + 1)
1654
+ };
1655
+ });
1656
+ const applyUnwrappedEvent = (uw) => Effect8.gen(function* () {
1657
+ const { giftWrap: remoteGw, rumor, collection: collectionName, recordId } = uw;
1644
1658
  const retention = knownCollections.get(collectionName);
1645
1659
  if (retention === void 0) {
1646
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1660
+ yield* storeGiftWrapShell(remoteGw);
1647
1661
  return null;
1648
1662
  }
1649
1663
  if (rumor.pubkey) {
@@ -1652,20 +1666,20 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1652
1666
  yield* Effect8.logWarning("Rejected write from removed member", {
1653
1667
  author: rumor.pubkey.slice(0, 12)
1654
1668
  });
1655
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1669
+ yield* storeGiftWrapShell(remoteGw);
1656
1670
  return null;
1657
1671
  }
1658
1672
  }
1659
- let data = null;
1660
- let kind = "u";
1661
1673
  const parsed = yield* Effect8.try({
1662
1674
  try: () => JSON.parse(rumor.content),
1663
1675
  catch: () => void 0
1664
1676
  }).pipe(Effect8.orElseSucceed(() => void 0));
1665
1677
  if (parsed === void 0) {
1666
- yield* storage.putGiftWrap({ id: remoteGw.id, event: remoteGw, createdAt: remoteGw.created_at });
1678
+ yield* storeGiftWrapShell(remoteGw);
1667
1679
  return null;
1668
1680
  }
1681
+ let data = null;
1682
+ let kind = "u";
1669
1683
  if (parsed === null || parsed._deleted) {
1670
1684
  kind = "d";
1671
1685
  } else {
@@ -1702,6 +1716,78 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1702
1716
  }
1703
1717
  return collectionName;
1704
1718
  });
1719
+ const reconcileRelay = (url, pubKeys) => Effect8.gen(function* () {
1720
+ yield* Effect8.logDebug("Syncing relay", { relay: url });
1721
+ const { haveIds, needIds } = yield* reconcileWithRelay(
1722
+ storage,
1723
+ relay,
1724
+ url,
1725
+ Array.from(pubKeys)
1726
+ ).pipe(
1727
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1728
+ Effect8.orElseSucceed(() => ({ haveIds: [], needIds: [] }))
1729
+ );
1730
+ yield* Effect8.logDebug("Relay reconciliation result", {
1731
+ relay: url,
1732
+ need: needIds.length,
1733
+ have: haveIds.length
1734
+ });
1735
+ const events = needIds.length > 0 ? yield* relay.fetchEvents(needIds, url).pipe(
1736
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1737
+ Effect8.orElseSucceed(() => [])
1738
+ ) : [];
1739
+ return {
1740
+ events,
1741
+ haveIds: haveIds.map((id) => ({ id, url }))
1742
+ };
1743
+ }).pipe(Effect8.withLogSpan("tablinum.reconcileRelay"));
1744
+ const syncAllRelays = (pubKeys, changedCollections) => Effect8.gen(function* () {
1745
+ const results = yield* Effect8.forEach(
1746
+ relayUrls,
1747
+ (url) => reconcileRelay(url, pubKeys),
1748
+ { concurrency: "unbounded" }
1749
+ );
1750
+ const seen = /* @__PURE__ */ new Set();
1751
+ const allGiftWraps = [];
1752
+ for (const { events } of results) {
1753
+ for (const ev of events) {
1754
+ if (!seen.has(ev.id)) {
1755
+ seen.add(ev.id);
1756
+ allGiftWraps.push(ev);
1757
+ }
1758
+ }
1759
+ }
1760
+ const unwrapped = [];
1761
+ for (const gw of allGiftWraps) {
1762
+ const result = yield* unwrapGiftWrap(gw).pipe(Effect8.orElseSucceed(() => null));
1763
+ if (result) unwrapped.push(result);
1764
+ }
1765
+ unwrapped.sort((a, b) => a.rumor.created_at - b.rumor.created_at || (a.rumor.id < b.rumor.id ? -1 : 1));
1766
+ for (const event of unwrapped) {
1767
+ const collection2 = yield* applyUnwrappedEvent(event).pipe(
1768
+ Effect8.orElseSucceed(() => null)
1769
+ );
1770
+ if (collection2) changedCollections.add(collection2);
1771
+ }
1772
+ const allHaveIds = results.flatMap((r) => r.haveIds);
1773
+ yield* Effect8.forEach(
1774
+ allHaveIds,
1775
+ ({ id, url }) => Effect8.gen(function* () {
1776
+ const gw = yield* storage.getGiftWrap(id);
1777
+ if (!gw?.event) return;
1778
+ yield* relay.publish(gw.event, [url]).pipe(
1779
+ Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1780
+ Effect8.ignore
1781
+ );
1782
+ }),
1783
+ { concurrency: "unbounded", discard: true }
1784
+ );
1785
+ }).pipe(Effect8.withLogSpan("tablinum.syncAllRelays"));
1786
+ const processGiftWrap = (remoteGw) => Effect8.gen(function* () {
1787
+ const uw = yield* unwrapGiftWrap(remoteGw);
1788
+ if (!uw) return null;
1789
+ return yield* applyUnwrappedEvent(uw);
1790
+ });
1705
1791
  const processRealtimeGiftWrap = (remoteGw) => Effect8.gen(function* () {
1706
1792
  const collection2 = yield* processGiftWrap(remoteGw).pipe(Effect8.orElseSucceed(() => null));
1707
1793
  if (collection2) {
@@ -1770,55 +1856,8 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1770
1856
  Effect8.ignore
1771
1857
  );
1772
1858
  }),
1773
- { discard: true }
1859
+ { concurrency: "unbounded", discard: true }
1774
1860
  );
1775
- const syncRelay = (url, pubKeys, changedCollections) => Effect8.gen(function* () {
1776
- yield* Effect8.logDebug("Syncing relay", { relay: url });
1777
- const reconcileResult = yield* Effect8.result(
1778
- reconcileWithRelay(storage, relay, url, Array.from(pubKeys))
1779
- );
1780
- if (reconcileResult._tag === "Failure") {
1781
- onSyncError?.(reconcileResult.failure);
1782
- return;
1783
- }
1784
- const { haveIds, needIds } = reconcileResult.success;
1785
- yield* Effect8.logDebug("Relay reconciliation result", {
1786
- relay: url,
1787
- need: needIds.length,
1788
- have: haveIds.length
1789
- });
1790
- if (needIds.length > 0) {
1791
- const fetched = yield* relay.fetchEvents(needIds, url).pipe(
1792
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1793
- Effect8.orElseSucceed(() => [])
1794
- );
1795
- const sorted = [...fetched].sort((a, b) => a.created_at - b.created_at);
1796
- yield* Effect8.forEach(
1797
- sorted,
1798
- (remoteGw) => Effect8.gen(function* () {
1799
- const collection2 = yield* processGiftWrap(remoteGw).pipe(
1800
- Effect8.orElseSucceed(() => null)
1801
- );
1802
- if (collection2) changedCollections.add(collection2);
1803
- }),
1804
- { discard: true }
1805
- );
1806
- }
1807
- if (haveIds.length > 0) {
1808
- yield* Effect8.forEach(
1809
- haveIds,
1810
- (id) => Effect8.gen(function* () {
1811
- const gw = yield* storage.getGiftWrap(id);
1812
- if (!gw?.event) return;
1813
- yield* relay.publish(gw.event, [url]).pipe(
1814
- Effect8.tapError((err) => Effect8.sync(() => onSyncError?.(err))),
1815
- Effect8.ignore
1816
- );
1817
- }),
1818
- { discard: true }
1819
- );
1820
- }
1821
- }).pipe(Effect8.withLogSpan("tablinum.syncRelay"));
1822
1861
  let healingActive = false;
1823
1862
  const healingEffect = Effect8.gen(function* () {
1824
1863
  if (!healingActive) return;
@@ -1828,9 +1867,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1828
1867
  yield* Effect8.gen(function* () {
1829
1868
  const pubKeys = getSubscriptionPubKeys();
1830
1869
  const changedCollections = /* @__PURE__ */ new Set();
1831
- yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1832
- discard: true
1833
- });
1870
+ yield* syncAllRelays(pubKeys, changedCollections);
1834
1871
  if (changedCollections.size > 0) {
1835
1872
  yield* notifyReplayComplete(watchCtx, [...changedCollections]);
1836
1873
  }
@@ -1844,9 +1881,7 @@ function createSyncHandle(storage, giftWrapHandle, relay, publishQueue, syncStat
1844
1881
  const changedCollections = /* @__PURE__ */ new Set();
1845
1882
  yield* Effect8.gen(function* () {
1846
1883
  const pubKeys = getSubscriptionPubKeys();
1847
- yield* Effect8.forEach(relayUrls, (url) => syncRelay(url, pubKeys, changedCollections), {
1848
- discard: true
1849
- });
1884
+ yield* syncAllRelays(pubKeys, changedCollections);
1850
1885
  yield* publishQueue.flush(relayUrls).pipe(Effect8.ignore);
1851
1886
  }).pipe(
1852
1887
  Effect8.ensuring(
@@ -3,11 +3,10 @@ import { unwrapEvent } from "nostr-tools/nip59";
3
3
  import type { NostrEvent, UnsignedEvent } from "nostr-tools/pure";
4
4
  import { CryptoError } from "../errors.ts";
5
5
  import type { EpochStore } from "../db/epoch.ts";
6
- type Rumor = ReturnType<typeof unwrapEvent>;
6
+ export type Rumor = ReturnType<typeof unwrapEvent>;
7
7
  export interface GiftWrapHandle {
8
8
  readonly wrap: (rumor: Partial<UnsignedEvent>) => Effect.Effect<NostrEvent, CryptoError>;
9
9
  readonly unwrap: (giftWrap: NostrEvent) => Effect.Effect<Rumor, CryptoError>;
10
10
  }
11
11
  export declare function createGiftWrapHandle(senderPrivateKey: Uint8Array, recipientPublicKey: string, decryptionPrivateKey: Uint8Array): GiftWrapHandle;
12
12
  export declare function createEpochGiftWrapHandle(senderPrivateKey: Uint8Array, epochStore: EpochStore): GiftWrapHandle;
13
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tablinum",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/kevmodrome/tablinum.git",