@novasamatech/host-substrate-chain-connection 0.7.6 → 0.7.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.
@@ -106,15 +106,31 @@ export const createChainConnection = ({ resolve, clientOptions, createProvider,
106
106
  },
107
107
  getProvider(chain) {
108
108
  return getSyncProvider(onResult => {
109
+ let teardownCalled = false;
110
+ let pendingUnlock = null;
111
+ // Idempotent: subsequent calls (e.g. teardown after disconnect) are no-ops.
112
+ const releaseUnlock = () => {
113
+ const unlock = pendingUnlock;
114
+ pendingUnlock = null;
115
+ unlock?.();
116
+ };
109
117
  rawAcquire(chain)
110
118
  .then(({ pooled, unlock }) => {
111
- onResult((onMessage, _onHalt) => pooled.provider.branch(unlock)(onMessage));
119
+ if (teardownCalled) {
120
+ unlock();
121
+ onResult(null);
122
+ return;
123
+ }
124
+ pendingUnlock = unlock;
125
+ onResult((onMessage, _onHalt) => pooled.provider.branch(releaseUnlock)(onMessage));
112
126
  })
113
127
  .catch(() => {
114
128
  onResult(null);
115
129
  });
130
+ // Covers teardown without disconnect, and teardown before acquire resolves.
116
131
  return () => {
117
- /* empty */
132
+ teardownCalled = true;
133
+ releaseUnlock();
118
134
  };
119
135
  });
120
136
  },
@@ -39,7 +39,10 @@ export const createMetadataCache = (options) => {
39
39
  setMetadata(key, metadata) {
40
40
  const k = cacheKey(chainId, key);
41
41
  memory.set(k, metadata);
42
- storage?.write(k, bytesToBase64(metadata));
42
+ // setMetadata is fire-and-forget by contract; log persist failures.
43
+ storage?.write(k, bytesToBase64(metadata)).orTee(error => {
44
+ console.error(`[metadataCache] failed to persist metadata for ${k}:`, error);
45
+ });
43
46
  },
44
47
  };
45
48
  },
@@ -3,8 +3,8 @@ import { describe, expect, it, vi } from 'vitest';
3
3
  import { createMetadataCache } from './metadataCache.js';
4
4
  const createMockStorage = () => ({
5
5
  read: vi.fn(),
6
- write: vi.fn(),
7
- clear: vi.fn(),
6
+ write: vi.fn(() => okAsync(undefined)),
7
+ clear: vi.fn(() => okAsync(undefined)),
8
8
  // eslint-disable-next-line @typescript-eslint/no-empty-function
9
9
  subscribe: vi.fn(() => () => { }),
10
10
  });
@@ -69,6 +69,15 @@ export const withSubscriptionReplay = (provider, onReconnect) => onMessage => {
69
69
  onMessage(message);
70
70
  });
71
71
  const unsubReconnect = onReconnect(() => {
72
+ // Replay unconfirmed subscribes first: their `reconnectFor === null`, so
73
+ // they're disjoint from the entries inserted by the active-replay loop
74
+ // below (which all carry a non-null `reconnectFor`).
75
+ for (const [, pending] of pendingSubscriptions) {
76
+ if (pending.reconnectFor === null)
77
+ conn.send(pending.payload);
78
+ }
79
+ // Replay confirmed subscriptions: the server returns a fresh subId, which
80
+ // we map back to the consumer's stable subId.
72
81
  for (const [consumerSubId, sub] of activeSubscriptions) {
73
82
  pendingSubscriptions.set(sub.id, { payload: sub.payload, reconnectFor: consumerSubId });
74
83
  conn.send(sub.payload);
@@ -61,15 +61,16 @@ describe('withSubscriptionReplay', () => {
61
61
  control.triggerReconnect();
62
62
  expect(mock.send).not.toHaveBeenCalled();
63
63
  });
64
- it('does not replay pending subscriptions (no server response yet) on reconnect', () => {
64
+ it('replays pending subscriptions (no server response yet) on reconnect', () => {
65
65
  const mock = createMockProvider();
66
66
  const control = createReconnectControl();
67
67
  const conn = withSubscriptionReplay(mock.provider, control.onReconnect)(vi.fn());
68
- conn.send(req(1, 'statement_subscribeStatement'));
68
+ const subscribeMsg = req(1, 'statement_subscribeStatement');
69
+ conn.send(subscribeMsg);
69
70
  // intentionally no simulateMessage — subscription not confirmed by server
70
71
  mock.send.mockClear();
71
72
  control.triggerReconnect();
72
- expect(mock.send).not.toHaveBeenCalled();
73
+ expect(mock.send).toHaveBeenCalledWith(subscribeMsg);
73
74
  });
74
75
  it('replays active subscriptions on reconnect', () => {
75
76
  const mock = createMockProvider();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-substrate-chain-connection",
3
3
  "type": "module",
4
- "version": "0.7.6",
4
+ "version": "0.7.7",
5
5
  "description": "Chain connection pool with ref counting and provider branching for Polkadot API",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -25,7 +25,7 @@
25
25
  "README.md"
26
26
  ],
27
27
  "dependencies": {
28
- "@novasamatech/storage-adapter": "0.7.6",
28
+ "@novasamatech/storage-adapter": "0.7.7",
29
29
  "@polkadot-api/ws-provider": "^0.9.0",
30
30
  "@polkadot-api/json-rpc-provider": "^0.2.0",
31
31
  "@polkadot-api/json-rpc-provider-proxy": "^0.4.0",