@entifix/react-integration 0.4.0 → 0.4.2

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.
@@ -1,6 +1,6 @@
1
- import type { PendingTransactionStore, TransactionRecord } from '@entifix/transactions';
1
+ import type { PendingTransactionStore, TransactionRecord, TransactionStatusReader } from '@entifix/transactions';
2
2
  import type { ReactiveChannel } from './reactive-channel.js';
3
- export interface UseTransactionSettlementOptions {
3
+ interface SettlementBase {
4
4
  /** The stream outcomes arrive on, and whose reconnects trigger a re-query. */
5
5
  channel: ReactiveChannel;
6
6
  /**
@@ -11,10 +11,25 @@ export interface UseTransactionSettlementOptions {
11
11
  * upward. Same seam, and the same reason, as `EntityDraftStore` for autosave.
12
12
  */
13
13
  pending: PendingTransactionStore;
14
- /** Reads one record. `undefined` means *not tracked yet* — never *failed*. */
15
- read: (transactionId: string) => Promise<TransactionRecord | undefined>;
16
14
  pollIntervalMs?: number;
17
15
  }
16
+ /**
17
+ * How a record is read, and exactly one way.
18
+ *
19
+ * `reader` is the port, built once where the host composes its adapters, so the
20
+ * component calling this hook imports no transport. `read` is the same thing as
21
+ * a bare function, for a caller that already holds one.
22
+ */
23
+ type RecordSource = {
24
+ /** Reads one record. `undefined` means *not tracked yet* — never *failed*. */
25
+ reader: TransactionStatusReader;
26
+ read?: never;
27
+ } | {
28
+ /** Reads one record. `undefined` means *not tracked yet* — never *failed*. */
29
+ read: (transactionId: string) => Promise<TransactionRecord | undefined>;
30
+ reader?: never;
31
+ };
32
+ export type UseTransactionSettlementOptions = SettlementBase & RecordSource;
18
33
  /**
19
34
  * Settles the optimistic writes this browser started.
20
35
  *
@@ -37,5 +52,6 @@ export interface UseTransactionSettlementOptions {
37
52
  * un-renders a healthy write at exactly the moment nobody can tell a UI bug from
38
53
  * an outage. Only `FAILED` and `STALE` are terminal.
39
54
  */
40
- export declare function useTransactionSettlement({ channel, pending, read, pollIntervalMs, }: UseTransactionSettlementOptions): void;
55
+ export declare function useTransactionSettlement({ channel, pending, pollIntervalMs, ...source }: UseTransactionSettlementOptions): void;
56
+ export {};
41
57
  //# sourceMappingURL=use-transaction-settlement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-transaction-settlement.d.ts","sourceRoot":"","sources":["../../src/reactive/use-transaction-settlement.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,uBAAuB,EACvB,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAK/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAK7D,MAAM,WAAW,+BAA+B;IAC9C,8EAA8E;IAC9E,OAAO,EAAE,eAAe,CAAC;IACzB;;;;;;OAMG;IACH,OAAO,EAAE,uBAAuB,CAAC;IACjC,8EAA8E;IAC9E,IAAI,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAC;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,OAAO,EACP,IAAI,EACJ,cAAgC,GACjC,EAAE,+BAA+B,GAAG,IAAI,CA2GxC"}
1
+ {"version":3,"file":"use-transaction-settlement.d.ts","sourceRoot":"","sources":["../../src/reactive/use-transaction-settlement.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,uBAAuB,EACvB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAK7D,UAAU,cAAc;IACtB,8EAA8E;IAC9E,OAAO,EAAE,eAAe,CAAC;IACzB;;;;;;OAMG;IACH,OAAO,EAAE,uBAAuB,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,KAAK,YAAY,GACb;IACE,8EAA8E;IAC9E,MAAM,EAAE,uBAAuB,CAAC;IAChC,IAAI,CAAC,EAAE,KAAK,CAAC;CACd,GACD;IACE,8EAA8E;IAC9E,IAAI,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAC;IACxE,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB,CAAC;AAEN,MAAM,MAAM,+BAA+B,GAAG,cAAc,GAAG,YAAY,CAAC;AAU5E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,OAAO,EACP,cAAgC,EAChC,GAAG,MAAM,EACV,EAAE,+BAA+B,GAAG,IAAI,CA4GxC"}
@@ -1,8 +1,15 @@
1
1
  'use client';
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
2
3
  import { useQueryClient } from '@tanstack/react-query';
4
+ import { Effect } from 'effect';
3
5
  import { useCallback, useEffect, useRef } from 'react';
4
6
  import { entityQueryScopeFor } from '../query/entity-query-key.js';
5
7
  /** How often to re-ask while anything is pending. */ const DEFAULT_POLL_MS = 30000;
8
+ const readerOf = (source)=>{
9
+ if (source.reader === undefined) return source.read;
10
+ const { reader } = source;
11
+ return (transactionId)=>Effect.runPromise(reader.read(transactionId));
12
+ };
6
13
  /**
7
14
  * Settles the optimistic writes this browser started.
8
15
  *
@@ -24,7 +31,13 @@ import { entityQueryScopeFor } from '../query/entity-query-key.js';
24
31
  * no event is ever published and the tracker holds nothing. Rolling back there
25
32
  * un-renders a healthy write at exactly the moment nobody can tell a UI bug from
26
33
  * an outage. Only `FAILED` and `STALE` are terminal.
27
- */ export function useTransactionSettlement({ channel, pending, read, pollIntervalMs = DEFAULT_POLL_MS }) {
34
+ */ export function useTransactionSettlement(_0) {
35
+ let { channel, pending, pollIntervalMs = DEFAULT_POLL_MS } = _0, source = _object_without_properties_loose(_0, [
36
+ "channel",
37
+ "pending",
38
+ "pollIntervalMs"
39
+ ]);
40
+ const read = readerOf(source);
28
41
  const queryClient = useQueryClient();
29
42
  // The store is rebuilt whenever an entry changes and `read` is typically
30
43
  // inline, so both are held in refs: keying the subscription on their identity
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/reactive/use-transaction-settlement.tsx"],"sourcesContent":["'use client';\n\nimport type {\n PendingEntry,\n PendingTransactionStore,\n TransactionRecord,\n} from '@entifix/transactions';\nimport { useQueryClient } from '@tanstack/react-query';\nimport { useCallback, useEffect, useRef } from 'react';\n\nimport { entityQueryScopeFor } from '../query/entity-query-key.js';\nimport type { ReactiveChannel } from './reactive-channel.js';\n\n/** How often to re-ask while anything is pending. */\nconst DEFAULT_POLL_MS = 30_000;\n\nexport interface UseTransactionSettlementOptions {\n /** The stream outcomes arrive on, and whose reconnects trigger a re-query. */\n channel: ReactiveChannel;\n /**\n * The writes being watched.\n *\n * Passed in rather than imported: the store is a shell concern\n * (`@entifix/next-shell`, T4) and this hook is T3, which may not depend\n * upward. Same seam, and the same reason, as `EntityDraftStore` for autosave.\n */\n pending: PendingTransactionStore;\n /** Reads one record. `undefined` means *not tracked yet* — never *failed*. */\n read: (transactionId: string) => Promise<TransactionRecord | undefined>;\n pollIntervalMs?: number;\n}\n\n/**\n * Settles the optimistic writes this browser started.\n *\n * Three things drive it, and they exist for three different failures:\n *\n * - **The stream**, the fast path: `transaction.completed` and\n * `transaction.failed` carry the correlation id, so an outcome lands in the\n * moment it happens.\n * - **`onConnect`**, because the stream does not replay (ADR 0036). It fires on\n * every open *including for a listener registered while already connected*, so\n * a page load holding a restored pending set and an in-session reconnect take\n * one path rather than two.\n * - **An interval**, because `STALE` is written by the recovery sweep and emits\n * no event at all — it is reachable only by asking. It runs only while\n * something is pending, so an idle workspace polls nothing.\n *\n * ⚠️ **A missing record keeps the entry pending.** `accepted` reaches the\n * tracker over the bus, so with the broker down the entity write commits while\n * no event is ever published and the tracker holds nothing. Rolling back there\n * un-renders a healthy write at exactly the moment nobody can tell a UI bug from\n * an outage. Only `FAILED` and `STALE` are terminal.\n */\nexport function useTransactionSettlement({\n channel,\n pending,\n read,\n pollIntervalMs = DEFAULT_POLL_MS,\n}: UseTransactionSettlementOptions): void {\n const queryClient = useQueryClient();\n\n // The store is rebuilt whenever an entry changes and `read` is typically\n // inline, so both are held in refs: keying the subscription on their identity\n // would tear down and re-open the stream every time a write settled.\n const pendingRef = useRef(pending);\n const readRef = useRef(read);\n\n // Refs may not be written during render. This runs before the effects below,\n // since effects fire in declaration order, so they always see the latest.\n useEffect(() => {\n pendingRef.current = pending;\n readRef.current = read;\n });\n\n const invalidate = useCallback(\n (entity: string) =>\n void queryClient.invalidateQueries({\n queryKey: entityQueryScopeFor(entity),\n }),\n [queryClient],\n );\n\n /**\n * Applies whatever the tracker currently says about one entry.\n *\n * The single place the terminal-state rule lives — the stream path, the\n * reconnect path and the interval all come through here, so there is one\n * answer to \"what does this state mean\" rather than three that can drift.\n */\n const reconcile = useCallback(\n async (entry: PendingEntry): Promise<void> => {\n const record = await readRef.current(entry.transactionId).catch(\n // The tracker being unreachable says nothing about the write. Keeping\n // the entry is what makes a service restart converge instead of dropping\n // every optimistic row on the floor.\n () => undefined,\n );\n\n // ⚠️ Not tracked yet, or still running. Either way it is not over, and\n // this is the branch that must not be turned into a rollback.\n if (record === undefined || record.state === 'PENDING') return;\n\n if (record.state === 'COMPLETED') {\n pendingRef.current.settle(entry.transactionId);\n } else {\n // `record.error` is free text from whatever threw — diagnostic detail\n // beside a translated headline, never the message itself.\n pendingRef.current.fail(entry.transactionId, record.error);\n }\n invalidate(entry.entity);\n },\n [invalidate],\n );\n\n const reconcileAll = useCallback(() => {\n for (const entry of pendingRef.current.entries) {\n if (entry.state === 'pending') void reconcile(entry);\n }\n }, [reconcile]);\n\n useEffect(() => {\n const stopListening = channel.subscribe(event => {\n const transactionId = event.correlationId;\n if (transactionId === undefined) return;\n\n const entry = pendingRef.current.entries.find(\n candidate => candidate.transactionId === transactionId,\n );\n // An id this browser never started belongs to another tab or another\n // session — the stream is organization-scoped, not connection-scoped.\n if (entry === undefined) return;\n\n if (event.name === 'transaction.completed') {\n pendingRef.current.settle(transactionId);\n invalidate(event.data.entity);\n return;\n }\n\n if (event.name === 'transaction.failed') {\n // The frame carries no reason: `EntityChangeEvent` collapses four states\n // into created/deleted and holds no `error`. So the record is re-read\n // rather than the payload widened (ADR 0036's hint-versus-record split).\n void reconcile(entry);\n }\n });\n\n const stopWatchingConnections = channel.onConnect(reconcileAll);\n\n return () => {\n stopListening();\n stopWatchingConnections();\n };\n }, [channel, invalidate, reconcile, reconcileAll]);\n\n // Keyed on whether anything is watched at all, not on the entries: that flips\n // once per transition rather than on every settle, so the timer is not torn\n // down and rebuilt each time a write lands.\n const watching = pending.entries.length > 0;\n\n useEffect(() => {\n if (!watching) return;\n\n const timer = setInterval(reconcileAll, pollIntervalMs);\n return () => clearInterval(timer);\n }, [watching, pollIntervalMs, reconcileAll]);\n}\n"],"names":["useQueryClient","useCallback","useEffect","useRef","entityQueryScopeFor","DEFAULT_POLL_MS","useTransactionSettlement","channel","pending","read","pollIntervalMs","queryClient","pendingRef","readRef","current","invalidate","entity","invalidateQueries","queryKey","reconcile","entry","record","transactionId","catch","undefined","state","settle","fail","error","reconcileAll","entries","stopListening","subscribe","event","correlationId","find","candidate","name","data","stopWatchingConnections","onConnect","watching","length","timer","setInterval","clearInterval"],"mappings":"AAAA;AAOA,SAASA,cAAc,QAAQ,wBAAwB;AACvD,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,QAAQ,QAAQ;AAEvD,SAASC,mBAAmB,QAAQ,+BAA+B;AAGnE,mDAAmD,GACnD,MAAMC,kBAAkB;AAkBxB;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,OAAO,SAASC,yBAAyB,EACvCC,OAAO,EACPC,OAAO,EACPC,IAAI,EACJC,iBAAiBL,eAAe,EACA;IAChC,MAAMM,cAAcX;IAEpB,yEAAyE;IACzE,8EAA8E;IAC9E,qEAAqE;IACrE,MAAMY,aAAaT,OAAOK;IAC1B,MAAMK,UAAUV,OAAOM;IAEvB,6EAA6E;IAC7E,0EAA0E;IAC1EP,UAAU;QACRU,WAAWE,OAAO,GAAGN;QACrBK,QAAQC,OAAO,GAAGL;IACpB;IAEA,MAAMM,aAAad,YACjB,CAACe,SACC,KAAKL,YAAYM,iBAAiB,CAAC;YACjCC,UAAUd,oBAAoBY;QAChC,IACF;QAACL;KAAY;IAGf;;;;;;GAMC,GACD,MAAMQ,YAAYlB,YAChB,OAAOmB;QACL,MAAMC,SAAS,MAAMR,QAAQC,OAAO,CAACM,MAAME,aAAa,EAAEC,KAAK,CAC7D,sEAAsE;QACtE,yEAAyE;QACzE,qCAAqC;QACrC,IAAMC;QAGR,uEAAuE;QACvE,8DAA8D;QAC9D,IAAIH,WAAWG,aAAaH,OAAOI,KAAK,KAAK,WAAW;QAExD,IAAIJ,OAAOI,KAAK,KAAK,aAAa;YAChCb,WAAWE,OAAO,CAACY,MAAM,CAACN,MAAME,aAAa;QAC/C,OAAO;YACL,sEAAsE;YACtE,0DAA0D;YAC1DV,WAAWE,OAAO,CAACa,IAAI,CAACP,MAAME,aAAa,EAAED,OAAOO,KAAK;QAC3D;QACAb,WAAWK,MAAMJ,MAAM;IACzB,GACA;QAACD;KAAW;IAGd,MAAMc,eAAe5B,YAAY;QAC/B,KAAK,MAAMmB,SAASR,WAAWE,OAAO,CAACgB,OAAO,CAAE;YAC9C,IAAIV,MAAMK,KAAK,KAAK,WAAW,KAAKN,UAAUC;QAChD;IACF,GAAG;QAACD;KAAU;IAEdjB,UAAU;QACR,MAAM6B,gBAAgBxB,QAAQyB,SAAS,CAACC,CAAAA;YACtC,MAAMX,gBAAgBW,MAAMC,aAAa;YACzC,IAAIZ,kBAAkBE,WAAW;YAEjC,MAAMJ,QAAQR,WAAWE,OAAO,CAACgB,OAAO,CAACK,IAAI,CAC3CC,CAAAA,YAAaA,UAAUd,aAAa,KAAKA;YAE3C,qEAAqE;YACrE,sEAAsE;YACtE,IAAIF,UAAUI,WAAW;YAEzB,IAAIS,MAAMI,IAAI,KAAK,yBAAyB;gBAC1CzB,WAAWE,OAAO,CAACY,MAAM,CAACJ;gBAC1BP,WAAWkB,MAAMK,IAAI,CAACtB,MAAM;gBAC5B;YACF;YAEA,IAAIiB,MAAMI,IAAI,KAAK,sBAAsB;gBACvC,yEAAyE;gBACzE,sEAAsE;gBACtE,yEAAyE;gBACzE,KAAKlB,UAAUC;YACjB;QACF;QAEA,MAAMmB,0BAA0BhC,QAAQiC,SAAS,CAACX;QAElD,OAAO;YACLE;YACAQ;QACF;IACF,GAAG;QAAChC;QAASQ;QAAYI;QAAWU;KAAa;IAEjD,8EAA8E;IAC9E,4EAA4E;IAC5E,4CAA4C;IAC5C,MAAMY,WAAWjC,QAAQsB,OAAO,CAACY,MAAM,GAAG;IAE1CxC,UAAU;QACR,IAAI,CAACuC,UAAU;QAEf,MAAME,QAAQC,YAAYf,cAAcnB;QACxC,OAAO,IAAMmC,cAAcF;IAC7B,GAAG;QAACF;QAAU/B;QAAgBmB;KAAa;AAC7C"}
1
+ {"version":3,"sources":["../../src/reactive/use-transaction-settlement.tsx"],"sourcesContent":["'use client';\n\nimport type {\n PendingEntry,\n PendingTransactionStore,\n TransactionRecord,\n TransactionStatusReader,\n} from '@entifix/transactions';\nimport { useQueryClient } from '@tanstack/react-query';\nimport { Effect } from 'effect';\nimport { useCallback, useEffect, useRef } from 'react';\n\nimport { entityQueryScopeFor } from '../query/entity-query-key.js';\nimport type { ReactiveChannel } from './reactive-channel.js';\n\n/** How often to re-ask while anything is pending. */\nconst DEFAULT_POLL_MS = 30_000;\n\ninterface SettlementBase {\n /** The stream outcomes arrive on, and whose reconnects trigger a re-query. */\n channel: ReactiveChannel;\n /**\n * The writes being watched.\n *\n * Passed in rather than imported: the store is a shell concern\n * (`@entifix/next-shell`, T4) and this hook is T3, which may not depend\n * upward. Same seam, and the same reason, as `EntityDraftStore` for autosave.\n */\n pending: PendingTransactionStore;\n pollIntervalMs?: number;\n}\n\n/**\n * How a record is read, and exactly one way.\n *\n * `reader` is the port, built once where the host composes its adapters, so the\n * component calling this hook imports no transport. `read` is the same thing as\n * a bare function, for a caller that already holds one.\n */\ntype RecordSource =\n | {\n /** Reads one record. `undefined` means *not tracked yet* — never *failed*. */\n reader: TransactionStatusReader;\n read?: never;\n }\n | {\n /** Reads one record. `undefined` means *not tracked yet* — never *failed*. */\n read: (transactionId: string) => Promise<TransactionRecord | undefined>;\n reader?: never;\n };\n\nexport type UseTransactionSettlementOptions = SettlementBase & RecordSource;\n\nconst readerOf = (\n source: RecordSource,\n): ((transactionId: string) => Promise<TransactionRecord | undefined>) => {\n if (source.reader === undefined) return source.read;\n const { reader } = source;\n return transactionId => Effect.runPromise(reader.read(transactionId));\n};\n\n/**\n * Settles the optimistic writes this browser started.\n *\n * Three things drive it, and they exist for three different failures:\n *\n * - **The stream**, the fast path: `transaction.completed` and\n * `transaction.failed` carry the correlation id, so an outcome lands in the\n * moment it happens.\n * - **`onConnect`**, because the stream does not replay (ADR 0036). It fires on\n * every open *including for a listener registered while already connected*, so\n * a page load holding a restored pending set and an in-session reconnect take\n * one path rather than two.\n * - **An interval**, because `STALE` is written by the recovery sweep and emits\n * no event at all — it is reachable only by asking. It runs only while\n * something is pending, so an idle workspace polls nothing.\n *\n * ⚠️ **A missing record keeps the entry pending.** `accepted` reaches the\n * tracker over the bus, so with the broker down the entity write commits while\n * no event is ever published and the tracker holds nothing. Rolling back there\n * un-renders a healthy write at exactly the moment nobody can tell a UI bug from\n * an outage. Only `FAILED` and `STALE` are terminal.\n */\nexport function useTransactionSettlement({\n channel,\n pending,\n pollIntervalMs = DEFAULT_POLL_MS,\n ...source\n}: UseTransactionSettlementOptions): void {\n const read = readerOf(source);\n const queryClient = useQueryClient();\n\n // The store is rebuilt whenever an entry changes and `read` is typically\n // inline, so both are held in refs: keying the subscription on their identity\n // would tear down and re-open the stream every time a write settled.\n const pendingRef = useRef(pending);\n const readRef = useRef(read);\n\n // Refs may not be written during render. This runs before the effects below,\n // since effects fire in declaration order, so they always see the latest.\n useEffect(() => {\n pendingRef.current = pending;\n readRef.current = read;\n });\n\n const invalidate = useCallback(\n (entity: string) =>\n void queryClient.invalidateQueries({\n queryKey: entityQueryScopeFor(entity),\n }),\n [queryClient],\n );\n\n /**\n * Applies whatever the tracker currently says about one entry.\n *\n * The single place the terminal-state rule lives — the stream path, the\n * reconnect path and the interval all come through here, so there is one\n * answer to \"what does this state mean\" rather than three that can drift.\n */\n const reconcile = useCallback(\n async (entry: PendingEntry): Promise<void> => {\n const record = await readRef.current(entry.transactionId).catch(\n // The tracker being unreachable says nothing about the write. Keeping\n // the entry is what makes a service restart converge instead of dropping\n // every optimistic row on the floor.\n () => undefined,\n );\n\n // ⚠️ Not tracked yet, or still running. Either way it is not over, and\n // this is the branch that must not be turned into a rollback.\n if (record === undefined || record.state === 'PENDING') return;\n\n if (record.state === 'COMPLETED') {\n pendingRef.current.settle(entry.transactionId);\n } else {\n // `record.error` is free text from whatever threw — diagnostic detail\n // beside a translated headline, never the message itself.\n pendingRef.current.fail(entry.transactionId, record.error);\n }\n invalidate(entry.entity);\n },\n [invalidate],\n );\n\n const reconcileAll = useCallback(() => {\n for (const entry of pendingRef.current.entries) {\n if (entry.state === 'pending') void reconcile(entry);\n }\n }, [reconcile]);\n\n useEffect(() => {\n const stopListening = channel.subscribe(event => {\n const transactionId = event.correlationId;\n if (transactionId === undefined) return;\n\n const entry = pendingRef.current.entries.find(\n candidate => candidate.transactionId === transactionId,\n );\n // An id this browser never started belongs to another tab or another\n // session — the stream is organization-scoped, not connection-scoped.\n if (entry === undefined) return;\n\n if (event.name === 'transaction.completed') {\n pendingRef.current.settle(transactionId);\n invalidate(event.data.entity);\n return;\n }\n\n if (event.name === 'transaction.failed') {\n // The frame carries no reason: `EntityChangeEvent` collapses four states\n // into created/deleted and holds no `error`. So the record is re-read\n // rather than the payload widened (ADR 0036's hint-versus-record split).\n void reconcile(entry);\n }\n });\n\n const stopWatchingConnections = channel.onConnect(reconcileAll);\n\n return () => {\n stopListening();\n stopWatchingConnections();\n };\n }, [channel, invalidate, reconcile, reconcileAll]);\n\n // Keyed on whether anything is watched at all, not on the entries: that flips\n // once per transition rather than on every settle, so the timer is not torn\n // down and rebuilt each time a write lands.\n const watching = pending.entries.length > 0;\n\n useEffect(() => {\n if (!watching) return;\n\n const timer = setInterval(reconcileAll, pollIntervalMs);\n return () => clearInterval(timer);\n }, [watching, pollIntervalMs, reconcileAll]);\n}\n"],"names":["useQueryClient","Effect","useCallback","useEffect","useRef","entityQueryScopeFor","DEFAULT_POLL_MS","readerOf","source","reader","undefined","read","transactionId","runPromise","useTransactionSettlement","channel","pending","pollIntervalMs","queryClient","pendingRef","readRef","current","invalidate","entity","invalidateQueries","queryKey","reconcile","entry","record","catch","state","settle","fail","error","reconcileAll","entries","stopListening","subscribe","event","correlationId","find","candidate","name","data","stopWatchingConnections","onConnect","watching","length","timer","setInterval","clearInterval"],"mappings":"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"}
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  }
22
22
  }
23
23
  },
24
- "version": "0.4.0",
24
+ "version": "0.4.2",
25
25
  "description": "React hooks that bind entities to data: TanStack Query and Form over the entity adapters, drafts, mutations and live invalidation.",
26
26
  "license": "MIT",
27
27
  "repository": {
@@ -55,16 +55,16 @@
55
55
  "LICENSE"
56
56
  ],
57
57
  "dependencies": {
58
- "@entifix/business": "0.4.0",
59
- "@entifix/core": "0.4.0",
60
- "@entifix/transactions": "0.4.0"
58
+ "@entifix/business": "0.4.2",
59
+ "@entifix/core": "0.4.2",
60
+ "@entifix/transactions": "0.4.2"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@tanstack/react-form": "1.33.5",
64
64
  "@tanstack/react-query": "^5.104.5",
65
65
  "effect": "4.0.5",
66
66
  "react": "19.3.1",
67
- "@entifix/testing-unit": "0.4.0"
67
+ "@entifix/testing-unit": "0.4.2"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@tanstack/react-form": ">=1.33 <2",