@electric-sql/react 1.0.0-beta.5 → 1.0.0-beta.6

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.
@@ -144,6 +144,9 @@ function parseShapeData(shape) {
144
144
  error: shape.error
145
145
  };
146
146
  }
147
+ function shapeResultChanged(oldRes, newRes) {
148
+ return !oldRes || oldRes.isLoading !== newRes.isLoading || oldRes.lastSyncedAt !== newRes.lastSyncedAt || oldRes.isError !== newRes.isError || oldRes.error !== newRes.error || oldRes.shape.lastOffset !== newRes.shape.lastOffset || oldRes.shape.handle !== newRes.shape.handle;
149
+ }
147
150
  function identity(arg) {
148
151
  return arg;
149
152
  }
@@ -158,12 +161,22 @@ function useShape(_a) {
158
161
  );
159
162
  const shape = getShape(shapeStream);
160
163
  const useShapeData = import_react.default.useMemo(() => {
161
- let latestShapeData = parseShapeData(shape);
162
- const getSnapshot = () => latestShapeData;
163
- const subscribe = (onStoreChange) => shapeSubscribe(shape, () => {
164
- latestShapeData = parseShapeData(shape);
165
- onStoreChange();
166
- });
164
+ let latestShapeData;
165
+ const getSnapshot = () => {
166
+ latestShapeData != null ? latestShapeData : latestShapeData = parseShapeData(shape);
167
+ return latestShapeData;
168
+ };
169
+ const subscribe = (onStoreChange) => {
170
+ const newShapeData = parseShapeData(shape);
171
+ if (shapeResultChanged(latestShapeData, newShapeData)) {
172
+ latestShapeData = newShapeData;
173
+ onStoreChange();
174
+ }
175
+ return shapeSubscribe(shape, () => {
176
+ latestShapeData = parseShapeData(shape);
177
+ onStoreChange();
178
+ });
179
+ };
167
180
  return () => {
168
181
  return (0, import_with_selector.useSyncExternalStoreWithSelector)(
169
182
  subscribe,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/react-hooks.tsx"],"sourcesContent":["export * from './react-hooks'\n","import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n GetExtensions,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\ntype UnknownShape = Shape<Row<unknown>>\ntype UnknownShapeStream = ShapeStream<Row<unknown>>\n\nconst streamCache = new Map<string, UnknownShapeStream>()\nconst shapeCache = new Map<UnknownShapeStream, UnknownShape>()\n\nexport async function preloadShape<T extends Row<unknown> = Row>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.rows\n return shape\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sortObjectKeys(obj: any): any {\n if (typeof obj !== `object` || obj === null) return obj\n\n if (Array.isArray(obj)) {\n return obj.map(sortObjectKeys)\n }\n\n return (\n Object.keys(obj)\n .sort()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .reduce<Record<string, any>>((sorted, key) => {\n sorted[key] = sortObjectKeys(obj[key])\n return sorted\n }, {})\n )\n}\n\nexport function sortedOptionsHash<T>(options: ShapeStreamOptions<T>): string {\n return JSON.stringify(sortObjectKeys(options))\n}\n\nexport function getShapeStream<T extends Row<unknown>>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return it if valid\n if (streamCache.has(shapeHash)) {\n const stream = streamCache.get(shapeHash)! as ShapeStream<T>\n if (!stream.options.signal?.aborted) {\n return stream\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(shapeHash)\n shapeCache.delete(stream)\n }\n\n const newShapeStream = new ShapeStream<T>(options)\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n}\n\nexport function getShape<T extends Row<unknown>>(\n shapeStream: ShapeStream<T>\n): Shape<T> {\n // If the stream is already cached, return it if valid\n if (shapeCache.has(shapeStream)) {\n if (!shapeStream.options.signal?.aborted) {\n return shapeCache.get(shapeStream)! as Shape<T>\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(sortedOptionsHash(shapeStream.options))\n shapeCache.delete(shapeStream)\n }\n\n const newShape = new Shape<T>(shapeStream)\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n}\n\nexport interface UseShapeResult<T extends Row<unknown> = Row> {\n /**\n * The array of rows that make up the Shape.\n * @type {T[]}\n */\n data: T[]\n /**\n * The Shape instance used by this useShape\n * @type {Shape<T>}\n */\n shape: Shape<T>\n /**\n * The ShapeStream instance used by this Shape\n * @type {ShapeStream<T>}\n */\n stream: ShapeStream<T>\n /** True during initial fetch. False afterwise. */\n isLoading: boolean\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt?: number\n error: Shape<T>[`error`]\n isError: boolean\n}\n\nfunction shapeSubscribe<T extends Row<unknown>>(\n shape: Shape<T>,\n callback: () => void\n) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row<unknown>>(\n shape: Shape<T>\n): UseShapeResult<T> {\n return {\n data: shape.currentRows,\n isLoading: shape.isLoading(),\n lastSyncedAt: shape.lastSyncedAt(),\n isError: shape.error !== false,\n shape,\n stream: shape.stream as ShapeStream<T>,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row<unknown>, Selection>\n extends ShapeStreamOptions<GetExtensions<SourceData>> {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row<unknown> = Row,\n Selection = UseShapeResult<SourceData>,\n>({\n selector = identity as (arg: UseShapeResult<SourceData>) => Selection,\n ...options\n}: UseShapeOptions<SourceData, Selection>): Selection {\n const shapeStream = getShapeStream<SourceData>(\n options as ShapeStreamOptions<GetExtensions<SourceData>>\n )\n const shape = getShape<SourceData>(shapeStream)\n\n const useShapeData = React.useMemo(() => {\n let latestShapeData = parseShapeData(shape)\n const getSnapshot = () => latestShapeData\n const subscribe = (onStoreChange: () => void) =>\n shapeSubscribe(shape, () => {\n latestShapeData = parseShapeData(shape)\n onStoreChange()\n })\n\n return () => {\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n selector\n )\n }\n }, [shape, selector])\n\n return useShapeData()\n}\n"],"mappings":"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","names":["React"]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/react-hooks.tsx"],"sourcesContent":["export * from './react-hooks'\n","import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n GetExtensions,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\ntype UnknownShape = Shape<Row<unknown>>\ntype UnknownShapeStream = ShapeStream<Row<unknown>>\n\nconst streamCache = new Map<string, UnknownShapeStream>()\nconst shapeCache = new Map<UnknownShapeStream, UnknownShape>()\n\nexport async function preloadShape<T extends Row<unknown> = Row>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.rows\n return shape\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sortObjectKeys(obj: any): any {\n if (typeof obj !== `object` || obj === null) return obj\n\n if (Array.isArray(obj)) {\n return obj.map(sortObjectKeys)\n }\n\n return (\n Object.keys(obj)\n .sort()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .reduce<Record<string, any>>((sorted, key) => {\n sorted[key] = sortObjectKeys(obj[key])\n return sorted\n }, {})\n )\n}\n\nexport function sortedOptionsHash<T>(options: ShapeStreamOptions<T>): string {\n return JSON.stringify(sortObjectKeys(options))\n}\n\nexport function getShapeStream<T extends Row<unknown>>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return it if valid\n if (streamCache.has(shapeHash)) {\n const stream = streamCache.get(shapeHash)! as ShapeStream<T>\n if (!stream.options.signal?.aborted) {\n return stream\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(shapeHash)\n shapeCache.delete(stream)\n }\n\n const newShapeStream = new ShapeStream<T>(options)\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n}\n\nexport function getShape<T extends Row<unknown>>(\n shapeStream: ShapeStream<T>\n): Shape<T> {\n // If the stream is already cached, return it if valid\n if (shapeCache.has(shapeStream)) {\n if (!shapeStream.options.signal?.aborted) {\n return shapeCache.get(shapeStream)! as Shape<T>\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(sortedOptionsHash(shapeStream.options))\n shapeCache.delete(shapeStream)\n }\n\n const newShape = new Shape<T>(shapeStream)\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n}\n\nexport interface UseShapeResult<T extends Row<unknown> = Row> {\n /**\n * The array of rows that make up the Shape.\n * @type {T[]}\n */\n data: T[]\n /**\n * The Shape instance used by this useShape\n * @type {Shape<T>}\n */\n shape: Shape<T>\n /**\n * The ShapeStream instance used by this Shape\n * @type {ShapeStream<T>}\n */\n stream: ShapeStream<T>\n /** True during initial fetch. False afterwise. */\n isLoading: boolean\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt?: number\n error: Shape<T>[`error`]\n isError: boolean\n}\n\nfunction shapeSubscribe<T extends Row<unknown>>(\n shape: Shape<T>,\n callback: () => void\n) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row<unknown>>(\n shape: Shape<T>\n): UseShapeResult<T> {\n return {\n data: shape.currentRows,\n isLoading: shape.isLoading(),\n lastSyncedAt: shape.lastSyncedAt(),\n isError: shape.error !== false,\n shape,\n stream: shape.stream as ShapeStream<T>,\n error: shape.error,\n }\n}\n\nfunction shapeResultChanged<T extends Row<unknown>>(\n oldRes: UseShapeResult<T> | undefined,\n newRes: UseShapeResult<T>\n): boolean {\n return (\n !oldRes ||\n oldRes.isLoading !== newRes.isLoading ||\n oldRes.lastSyncedAt !== newRes.lastSyncedAt ||\n oldRes.isError !== newRes.isError ||\n oldRes.error !== newRes.error ||\n oldRes.shape.lastOffset !== newRes.shape.lastOffset ||\n oldRes.shape.handle !== newRes.shape.handle\n )\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row<unknown>, Selection>\n extends ShapeStreamOptions<GetExtensions<SourceData>> {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row<unknown> = Row,\n Selection = UseShapeResult<SourceData>,\n>({\n selector = identity as (arg: UseShapeResult<SourceData>) => Selection,\n ...options\n}: UseShapeOptions<SourceData, Selection>): Selection {\n const shapeStream = getShapeStream<SourceData>(\n options as ShapeStreamOptions<GetExtensions<SourceData>>\n )\n const shape = getShape<SourceData>(shapeStream)\n\n const useShapeData = React.useMemo(() => {\n let latestShapeData: UseShapeResult<SourceData> | undefined\n\n const getSnapshot = () => {\n latestShapeData ??= parseShapeData(shape)\n return latestShapeData\n }\n\n const subscribe = (onStoreChange: () => void) => {\n // check if shape has changed between the initial snapshot\n // and subscribing, as there are no guarantees that the\n // two will occur synchronously with each other\n const newShapeData = parseShapeData(shape)\n if (shapeResultChanged(latestShapeData, newShapeData)) {\n latestShapeData = newShapeData\n onStoreChange()\n }\n\n return shapeSubscribe(shape, () => {\n latestShapeData = parseShapeData(shape)\n onStoreChange()\n })\n }\n\n return () => {\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n selector\n )\n }\n }, [shape, selector])\n\n return useShapeData()\n}\n"],"mappings":"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","names":["React"]}
@@ -1,2 +1,2 @@
1
- var w=Object.getOwnPropertySymbols;var y=Object.prototype.hasOwnProperty,g=Object.prototype.propertyIsEnumerable;var l=(e,n)=>{var t={};for(var a in e)y.call(e,a)&&n.indexOf(a)<0&&(t[a]=e[a]);if(e!=null&&w)for(var a of w(e))n.indexOf(a)<0&&g.call(e,a)&&(t[a]=e[a]);return t};var T=(e,n,t)=>new Promise((a,o)=>{var s=r=>{try{S(t.next(r))}catch(p){o(p)}},h=r=>{try{S(t.throw(r))}catch(p){o(p)}},S=r=>r.done?a(r.value):Promise.resolve(r.value).then(s,h);S((t=t.apply(e,n)).next())});import{Shape as k,ShapeStream as D}from"@electric-sql/client";import O from"react";import{useSyncExternalStoreWithSelector as U}from"use-sync-external-store/with-selector.js";var i=new Map,u=new Map;function J(e){return T(this,null,function*(){let n=f(e),t=x(n);return yield t.rows,t})}function c(e){return typeof e!="object"||e===null?e:Array.isArray(e)?e.map(c):Object.keys(e).sort().reduce((n,t)=>(n[t]=c(e[t]),n),{})}function d(e){return JSON.stringify(c(e))}function f(e){var a;let n=d(e);if(i.has(n)){let o=i.get(n);if(!((a=o.options.signal)!=null&&a.aborted))return o;i.delete(n),u.delete(o)}let t=new D(e);return i.set(n,t),t}function x(e){var t;if(u.has(e)){if(!((t=e.options.signal)!=null&&t.aborted))return u.get(e);i.delete(d(e.options)),u.delete(e)}let n=new k(e);return u.set(e,n),n}function b(e,n){let t=e.subscribe(n);return()=>{t()}}function m(e){return{data:e.currentRows,isLoading:e.isLoading(),lastSyncedAt:e.lastSyncedAt(),isError:e.error!==!1,shape:e,stream:e.stream,error:e.error}}function E(e){return e}function K(t){var a=t,{selector:e=E}=a,n=l(a,["selector"]);let o=f(n),s=x(o);return O.useMemo(()=>{let S=m(s),r=()=>S,p=R=>b(s,()=>{S=m(s),R()});return()=>U(p,r,r,e)},[s,e])()}export{x as getShape,f as getShapeStream,J as preloadShape,d as sortedOptionsHash,K as useShape};
1
+ var T=Object.getOwnPropertySymbols;var k=Object.prototype.hasOwnProperty,R=Object.prototype.propertyIsEnumerable;var f=(e,t)=>{var n={};for(var a in e)k.call(e,a)&&t.indexOf(a)<0&&(n[a]=e[a]);if(e!=null&&T)for(var a of T(e))t.indexOf(a)<0&&R.call(e,a)&&(n[a]=e[a]);return n};var m=(e,t,n)=>new Promise((a,s)=>{var S=o=>{try{r(n.next(o))}catch(i){s(i)}},w=o=>{try{r(n.throw(o))}catch(i){s(i)}},r=o=>o.done?a(o.value):Promise.resolve(o.value).then(S,w);r((n=n.apply(e,t)).next())});import{Shape as U,ShapeStream as D}from"@electric-sql/client";import O from"react";import{useSyncExternalStoreWithSelector as b}from"use-sync-external-store/with-selector.js";var p=new Map,u=new Map;function N(e){return m(this,null,function*(){let t=y(e),n=g(t);return yield n.rows,n})}function h(e){return typeof e!="object"||e===null?e:Array.isArray(e)?e.map(h):Object.keys(e).sort().reduce((t,n)=>(t[n]=h(e[n]),t),{})}function x(e){return JSON.stringify(h(e))}function y(e){var a;let t=x(e);if(p.has(t)){let s=p.get(t);if(!((a=s.options.signal)!=null&&a.aborted))return s;p.delete(t),u.delete(s)}let n=new D(e);return p.set(t,n),n}function g(e){var n;if(u.has(e)){if(!((n=e.options.signal)!=null&&n.aborted))return u.get(e);p.delete(x(e.options)),u.delete(e)}let t=new U(e);return u.set(e,t),t}function E(e,t){let n=e.subscribe(t);return()=>{n()}}function c(e){return{data:e.currentRows,isLoading:e.isLoading(),lastSyncedAt:e.lastSyncedAt(),isError:e.error!==!1,shape:e,stream:e.stream,error:e.error}}function A(e,t){return!e||e.isLoading!==t.isLoading||e.lastSyncedAt!==t.lastSyncedAt||e.isError!==t.isError||e.error!==t.error||e.shape.lastOffset!==t.shape.lastOffset||e.shape.handle!==t.shape.handle}function G(e){return e}function P(n){var a=n,{selector:e=G}=a,t=f(a,["selector"]);let s=y(t),S=g(s);return O.useMemo(()=>{let r,o=()=>(r!=null||(r=c(S)),r),i=d=>{let l=c(S);return A(r,l)&&(r=l,d()),E(S,()=>{r=c(S),d()})};return()=>b(i,o,o,e)},[S,e])()}export{g as getShape,y as getShapeStream,N as preloadShape,x as sortedOptionsHash,P as useShape};
2
2
  //# sourceMappingURL=index.browser.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n GetExtensions,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\ntype UnknownShape = Shape<Row<unknown>>\ntype UnknownShapeStream = ShapeStream<Row<unknown>>\n\nconst streamCache = new Map<string, UnknownShapeStream>()\nconst shapeCache = new Map<UnknownShapeStream, UnknownShape>()\n\nexport async function preloadShape<T extends Row<unknown> = Row>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.rows\n return shape\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sortObjectKeys(obj: any): any {\n if (typeof obj !== `object` || obj === null) return obj\n\n if (Array.isArray(obj)) {\n return obj.map(sortObjectKeys)\n }\n\n return (\n Object.keys(obj)\n .sort()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .reduce<Record<string, any>>((sorted, key) => {\n sorted[key] = sortObjectKeys(obj[key])\n return sorted\n }, {})\n )\n}\n\nexport function sortedOptionsHash<T>(options: ShapeStreamOptions<T>): string {\n return JSON.stringify(sortObjectKeys(options))\n}\n\nexport function getShapeStream<T extends Row<unknown>>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return it if valid\n if (streamCache.has(shapeHash)) {\n const stream = streamCache.get(shapeHash)! as ShapeStream<T>\n if (!stream.options.signal?.aborted) {\n return stream\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(shapeHash)\n shapeCache.delete(stream)\n }\n\n const newShapeStream = new ShapeStream<T>(options)\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n}\n\nexport function getShape<T extends Row<unknown>>(\n shapeStream: ShapeStream<T>\n): Shape<T> {\n // If the stream is already cached, return it if valid\n if (shapeCache.has(shapeStream)) {\n if (!shapeStream.options.signal?.aborted) {\n return shapeCache.get(shapeStream)! as Shape<T>\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(sortedOptionsHash(shapeStream.options))\n shapeCache.delete(shapeStream)\n }\n\n const newShape = new Shape<T>(shapeStream)\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n}\n\nexport interface UseShapeResult<T extends Row<unknown> = Row> {\n /**\n * The array of rows that make up the Shape.\n * @type {T[]}\n */\n data: T[]\n /**\n * The Shape instance used by this useShape\n * @type {Shape<T>}\n */\n shape: Shape<T>\n /**\n * The ShapeStream instance used by this Shape\n * @type {ShapeStream<T>}\n */\n stream: ShapeStream<T>\n /** True during initial fetch. False afterwise. */\n isLoading: boolean\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt?: number\n error: Shape<T>[`error`]\n isError: boolean\n}\n\nfunction shapeSubscribe<T extends Row<unknown>>(\n shape: Shape<T>,\n callback: () => void\n) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row<unknown>>(\n shape: Shape<T>\n): UseShapeResult<T> {\n return {\n data: shape.currentRows,\n isLoading: shape.isLoading(),\n lastSyncedAt: shape.lastSyncedAt(),\n isError: shape.error !== false,\n shape,\n stream: shape.stream as ShapeStream<T>,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row<unknown>, Selection>\n extends ShapeStreamOptions<GetExtensions<SourceData>> {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row<unknown> = Row,\n Selection = UseShapeResult<SourceData>,\n>({\n selector = identity as (arg: UseShapeResult<SourceData>) => Selection,\n ...options\n}: UseShapeOptions<SourceData, Selection>): Selection {\n const shapeStream = getShapeStream<SourceData>(\n options as ShapeStreamOptions<GetExtensions<SourceData>>\n )\n const shape = getShape<SourceData>(shapeStream)\n\n const useShapeData = React.useMemo(() => {\n let latestShapeData = parseShapeData(shape)\n const getSnapshot = () => latestShapeData\n const subscribe = (onStoreChange: () => void) =>\n shapeSubscribe(shape, () => {\n latestShapeData = parseShapeData(shape)\n onStoreChange()\n })\n\n return () => {\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n selector\n )\n }\n }, [shape, selector])\n\n return useShapeData()\n}\n"],"mappings":"geAAA,OACE,SAAAA,EACA,eAAAC,MAIK,uBACP,OAAOC,MAAW,QAClB,OAAS,oCAAAC,MAAwC,2CAKjD,IAAMC,EAAc,IAAI,IAClBC,EAAa,IAAI,IAEvB,SAAsBC,EACpBC,EACmB,QAAAC,EAAA,sBACnB,IAAMC,EAAcC,EAAkBH,CAAO,EACvCI,EAAQC,EAAYH,CAAW,EACrC,aAAME,EAAM,KACLA,CACT,GAGA,SAASE,EAAeC,EAAe,CACrC,OAAI,OAAOA,GAAQ,UAAYA,IAAQ,KAAaA,EAEhD,MAAM,QAAQA,CAAG,EACZA,EAAI,IAAID,CAAc,EAI7B,OAAO,KAAKC,CAAG,EACZ,KAAK,EAEL,OAA4B,CAACC,EAAQC,KACpCD,EAAOC,CAAG,EAAIH,EAAeC,EAAIE,CAAG,CAAC,EAC9BD,GACN,CAAC,CAAC,CAEX,CAEO,SAASE,EAAqBV,EAAwC,CAC3E,OAAO,KAAK,UAAUM,EAAeN,CAAO,CAAC,CAC/C,CAEO,SAASG,EACdH,EACgB,CAlDlB,IAAAW,EAmDE,IAAMC,EAAYF,EAAkBV,CAAO,EAG3C,GAAIH,EAAY,IAAIe,CAAS,EAAG,CAC9B,IAAMC,EAAShB,EAAY,IAAIe,CAAS,EACxC,GAAI,GAACD,EAAAE,EAAO,QAAQ,SAAf,MAAAF,EAAuB,SAC1B,OAAOE,EAIThB,EAAY,OAAOe,CAAS,EAC5Bd,EAAW,OAAOe,CAAM,CAC1B,CAEA,IAAMC,EAAiB,IAAIC,EAAef,CAAO,EACjD,OAAAH,EAAY,IAAIe,EAAWE,CAAc,EAGlCA,CACT,CAEO,SAAST,EACdH,EACU,CA1EZ,IAAAS,EA4EE,GAAIb,EAAW,IAAII,CAAW,EAAG,CAC/B,GAAI,GAACS,EAAAT,EAAY,QAAQ,SAApB,MAAAS,EAA4B,SAC/B,OAAOb,EAAW,IAAII,CAAW,EAInCL,EAAY,OAAOa,EAAkBR,EAAY,OAAO,CAAC,EACzDJ,EAAW,OAAOI,CAAW,CAC/B,CAEA,IAAMc,EAAW,IAAIC,EAASf,CAAW,EACzC,OAAAJ,EAAW,IAAII,EAAac,CAAQ,EAG7BA,CACT,CA0BA,SAASE,EACPd,EACAe,EACA,CACA,IAAMC,EAAchB,EAAM,UAAUe,CAAQ,EAC5C,MAAO,IAAM,CACXC,EAAY,CACd,CACF,CAEA,SAASC,EACPjB,EACmB,CACnB,MAAO,CACL,KAAMA,EAAM,YACZ,UAAWA,EAAM,UAAU,EAC3B,aAAcA,EAAM,aAAa,EACjC,QAASA,EAAM,QAAU,GACzB,MAAAA,EACA,OAAQA,EAAM,OACd,MAAOA,EAAM,KACf,CACF,CAEA,SAASkB,EAAYC,EAAW,CAC9B,OAAOA,CACT,CAOO,SAASC,EAGdb,EAGoD,CAHpD,IAAAc,EAAAd,EACA,UAAAe,EAAWJ,CA1Jb,EAyJEG,EAEGzB,EAAA2B,EAFHF,EAEG,CADH,aAGA,IAAMvB,EAAcC,EAClBH,CACF,EACMI,EAAQC,EAAqBH,CAAW,EAqB9C,OAnBqB0B,EAAM,QAAQ,IAAM,CACvC,IAAIC,EAAkBR,EAAejB,CAAK,EACpC0B,EAAc,IAAMD,EACpBE,EAAaC,GACjBd,EAAed,EAAO,IAAM,CAC1ByB,EAAkBR,EAAejB,CAAK,EACtC4B,EAAc,CAChB,CAAC,EAEH,MAAO,IACEC,EACLF,EACAD,EACAA,EACAJ,CACF,CAEJ,EAAG,CAACtB,EAAOsB,CAAQ,CAAC,EAEA,CACtB","names":["Shape","ShapeStream","React","useSyncExternalStoreWithSelector","streamCache","shapeCache","preloadShape","options","__async","shapeStream","getShapeStream","shape","getShape","sortObjectKeys","obj","sorted","key","sortedOptionsHash","_a","shapeHash","stream","newShapeStream","ShapeStream","newShape","Shape","shapeSubscribe","callback","unsubscribe","parseShapeData","identity","arg","useShape","_b","selector","__objRest","React","latestShapeData","getSnapshot","subscribe","onStoreChange","useSyncExternalStoreWithSelector"]}
1
+ {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n GetExtensions,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\ntype UnknownShape = Shape<Row<unknown>>\ntype UnknownShapeStream = ShapeStream<Row<unknown>>\n\nconst streamCache = new Map<string, UnknownShapeStream>()\nconst shapeCache = new Map<UnknownShapeStream, UnknownShape>()\n\nexport async function preloadShape<T extends Row<unknown> = Row>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.rows\n return shape\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sortObjectKeys(obj: any): any {\n if (typeof obj !== `object` || obj === null) return obj\n\n if (Array.isArray(obj)) {\n return obj.map(sortObjectKeys)\n }\n\n return (\n Object.keys(obj)\n .sort()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .reduce<Record<string, any>>((sorted, key) => {\n sorted[key] = sortObjectKeys(obj[key])\n return sorted\n }, {})\n )\n}\n\nexport function sortedOptionsHash<T>(options: ShapeStreamOptions<T>): string {\n return JSON.stringify(sortObjectKeys(options))\n}\n\nexport function getShapeStream<T extends Row<unknown>>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return it if valid\n if (streamCache.has(shapeHash)) {\n const stream = streamCache.get(shapeHash)! as ShapeStream<T>\n if (!stream.options.signal?.aborted) {\n return stream\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(shapeHash)\n shapeCache.delete(stream)\n }\n\n const newShapeStream = new ShapeStream<T>(options)\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n}\n\nexport function getShape<T extends Row<unknown>>(\n shapeStream: ShapeStream<T>\n): Shape<T> {\n // If the stream is already cached, return it if valid\n if (shapeCache.has(shapeStream)) {\n if (!shapeStream.options.signal?.aborted) {\n return shapeCache.get(shapeStream)! as Shape<T>\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(sortedOptionsHash(shapeStream.options))\n shapeCache.delete(shapeStream)\n }\n\n const newShape = new Shape<T>(shapeStream)\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n}\n\nexport interface UseShapeResult<T extends Row<unknown> = Row> {\n /**\n * The array of rows that make up the Shape.\n * @type {T[]}\n */\n data: T[]\n /**\n * The Shape instance used by this useShape\n * @type {Shape<T>}\n */\n shape: Shape<T>\n /**\n * The ShapeStream instance used by this Shape\n * @type {ShapeStream<T>}\n */\n stream: ShapeStream<T>\n /** True during initial fetch. False afterwise. */\n isLoading: boolean\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt?: number\n error: Shape<T>[`error`]\n isError: boolean\n}\n\nfunction shapeSubscribe<T extends Row<unknown>>(\n shape: Shape<T>,\n callback: () => void\n) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row<unknown>>(\n shape: Shape<T>\n): UseShapeResult<T> {\n return {\n data: shape.currentRows,\n isLoading: shape.isLoading(),\n lastSyncedAt: shape.lastSyncedAt(),\n isError: shape.error !== false,\n shape,\n stream: shape.stream as ShapeStream<T>,\n error: shape.error,\n }\n}\n\nfunction shapeResultChanged<T extends Row<unknown>>(\n oldRes: UseShapeResult<T> | undefined,\n newRes: UseShapeResult<T>\n): boolean {\n return (\n !oldRes ||\n oldRes.isLoading !== newRes.isLoading ||\n oldRes.lastSyncedAt !== newRes.lastSyncedAt ||\n oldRes.isError !== newRes.isError ||\n oldRes.error !== newRes.error ||\n oldRes.shape.lastOffset !== newRes.shape.lastOffset ||\n oldRes.shape.handle !== newRes.shape.handle\n )\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row<unknown>, Selection>\n extends ShapeStreamOptions<GetExtensions<SourceData>> {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row<unknown> = Row,\n Selection = UseShapeResult<SourceData>,\n>({\n selector = identity as (arg: UseShapeResult<SourceData>) => Selection,\n ...options\n}: UseShapeOptions<SourceData, Selection>): Selection {\n const shapeStream = getShapeStream<SourceData>(\n options as ShapeStreamOptions<GetExtensions<SourceData>>\n )\n const shape = getShape<SourceData>(shapeStream)\n\n const useShapeData = React.useMemo(() => {\n let latestShapeData: UseShapeResult<SourceData> | undefined\n\n const getSnapshot = () => {\n latestShapeData ??= parseShapeData(shape)\n return latestShapeData\n }\n\n const subscribe = (onStoreChange: () => void) => {\n // check if shape has changed between the initial snapshot\n // and subscribing, as there are no guarantees that the\n // two will occur synchronously with each other\n const newShapeData = parseShapeData(shape)\n if (shapeResultChanged(latestShapeData, newShapeData)) {\n latestShapeData = newShapeData\n onStoreChange()\n }\n\n return shapeSubscribe(shape, () => {\n latestShapeData = parseShapeData(shape)\n onStoreChange()\n })\n }\n\n return () => {\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n selector\n )\n }\n }, [shape, selector])\n\n return useShapeData()\n}\n"],"mappings":"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","names":["Shape","ShapeStream","React","useSyncExternalStoreWithSelector","streamCache","shapeCache","preloadShape","options","__async","shapeStream","getShapeStream","shape","getShape","sortObjectKeys","obj","sorted","key","sortedOptionsHash","_a","shapeHash","stream","newShapeStream","ShapeStream","newShape","Shape","shapeSubscribe","callback","unsubscribe","parseShapeData","shapeResultChanged","oldRes","newRes","identity","arg","useShape","_b","selector","__objRest","React","latestShapeData","getSnapshot","subscribe","onStoreChange","newShapeData","useSyncExternalStoreWithSelector"]}
@@ -87,6 +87,9 @@ function parseShapeData(shape) {
87
87
  error: shape.error
88
88
  };
89
89
  }
90
+ function shapeResultChanged(oldRes, newRes) {
91
+ return !oldRes || oldRes.isLoading !== newRes.isLoading || oldRes.lastSyncedAt !== newRes.lastSyncedAt || oldRes.isError !== newRes.isError || oldRes.error !== newRes.error || oldRes.shape.lastOffset !== newRes.shape.lastOffset || oldRes.shape.handle !== newRes.shape.handle;
92
+ }
90
93
  function identity(arg) {
91
94
  return arg;
92
95
  }
@@ -101,12 +104,22 @@ function useShape(_a) {
101
104
  );
102
105
  const shape = getShape(shapeStream);
103
106
  const useShapeData = React.useMemo(() => {
104
- let latestShapeData = parseShapeData(shape);
105
- const getSnapshot = () => latestShapeData;
106
- const subscribe = (onStoreChange) => shapeSubscribe(shape, () => {
107
- latestShapeData = parseShapeData(shape);
108
- onStoreChange();
109
- });
107
+ let latestShapeData;
108
+ const getSnapshot = () => {
109
+ latestShapeData != null ? latestShapeData : latestShapeData = parseShapeData(shape);
110
+ return latestShapeData;
111
+ };
112
+ const subscribe = (onStoreChange) => {
113
+ const newShapeData = parseShapeData(shape);
114
+ if (shapeResultChanged(latestShapeData, newShapeData)) {
115
+ latestShapeData = newShapeData;
116
+ onStoreChange();
117
+ }
118
+ return shapeSubscribe(shape, () => {
119
+ latestShapeData = parseShapeData(shape);
120
+ onStoreChange();
121
+ });
122
+ };
110
123
  return () => {
111
124
  return useSyncExternalStoreWithSelector(
112
125
  subscribe,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n GetExtensions,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\ntype UnknownShape = Shape<Row<unknown>>\ntype UnknownShapeStream = ShapeStream<Row<unknown>>\n\nconst streamCache = new Map<string, UnknownShapeStream>()\nconst shapeCache = new Map<UnknownShapeStream, UnknownShape>()\n\nexport async function preloadShape<T extends Row<unknown> = Row>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.rows\n return shape\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sortObjectKeys(obj: any): any {\n if (typeof obj !== `object` || obj === null) return obj\n\n if (Array.isArray(obj)) {\n return obj.map(sortObjectKeys)\n }\n\n return (\n Object.keys(obj)\n .sort()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .reduce<Record<string, any>>((sorted, key) => {\n sorted[key] = sortObjectKeys(obj[key])\n return sorted\n }, {})\n )\n}\n\nexport function sortedOptionsHash<T>(options: ShapeStreamOptions<T>): string {\n return JSON.stringify(sortObjectKeys(options))\n}\n\nexport function getShapeStream<T extends Row<unknown>>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return it if valid\n if (streamCache.has(shapeHash)) {\n const stream = streamCache.get(shapeHash)! as ShapeStream<T>\n if (!stream.options.signal?.aborted) {\n return stream\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(shapeHash)\n shapeCache.delete(stream)\n }\n\n const newShapeStream = new ShapeStream<T>(options)\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n}\n\nexport function getShape<T extends Row<unknown>>(\n shapeStream: ShapeStream<T>\n): Shape<T> {\n // If the stream is already cached, return it if valid\n if (shapeCache.has(shapeStream)) {\n if (!shapeStream.options.signal?.aborted) {\n return shapeCache.get(shapeStream)! as Shape<T>\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(sortedOptionsHash(shapeStream.options))\n shapeCache.delete(shapeStream)\n }\n\n const newShape = new Shape<T>(shapeStream)\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n}\n\nexport interface UseShapeResult<T extends Row<unknown> = Row> {\n /**\n * The array of rows that make up the Shape.\n * @type {T[]}\n */\n data: T[]\n /**\n * The Shape instance used by this useShape\n * @type {Shape<T>}\n */\n shape: Shape<T>\n /**\n * The ShapeStream instance used by this Shape\n * @type {ShapeStream<T>}\n */\n stream: ShapeStream<T>\n /** True during initial fetch. False afterwise. */\n isLoading: boolean\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt?: number\n error: Shape<T>[`error`]\n isError: boolean\n}\n\nfunction shapeSubscribe<T extends Row<unknown>>(\n shape: Shape<T>,\n callback: () => void\n) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row<unknown>>(\n shape: Shape<T>\n): UseShapeResult<T> {\n return {\n data: shape.currentRows,\n isLoading: shape.isLoading(),\n lastSyncedAt: shape.lastSyncedAt(),\n isError: shape.error !== false,\n shape,\n stream: shape.stream as ShapeStream<T>,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row<unknown>, Selection>\n extends ShapeStreamOptions<GetExtensions<SourceData>> {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row<unknown> = Row,\n Selection = UseShapeResult<SourceData>,\n>({\n selector = identity as (arg: UseShapeResult<SourceData>) => Selection,\n ...options\n}: UseShapeOptions<SourceData, Selection>): Selection {\n const shapeStream = getShapeStream<SourceData>(\n options as ShapeStreamOptions<GetExtensions<SourceData>>\n )\n const shape = getShape<SourceData>(shapeStream)\n\n const useShapeData = React.useMemo(() => {\n let latestShapeData = parseShapeData(shape)\n const getSnapshot = () => latestShapeData\n const subscribe = (onStoreChange: () => void) =>\n shapeSubscribe(shape, () => {\n latestShapeData = parseShapeData(shape)\n onStoreChange()\n })\n\n return () => {\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n selector\n )\n }\n }, [shape, selector])\n\n return useShapeData()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AACP,OAAO,WAAW;AAClB,SAAS,wCAAwC;AAKjD,IAAM,cAAc,oBAAI,IAAgC;AACxD,IAAM,aAAa,oBAAI,IAAsC;AAE7D,eAAsB,aACpB,SACmB;AACnB,QAAM,cAAc,eAAkB,OAAO;AAC7C,QAAM,QAAQ,SAAY,WAAW;AACrC,QAAM,MAAM;AACZ,SAAO;AACT;AAGA,SAAS,eAAe,KAAe;AACrC,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;AAEpD,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,cAAc;AAAA,EAC/B;AAEA,SACE,OAAO,KAAK,GAAG,EACZ,KAAK,EAEL,OAA4B,CAAC,QAAQ,QAAQ;AAC5C,WAAO,GAAG,IAAI,eAAe,IAAI,GAAG,CAAC;AACrC,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAEX;AAEO,SAAS,kBAAqB,SAAwC;AAC3E,SAAO,KAAK,UAAU,eAAe,OAAO,CAAC;AAC/C;AAEO,SAAS,eACd,SACgB;AAlDlB;AAmDE,QAAM,YAAY,kBAAkB,OAAO;AAG3C,MAAI,YAAY,IAAI,SAAS,GAAG;AAC9B,UAAM,SAAS,YAAY,IAAI,SAAS;AACxC,QAAI,GAAC,YAAO,QAAQ,WAAf,mBAAuB,UAAS;AACnC,aAAO;AAAA,IACT;AAGA,gBAAY,OAAO,SAAS;AAC5B,eAAW,OAAO,MAAM;AAAA,EAC1B;AAEA,QAAM,iBAAiB,IAAI,YAAe,OAAO;AACjD,cAAY,IAAI,WAAW,cAAc;AAGzC,SAAO;AACT;AAEO,SAAS,SACd,aACU;AA1EZ;AA4EE,MAAI,WAAW,IAAI,WAAW,GAAG;AAC/B,QAAI,GAAC,iBAAY,QAAQ,WAApB,mBAA4B,UAAS;AACxC,aAAO,WAAW,IAAI,WAAW;AAAA,IACnC;AAGA,gBAAY,OAAO,kBAAkB,YAAY,OAAO,CAAC;AACzD,eAAW,OAAO,WAAW;AAAA,EAC/B;AAEA,QAAM,WAAW,IAAI,MAAS,WAAW;AACzC,aAAW,IAAI,aAAa,QAAQ;AAGpC,SAAO;AACT;AA0BA,SAAS,eACP,OACA,UACA;AACA,QAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,SAAO,MAAM;AACX,gBAAY;AAAA,EACd;AACF;AAEA,SAAS,eACP,OACmB;AACnB,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,WAAW,MAAM,UAAU;AAAA,IAC3B,cAAc,MAAM,aAAa;AAAA,IACjC,SAAS,MAAM,UAAU;AAAA,IACzB;AAAA,IACA,QAAQ,MAAM;AAAA,IACd,OAAO,MAAM;AAAA,EACf;AACF;AAEA,SAAS,SAAY,KAAW;AAC9B,SAAO;AACT;AAOO,SAAS,SAGd,IAGoD;AAHpD,eACA;AAAA,eAAW;AAAA,EA1Jb,IAyJE,IAEG,oBAFH,IAEG;AAAA,IADH;AAAA;AAGA,QAAM,cAAc;AAAA,IAClB;AAAA,EACF;AACA,QAAM,QAAQ,SAAqB,WAAW;AAE9C,QAAM,eAAe,MAAM,QAAQ,MAAM;AACvC,QAAI,kBAAkB,eAAe,KAAK;AAC1C,UAAM,cAAc,MAAM;AAC1B,UAAM,YAAY,CAAC,kBACjB,eAAe,OAAO,MAAM;AAC1B,wBAAkB,eAAe,KAAK;AACtC,oBAAc;AAAA,IAChB,CAAC;AAEH,WAAO,MAAM;AACX,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEpB,SAAO,aAAa;AACtB;","names":[]}
1
+ {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n GetExtensions,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\ntype UnknownShape = Shape<Row<unknown>>\ntype UnknownShapeStream = ShapeStream<Row<unknown>>\n\nconst streamCache = new Map<string, UnknownShapeStream>()\nconst shapeCache = new Map<UnknownShapeStream, UnknownShape>()\n\nexport async function preloadShape<T extends Row<unknown> = Row>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.rows\n return shape\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sortObjectKeys(obj: any): any {\n if (typeof obj !== `object` || obj === null) return obj\n\n if (Array.isArray(obj)) {\n return obj.map(sortObjectKeys)\n }\n\n return (\n Object.keys(obj)\n .sort()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .reduce<Record<string, any>>((sorted, key) => {\n sorted[key] = sortObjectKeys(obj[key])\n return sorted\n }, {})\n )\n}\n\nexport function sortedOptionsHash<T>(options: ShapeStreamOptions<T>): string {\n return JSON.stringify(sortObjectKeys(options))\n}\n\nexport function getShapeStream<T extends Row<unknown>>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return it if valid\n if (streamCache.has(shapeHash)) {\n const stream = streamCache.get(shapeHash)! as ShapeStream<T>\n if (!stream.options.signal?.aborted) {\n return stream\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(shapeHash)\n shapeCache.delete(stream)\n }\n\n const newShapeStream = new ShapeStream<T>(options)\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n}\n\nexport function getShape<T extends Row<unknown>>(\n shapeStream: ShapeStream<T>\n): Shape<T> {\n // If the stream is already cached, return it if valid\n if (shapeCache.has(shapeStream)) {\n if (!shapeStream.options.signal?.aborted) {\n return shapeCache.get(shapeStream)! as Shape<T>\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(sortedOptionsHash(shapeStream.options))\n shapeCache.delete(shapeStream)\n }\n\n const newShape = new Shape<T>(shapeStream)\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n}\n\nexport interface UseShapeResult<T extends Row<unknown> = Row> {\n /**\n * The array of rows that make up the Shape.\n * @type {T[]}\n */\n data: T[]\n /**\n * The Shape instance used by this useShape\n * @type {Shape<T>}\n */\n shape: Shape<T>\n /**\n * The ShapeStream instance used by this Shape\n * @type {ShapeStream<T>}\n */\n stream: ShapeStream<T>\n /** True during initial fetch. False afterwise. */\n isLoading: boolean\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt?: number\n error: Shape<T>[`error`]\n isError: boolean\n}\n\nfunction shapeSubscribe<T extends Row<unknown>>(\n shape: Shape<T>,\n callback: () => void\n) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row<unknown>>(\n shape: Shape<T>\n): UseShapeResult<T> {\n return {\n data: shape.currentRows,\n isLoading: shape.isLoading(),\n lastSyncedAt: shape.lastSyncedAt(),\n isError: shape.error !== false,\n shape,\n stream: shape.stream as ShapeStream<T>,\n error: shape.error,\n }\n}\n\nfunction shapeResultChanged<T extends Row<unknown>>(\n oldRes: UseShapeResult<T> | undefined,\n newRes: UseShapeResult<T>\n): boolean {\n return (\n !oldRes ||\n oldRes.isLoading !== newRes.isLoading ||\n oldRes.lastSyncedAt !== newRes.lastSyncedAt ||\n oldRes.isError !== newRes.isError ||\n oldRes.error !== newRes.error ||\n oldRes.shape.lastOffset !== newRes.shape.lastOffset ||\n oldRes.shape.handle !== newRes.shape.handle\n )\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row<unknown>, Selection>\n extends ShapeStreamOptions<GetExtensions<SourceData>> {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row<unknown> = Row,\n Selection = UseShapeResult<SourceData>,\n>({\n selector = identity as (arg: UseShapeResult<SourceData>) => Selection,\n ...options\n}: UseShapeOptions<SourceData, Selection>): Selection {\n const shapeStream = getShapeStream<SourceData>(\n options as ShapeStreamOptions<GetExtensions<SourceData>>\n )\n const shape = getShape<SourceData>(shapeStream)\n\n const useShapeData = React.useMemo(() => {\n let latestShapeData: UseShapeResult<SourceData> | undefined\n\n const getSnapshot = () => {\n latestShapeData ??= parseShapeData(shape)\n return latestShapeData\n }\n\n const subscribe = (onStoreChange: () => void) => {\n // check if shape has changed between the initial snapshot\n // and subscribing, as there are no guarantees that the\n // two will occur synchronously with each other\n const newShapeData = parseShapeData(shape)\n if (shapeResultChanged(latestShapeData, newShapeData)) {\n latestShapeData = newShapeData\n onStoreChange()\n }\n\n return shapeSubscribe(shape, () => {\n latestShapeData = parseShapeData(shape)\n onStoreChange()\n })\n }\n\n return () => {\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n selector\n )\n }\n }, [shape, selector])\n\n return useShapeData()\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -109,6 +109,9 @@ function parseShapeData(shape) {
109
109
  error: shape.error
110
110
  };
111
111
  }
112
+ function shapeResultChanged(oldRes, newRes) {
113
+ return !oldRes || oldRes.isLoading !== newRes.isLoading || oldRes.lastSyncedAt !== newRes.lastSyncedAt || oldRes.isError !== newRes.isError || oldRes.error !== newRes.error || oldRes.shape.lastOffset !== newRes.shape.lastOffset || oldRes.shape.handle !== newRes.shape.handle;
114
+ }
112
115
  function identity(arg) {
113
116
  return arg;
114
117
  }
@@ -123,12 +126,22 @@ function useShape(_a) {
123
126
  );
124
127
  const shape = getShape(shapeStream);
125
128
  const useShapeData = React.useMemo(() => {
126
- let latestShapeData = parseShapeData(shape);
127
- const getSnapshot = () => latestShapeData;
128
- const subscribe = (onStoreChange) => shapeSubscribe(shape, () => {
129
- latestShapeData = parseShapeData(shape);
130
- onStoreChange();
131
- });
129
+ let latestShapeData;
130
+ const getSnapshot = () => {
131
+ latestShapeData != null ? latestShapeData : latestShapeData = parseShapeData(shape);
132
+ return latestShapeData;
133
+ };
134
+ const subscribe = (onStoreChange) => {
135
+ const newShapeData = parseShapeData(shape);
136
+ if (shapeResultChanged(latestShapeData, newShapeData)) {
137
+ latestShapeData = newShapeData;
138
+ onStoreChange();
139
+ }
140
+ return shapeSubscribe(shape, () => {
141
+ latestShapeData = parseShapeData(shape);
142
+ onStoreChange();
143
+ });
144
+ };
132
145
  return () => {
133
146
  return useSyncExternalStoreWithSelector(
134
147
  subscribe,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n GetExtensions,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\ntype UnknownShape = Shape<Row<unknown>>\ntype UnknownShapeStream = ShapeStream<Row<unknown>>\n\nconst streamCache = new Map<string, UnknownShapeStream>()\nconst shapeCache = new Map<UnknownShapeStream, UnknownShape>()\n\nexport async function preloadShape<T extends Row<unknown> = Row>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.rows\n return shape\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sortObjectKeys(obj: any): any {\n if (typeof obj !== `object` || obj === null) return obj\n\n if (Array.isArray(obj)) {\n return obj.map(sortObjectKeys)\n }\n\n return (\n Object.keys(obj)\n .sort()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .reduce<Record<string, any>>((sorted, key) => {\n sorted[key] = sortObjectKeys(obj[key])\n return sorted\n }, {})\n )\n}\n\nexport function sortedOptionsHash<T>(options: ShapeStreamOptions<T>): string {\n return JSON.stringify(sortObjectKeys(options))\n}\n\nexport function getShapeStream<T extends Row<unknown>>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return it if valid\n if (streamCache.has(shapeHash)) {\n const stream = streamCache.get(shapeHash)! as ShapeStream<T>\n if (!stream.options.signal?.aborted) {\n return stream\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(shapeHash)\n shapeCache.delete(stream)\n }\n\n const newShapeStream = new ShapeStream<T>(options)\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n}\n\nexport function getShape<T extends Row<unknown>>(\n shapeStream: ShapeStream<T>\n): Shape<T> {\n // If the stream is already cached, return it if valid\n if (shapeCache.has(shapeStream)) {\n if (!shapeStream.options.signal?.aborted) {\n return shapeCache.get(shapeStream)! as Shape<T>\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(sortedOptionsHash(shapeStream.options))\n shapeCache.delete(shapeStream)\n }\n\n const newShape = new Shape<T>(shapeStream)\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n}\n\nexport interface UseShapeResult<T extends Row<unknown> = Row> {\n /**\n * The array of rows that make up the Shape.\n * @type {T[]}\n */\n data: T[]\n /**\n * The Shape instance used by this useShape\n * @type {Shape<T>}\n */\n shape: Shape<T>\n /**\n * The ShapeStream instance used by this Shape\n * @type {ShapeStream<T>}\n */\n stream: ShapeStream<T>\n /** True during initial fetch. False afterwise. */\n isLoading: boolean\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt?: number\n error: Shape<T>[`error`]\n isError: boolean\n}\n\nfunction shapeSubscribe<T extends Row<unknown>>(\n shape: Shape<T>,\n callback: () => void\n) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row<unknown>>(\n shape: Shape<T>\n): UseShapeResult<T> {\n return {\n data: shape.currentRows,\n isLoading: shape.isLoading(),\n lastSyncedAt: shape.lastSyncedAt(),\n isError: shape.error !== false,\n shape,\n stream: shape.stream as ShapeStream<T>,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row<unknown>, Selection>\n extends ShapeStreamOptions<GetExtensions<SourceData>> {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row<unknown> = Row,\n Selection = UseShapeResult<SourceData>,\n>({\n selector = identity as (arg: UseShapeResult<SourceData>) => Selection,\n ...options\n}: UseShapeOptions<SourceData, Selection>): Selection {\n const shapeStream = getShapeStream<SourceData>(\n options as ShapeStreamOptions<GetExtensions<SourceData>>\n )\n const shape = getShape<SourceData>(shapeStream)\n\n const useShapeData = React.useMemo(() => {\n let latestShapeData = parseShapeData(shape)\n const getSnapshot = () => latestShapeData\n const subscribe = (onStoreChange: () => void) =>\n shapeSubscribe(shape, () => {\n latestShapeData = parseShapeData(shape)\n onStoreChange()\n })\n\n return () => {\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n selector\n )\n }\n }, [shape, selector])\n\n return useShapeData()\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n GetExtensions,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\ntype UnknownShape = Shape<Row<unknown>>\ntype UnknownShapeStream = ShapeStream<Row<unknown>>\n\nconst streamCache = new Map<string, UnknownShapeStream>()\nconst shapeCache = new Map<UnknownShapeStream, UnknownShape>()\n\nexport async function preloadShape<T extends Row<unknown> = Row>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.rows\n return shape\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction sortObjectKeys(obj: any): any {\n if (typeof obj !== `object` || obj === null) return obj\n\n if (Array.isArray(obj)) {\n return obj.map(sortObjectKeys)\n }\n\n return (\n Object.keys(obj)\n .sort()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .reduce<Record<string, any>>((sorted, key) => {\n sorted[key] = sortObjectKeys(obj[key])\n return sorted\n }, {})\n )\n}\n\nexport function sortedOptionsHash<T>(options: ShapeStreamOptions<T>): string {\n return JSON.stringify(sortObjectKeys(options))\n}\n\nexport function getShapeStream<T extends Row<unknown>>(\n options: ShapeStreamOptions<GetExtensions<T>>\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return it if valid\n if (streamCache.has(shapeHash)) {\n const stream = streamCache.get(shapeHash)! as ShapeStream<T>\n if (!stream.options.signal?.aborted) {\n return stream\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(shapeHash)\n shapeCache.delete(stream)\n }\n\n const newShapeStream = new ShapeStream<T>(options)\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n}\n\nexport function getShape<T extends Row<unknown>>(\n shapeStream: ShapeStream<T>\n): Shape<T> {\n // If the stream is already cached, return it if valid\n if (shapeCache.has(shapeStream)) {\n if (!shapeStream.options.signal?.aborted) {\n return shapeCache.get(shapeStream)! as Shape<T>\n }\n\n // if stream is aborted, remove it and related shapes\n streamCache.delete(sortedOptionsHash(shapeStream.options))\n shapeCache.delete(shapeStream)\n }\n\n const newShape = new Shape<T>(shapeStream)\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n}\n\nexport interface UseShapeResult<T extends Row<unknown> = Row> {\n /**\n * The array of rows that make up the Shape.\n * @type {T[]}\n */\n data: T[]\n /**\n * The Shape instance used by this useShape\n * @type {Shape<T>}\n */\n shape: Shape<T>\n /**\n * The ShapeStream instance used by this Shape\n * @type {ShapeStream<T>}\n */\n stream: ShapeStream<T>\n /** True during initial fetch. False afterwise. */\n isLoading: boolean\n /** Unix time at which we last synced. Undefined when `isLoading` is true. */\n lastSyncedAt?: number\n error: Shape<T>[`error`]\n isError: boolean\n}\n\nfunction shapeSubscribe<T extends Row<unknown>>(\n shape: Shape<T>,\n callback: () => void\n) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row<unknown>>(\n shape: Shape<T>\n): UseShapeResult<T> {\n return {\n data: shape.currentRows,\n isLoading: shape.isLoading(),\n lastSyncedAt: shape.lastSyncedAt(),\n isError: shape.error !== false,\n shape,\n stream: shape.stream as ShapeStream<T>,\n error: shape.error,\n }\n}\n\nfunction shapeResultChanged<T extends Row<unknown>>(\n oldRes: UseShapeResult<T> | undefined,\n newRes: UseShapeResult<T>\n): boolean {\n return (\n !oldRes ||\n oldRes.isLoading !== newRes.isLoading ||\n oldRes.lastSyncedAt !== newRes.lastSyncedAt ||\n oldRes.isError !== newRes.isError ||\n oldRes.error !== newRes.error ||\n oldRes.shape.lastOffset !== newRes.shape.lastOffset ||\n oldRes.shape.handle !== newRes.shape.handle\n )\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row<unknown>, Selection>\n extends ShapeStreamOptions<GetExtensions<SourceData>> {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row<unknown> = Row,\n Selection = UseShapeResult<SourceData>,\n>({\n selector = identity as (arg: UseShapeResult<SourceData>) => Selection,\n ...options\n}: UseShapeOptions<SourceData, Selection>): Selection {\n const shapeStream = getShapeStream<SourceData>(\n options as ShapeStreamOptions<GetExtensions<SourceData>>\n )\n const shape = getShape<SourceData>(shapeStream)\n\n const useShapeData = React.useMemo(() => {\n let latestShapeData: UseShapeResult<SourceData> | undefined\n\n const getSnapshot = () => {\n latestShapeData ??= parseShapeData(shape)\n return latestShapeData\n }\n\n const subscribe = (onStoreChange: () => void) => {\n // check if shape has changed between the initial snapshot\n // and subscribing, as there are no guarantees that the\n // two will occur synchronously with each other\n const newShapeData = parseShapeData(shape)\n if (shapeResultChanged(latestShapeData, newShapeData)) {\n latestShapeData = newShapeData\n onStoreChange()\n }\n\n return shapeSubscribe(shape, () => {\n latestShapeData = parseShapeData(shape)\n onStoreChange()\n })\n }\n\n return () => {\n return useSyncExternalStoreWithSelector(\n subscribe,\n getSnapshot,\n getSnapshot,\n selector\n )\n }\n }, [shape, selector])\n\n return useShapeData()\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@electric-sql/react",
3
3
  "description": "React hooks for ElectricSQL",
4
- "version": "1.0.0-beta.5",
4
+ "version": "1.0.0-beta.6",
5
5
  "author": "ElectricSQL team and contributors.",
6
6
  "bugs": {
7
7
  "url": "https://github.com/electric-sql/electric/issues"
8
8
  },
9
9
  "dependencies": {
10
10
  "use-sync-external-store": "^1.2.2",
11
- "@electric-sql/client": "1.0.0-beta.4"
11
+ "@electric-sql/client": "1.0.0-beta.5"
12
12
  },
13
13
  "devDependencies": {
14
14
  "@testing-library/react": "^16.0.0",
@@ -139,6 +139,21 @@ function parseShapeData<T extends Row<unknown>>(
139
139
  }
140
140
  }
141
141
 
142
+ function shapeResultChanged<T extends Row<unknown>>(
143
+ oldRes: UseShapeResult<T> | undefined,
144
+ newRes: UseShapeResult<T>
145
+ ): boolean {
146
+ return (
147
+ !oldRes ||
148
+ oldRes.isLoading !== newRes.isLoading ||
149
+ oldRes.lastSyncedAt !== newRes.lastSyncedAt ||
150
+ oldRes.isError !== newRes.isError ||
151
+ oldRes.error !== newRes.error ||
152
+ oldRes.shape.lastOffset !== newRes.shape.lastOffset ||
153
+ oldRes.shape.handle !== newRes.shape.handle
154
+ )
155
+ }
156
+
142
157
  function identity<T>(arg: T): T {
143
158
  return arg
144
159
  }
@@ -161,13 +176,28 @@ export function useShape<
161
176
  const shape = getShape<SourceData>(shapeStream)
162
177
 
163
178
  const useShapeData = React.useMemo(() => {
164
- let latestShapeData = parseShapeData(shape)
165
- const getSnapshot = () => latestShapeData
166
- const subscribe = (onStoreChange: () => void) =>
167
- shapeSubscribe(shape, () => {
179
+ let latestShapeData: UseShapeResult<SourceData> | undefined
180
+
181
+ const getSnapshot = () => {
182
+ latestShapeData ??= parseShapeData(shape)
183
+ return latestShapeData
184
+ }
185
+
186
+ const subscribe = (onStoreChange: () => void) => {
187
+ // check if shape has changed between the initial snapshot
188
+ // and subscribing, as there are no guarantees that the
189
+ // two will occur synchronously with each other
190
+ const newShapeData = parseShapeData(shape)
191
+ if (shapeResultChanged(latestShapeData, newShapeData)) {
192
+ latestShapeData = newShapeData
193
+ onStoreChange()
194
+ }
195
+
196
+ return shapeSubscribe(shape, () => {
168
197
  latestShapeData = parseShapeData(shape)
169
198
  onStoreChange()
170
199
  })
200
+ }
171
201
 
172
202
  return () => {
173
203
  return useSyncExternalStoreWithSelector(