@electric-sql/react 0.3.2 → 0.3.4

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/README.md CHANGED
@@ -20,7 +20,7 @@ Add `useShape` to a component
20
20
  import { useShape } from "@electric-sql/react"
21
21
 
22
22
  export default function MyComponent () {
23
- const { isUpToDate, data as fooData } = useShape({
23
+ const { isUpToDate, data } = useShape({
24
24
  url: "http://my-api.com/shape/foo",
25
25
  })
26
26
 
@@ -133,21 +133,23 @@ function useShape(_a) {
133
133
  ]);
134
134
  const shapeStream = getShapeStream(options);
135
135
  const shape = getShape(shapeStream);
136
- const latestShapeData = (0, import_react.useRef)(parseShapeData(shape));
137
- const getSnapshot = import_react.default.useCallback(() => latestShapeData.current, []);
138
- const shapeData = (0, import_with_selector.useSyncExternalStoreWithSelector)(
139
- (0, import_react.useCallback)(
140
- (onStoreChange) => shapeSubscribe(shape, () => {
141
- latestShapeData.current = parseShapeData(shape);
142
- onStoreChange();
143
- }),
144
- [shape]
145
- ),
146
- getSnapshot,
147
- getSnapshot,
148
- selector
149
- );
150
- return shapeData;
136
+ const useShapeData = import_react.default.useMemo(() => {
137
+ let latestShapeData = parseShapeData(shape);
138
+ const getSnapshot = () => latestShapeData;
139
+ const subscribe = (onStoreChange) => shapeSubscribe(shape, () => {
140
+ latestShapeData = parseShapeData(shape);
141
+ onStoreChange();
142
+ });
143
+ return () => {
144
+ return (0, import_with_selector.useSyncExternalStoreWithSelector)(
145
+ subscribe,
146
+ getSnapshot,
147
+ getSnapshot,
148
+ selector
149
+ );
150
+ };
151
+ }, [shape, selector]);
152
+ return useShapeData();
151
153
  }
152
154
  // Annotate the CommonJS export names for ESM import in node:
153
155
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts","../../src/react-hooks.tsx"],"sourcesContent":["export * from './react-hooks'\n","import {\n Value,\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n} from '@electric-sql/client'\nimport React, { useCallback, useRef } from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\nconst streamCache = new Map<string, ShapeStream>()\nconst shapeCache = new Map<ShapeStream, Shape>()\n\nexport async function preloadShape(\n options: ShapeStreamOptions\n): Promise<Shape> {\n const shapeStream = getShapeStream(options)\n const shape = getShape(shapeStream)\n await shape.value\n return shape\n}\n\nexport function sortedOptionsHash(options: ShapeStreamOptions): string {\n return JSON.stringify(options, Object.keys(options).sort())\n}\n\nexport function getShapeStream(options: ShapeStreamOptions): ShapeStream {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return\n if (streamCache.has(shapeHash)) {\n // Return the ShapeStream\n return streamCache.get(shapeHash)!\n } else {\n const newShapeStream = new ShapeStream(options)\n\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n }\n}\n\nexport function getShape(shapeStream: ShapeStream): Shape {\n // If the stream is already cached, return\n if (shapeCache.has(shapeStream)) {\n // Return the ShapeStream\n return shapeCache.get(shapeStream)!\n } else {\n const newShape = new Shape(shapeStream)\n\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n }\n}\n\nexport interface UseShapeResult {\n /**\n * The array of rows that make up the Shape.\n * @type {{ [key: string]: Value }[]}\n */\n data: { [key: string]: Value }[]\n /**\n * The Shape instance used by this useShape\n * @type(Shape)\n */\n shape: Shape\n error: Shape[`error`]\n isError: boolean\n /**\n * Has the ShapeStream caught up with the replication log from Postgres.\n */\n isUpToDate: boolean\n}\n\nfunction shapeSubscribe(shape: Shape, callback: () => void) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData(shape: Shape): UseShapeResult {\n return {\n data: [...shape.valueSync.values()],\n isUpToDate: shape.isUpToDate,\n isError: shape.error !== false,\n shape,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<Selection> extends ShapeStreamOptions {\n selector?: (value: UseShapeResult) => Selection\n}\n\nexport function useShape<Selection = UseShapeResult>({\n selector = identity as (arg: UseShapeResult) => Selection,\n ...options\n}: UseShapeOptions<Selection>): Selection {\n const shapeStream = getShapeStream(options as ShapeStreamOptions)\n const shape = getShape(shapeStream)\n\n const latestShapeData = useRef(parseShapeData(shape))\n const getSnapshot = React.useCallback(() => latestShapeData.current, [])\n const shapeData = useSyncExternalStoreWithSelector(\n useCallback(\n (onStoreChange) =>\n shapeSubscribe(shape, () => {\n latestShapeData.current = parseShapeData(shape)\n onStoreChange()\n }),\n [shape]\n ),\n getSnapshot,\n getSnapshot,\n selector\n )\n\n return shapeData\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBAKO;AACP,mBAA2C;AAC3C,2BAAiD;AAEjD,IAAM,cAAc,oBAAI,IAAyB;AACjD,IAAM,aAAa,oBAAI,IAAwB;AAE/C,SAAsB,aACpB,SACgB;AAAA;AAChB,UAAM,cAAc,eAAe,OAAO;AAC1C,UAAM,QAAQ,SAAS,WAAW;AAClC,UAAM,MAAM;AACZ,WAAO;AAAA,EACT;AAAA;AAEO,SAAS,kBAAkB,SAAqC;AACrE,SAAO,KAAK,UAAU,SAAS,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC;AAC5D;AAEO,SAAS,eAAe,SAA0C;AACvE,QAAM,YAAY,kBAAkB,OAAO;AAG3C,MAAI,YAAY,IAAI,SAAS,GAAG;AAE9B,WAAO,YAAY,IAAI,SAAS;AAAA,EAClC,OAAO;AACL,UAAM,iBAAiB,IAAI,0BAAY,OAAO;AAE9C,gBAAY,IAAI,WAAW,cAAc;AAGzC,WAAO;AAAA,EACT;AACF;AAEO,SAAS,SAAS,aAAiC;AAExD,MAAI,WAAW,IAAI,WAAW,GAAG;AAE/B,WAAO,WAAW,IAAI,WAAW;AAAA,EACnC,OAAO;AACL,UAAM,WAAW,IAAI,oBAAM,WAAW;AAEtC,eAAW,IAAI,aAAa,QAAQ;AAGpC,WAAO;AAAA,EACT;AACF;AAqBA,SAAS,eAAe,OAAc,UAAsB;AAC1D,QAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,SAAO,MAAM;AACX,gBAAY;AAAA,EACd;AACF;AAEA,SAAS,eAAe,OAA8B;AACpD,SAAO;AAAA,IACL,MAAM,CAAC,GAAG,MAAM,UAAU,OAAO,CAAC;AAAA,IAClC,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM,UAAU;AAAA,IACzB;AAAA,IACA,OAAO,MAAM;AAAA,EACf;AACF;AAEA,SAAS,SAAY,KAAW;AAC9B,SAAO;AACT;AAMO,SAAS,SAAqC,IAGX;AAHW,eACnD;AAAA,eAAW;AAAA,EAtGb,IAqGqD,IAEhD,oBAFgD,IAEhD;AAAA,IADH;AAAA;AAGA,QAAM,cAAc,eAAe,OAA6B;AAChE,QAAM,QAAQ,SAAS,WAAW;AAElC,QAAM,sBAAkB,qBAAO,eAAe,KAAK,CAAC;AACpD,QAAM,cAAc,aAAAA,QAAM,YAAY,MAAM,gBAAgB,SAAS,CAAC,CAAC;AACvE,QAAM,gBAAY;AAAA,QAChB;AAAA,MACE,CAAC,kBACC,eAAe,OAAO,MAAM;AAC1B,wBAAgB,UAAU,eAAe,KAAK;AAC9C,sBAAc;AAAA,MAChB,CAAC;AAAA,MACH,CAAC,KAAK;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AACT;","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} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\nconst streamCache = new Map<string, ShapeStream>()\nconst shapeCache = new Map<ShapeStream, Shape>()\n\nexport async function preloadShape<T extends Row = Row>(\n options: ShapeStreamOptions\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.value\n return shape\n}\n\nexport function sortedOptionsHash(options: ShapeStreamOptions): string {\n return JSON.stringify(options, Object.keys(options).sort())\n}\n\nexport function getShapeStream<T extends Row = Row>(\n options: ShapeStreamOptions\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return\n if (streamCache.has(shapeHash)) {\n // Return the ShapeStream\n return streamCache.get(shapeHash)! as ShapeStream<T>\n } else {\n const newShapeStream = new ShapeStream<T>(options)\n\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n }\n}\n\nexport function getShape<T extends Row>(shapeStream: ShapeStream<T>): Shape<T> {\n // If the stream is already cached, return\n if (shapeCache.has(shapeStream)) {\n // Return the ShapeStream\n return shapeCache.get(shapeStream)! as Shape<T>\n } else {\n const newShape = new Shape<T>(shapeStream)\n\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n }\n}\n\nexport interface UseShapeResult<T extends Row = 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 error: Shape<T>[`error`]\n isError: boolean\n /**\n * Has the ShapeStream caught up with the replication log from Postgres.\n */\n isUpToDate: boolean\n}\n\nfunction shapeSubscribe<T extends Row>(shape: Shape<T>, callback: () => void) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row>(shape: Shape<T>): UseShapeResult<T> {\n return {\n data: [...shape.valueSync.values()],\n isUpToDate: shape.isUpToDate,\n isError: shape.error !== false,\n shape,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row, Selection>\n extends ShapeStreamOptions {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row = 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>(options as ShapeStreamOptions)\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,oBAKO;AACP,mBAAkB;AAClB,2BAAiD;AAEjD,IAAM,cAAc,oBAAI,IAAyB;AACjD,IAAM,aAAa,oBAAI,IAAwB;AAE/C,SAAsB,aACpB,SACmB;AAAA;AACnB,UAAM,cAAc,eAAkB,OAAO;AAC7C,UAAM,QAAQ,SAAY,WAAW;AACrC,UAAM,MAAM;AACZ,WAAO;AAAA,EACT;AAAA;AAEO,SAAS,kBAAkB,SAAqC;AACrE,SAAO,KAAK,UAAU,SAAS,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC;AAC5D;AAEO,SAAS,eACd,SACgB;AAChB,QAAM,YAAY,kBAAkB,OAAO;AAG3C,MAAI,YAAY,IAAI,SAAS,GAAG;AAE9B,WAAO,YAAY,IAAI,SAAS;AAAA,EAClC,OAAO;AACL,UAAM,iBAAiB,IAAI,0BAAe,OAAO;AAEjD,gBAAY,IAAI,WAAW,cAAc;AAGzC,WAAO;AAAA,EACT;AACF;AAEO,SAAS,SAAwB,aAAuC;AAE7E,MAAI,WAAW,IAAI,WAAW,GAAG;AAE/B,WAAO,WAAW,IAAI,WAAW;AAAA,EACnC,OAAO;AACL,UAAM,WAAW,IAAI,oBAAS,WAAW;AAEzC,eAAW,IAAI,aAAa,QAAQ;AAGpC,WAAO;AAAA,EACT;AACF;AAqBA,SAAS,eAA8B,OAAiB,UAAsB;AAC5E,QAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,SAAO,MAAM;AACX,gBAAY;AAAA,EACd;AACF;AAEA,SAAS,eAA8B,OAAoC;AACzE,SAAO;AAAA,IACL,MAAM,CAAC,GAAG,MAAM,UAAU,OAAO,CAAC;AAAA,IAClC,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM,UAAU;AAAA,IACzB;AAAA,IACA,OAAO,MAAM;AAAA,EACf;AACF;AAEA,SAAS,SAAY,KAAW;AAC9B,SAAO;AACT;AAOO,SAAS,SAGd,IAGoD;AAHpD,eACA;AAAA,eAAW;AAAA,EA5Gb,IA2GE,IAEG,oBAFH,IAEG;AAAA,IADH;AAAA;AAGA,QAAM,cAAc,eAA2B,OAA6B;AAC5E,QAAM,QAAQ,SAAqB,WAAW;AAE9C,QAAM,eAAe,aAAAA,QAAM,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,iBAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEpB,SAAO,aAAa;AACtB;","names":["React"]}
@@ -1,2 +1,2 @@
1
- var u=Object.getOwnPropertySymbols;var g=Object.prototype.hasOwnProperty,U=Object.prototype.propertyIsEnumerable;var l=(e,t)=>{var a={};for(var r in e)g.call(e,r)&&t.indexOf(r)<0&&(a[r]=e[r]);if(e!=null&&u)for(var r of u(e))t.indexOf(r)<0&&U.call(e,r)&&(a[r]=e[r]);return a};var m=(e,t,a)=>new Promise((r,S)=>{var n=s=>{try{o(a.next(s))}catch(p){S(p)}},i=s=>{try{o(a.throw(s))}catch(p){S(p)}},o=s=>s.done?r(s.value):Promise.resolve(s.value).then(n,i);o((a=a.apply(e,t)).next())});import{Shape as x,ShapeStream as d}from"@electric-sql/client";import w,{useCallback as y,useRef as D}from"react";import{useSyncExternalStoreWithSelector as R}from"use-sync-external-store/with-selector.js";var c=new Map,h=new Map;function J(e){return m(this,null,function*(){let t=b(e),a=O(t);return yield a.value,a})}function T(e){return JSON.stringify(e,Object.keys(e).sort())}function b(e){let t=T(e);if(c.has(t))return c.get(t);{let a=new d(e);return c.set(t,a),a}}function O(e){if(h.has(e))return h.get(e);{let t=new x(e);return h.set(e,t),t}}function v(e,t){let a=e.subscribe(t);return()=>{a()}}function f(e){return{data:[...e.valueSync.values()],isUpToDate:e.isUpToDate,isError:e.error!==!1,shape:e,error:e.error}}function k(e){return e}function N(a){var r=a,{selector:e=k}=r,t=l(r,["selector"]);let S=b(t),n=O(S),i=D(f(n)),o=w.useCallback(()=>i.current,[]);return R(y(p=>v(n,()=>{i.current=f(n),p()}),[n]),o,o,e)}export{O as getShape,b as getShapeStream,J as preloadShape,T as sortedOptionsHash,N as useShape};
1
+ var u=Object.getOwnPropertySymbols;var x=Object.prototype.hasOwnProperty,d=Object.prototype.propertyIsEnumerable;var T=(e,t)=>{var a={};for(var r in e)x.call(e,r)&&t.indexOf(r)<0&&(a[r]=e[r]);if(e!=null&&u)for(var r of u(e))t.indexOf(r)<0&&d.call(e,r)&&(a[r]=e[r]);return a};var l=(e,t,a)=>new Promise((r,p)=>{var s=o=>{try{n(a.next(o))}catch(S){p(S)}},h=o=>{try{n(a.throw(o))}catch(S){p(S)}},n=o=>o.done?r(o.value):Promise.resolve(o.value).then(s,h);n((a=a.apply(e,t)).next())});import{Shape as D,ShapeStream as b}from"@electric-sql/client";import O from"react";import{useSyncExternalStoreWithSelector as U}from"use-sync-external-store/with-selector.js";var c=new Map,i=new Map;function J(e){return l(this,null,function*(){let t=w(e),a=R(t);return yield a.value,a})}function g(e){return JSON.stringify(e,Object.keys(e).sort())}function w(e){let t=g(e);if(c.has(t))return c.get(t);{let a=new b(e);return c.set(t,a),a}}function R(e){if(i.has(e))return i.get(e);{let t=new D(e);return i.set(e,t),t}}function v(e,t){let a=e.subscribe(t);return()=>{a()}}function m(e){return{data:[...e.valueSync.values()],isUpToDate:e.isUpToDate,isError:e.error!==!1,shape:e,error:e.error}}function y(e){return e}function N(a){var r=a,{selector:e=y}=r,t=T(r,["selector"]);let p=w(t),s=R(p);return O.useMemo(()=>{let n=m(s),o=()=>n,S=f=>v(s,()=>{n=m(s),f()});return()=>U(S,o,o,e)},[s,e])()}export{R as getShape,w as getShapeStream,J as preloadShape,g as sortedOptionsHash,N as useShape};
2
2
  //# sourceMappingURL=index.browser.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Value,\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n} from '@electric-sql/client'\nimport React, { useCallback, useRef } from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\nconst streamCache = new Map<string, ShapeStream>()\nconst shapeCache = new Map<ShapeStream, Shape>()\n\nexport async function preloadShape(\n options: ShapeStreamOptions\n): Promise<Shape> {\n const shapeStream = getShapeStream(options)\n const shape = getShape(shapeStream)\n await shape.value\n return shape\n}\n\nexport function sortedOptionsHash(options: ShapeStreamOptions): string {\n return JSON.stringify(options, Object.keys(options).sort())\n}\n\nexport function getShapeStream(options: ShapeStreamOptions): ShapeStream {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return\n if (streamCache.has(shapeHash)) {\n // Return the ShapeStream\n return streamCache.get(shapeHash)!\n } else {\n const newShapeStream = new ShapeStream(options)\n\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n }\n}\n\nexport function getShape(shapeStream: ShapeStream): Shape {\n // If the stream is already cached, return\n if (shapeCache.has(shapeStream)) {\n // Return the ShapeStream\n return shapeCache.get(shapeStream)!\n } else {\n const newShape = new Shape(shapeStream)\n\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n }\n}\n\nexport interface UseShapeResult {\n /**\n * The array of rows that make up the Shape.\n * @type {{ [key: string]: Value }[]}\n */\n data: { [key: string]: Value }[]\n /**\n * The Shape instance used by this useShape\n * @type(Shape)\n */\n shape: Shape\n error: Shape[`error`]\n isError: boolean\n /**\n * Has the ShapeStream caught up with the replication log from Postgres.\n */\n isUpToDate: boolean\n}\n\nfunction shapeSubscribe(shape: Shape, callback: () => void) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData(shape: Shape): UseShapeResult {\n return {\n data: [...shape.valueSync.values()],\n isUpToDate: shape.isUpToDate,\n isError: shape.error !== false,\n shape,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<Selection> extends ShapeStreamOptions {\n selector?: (value: UseShapeResult) => Selection\n}\n\nexport function useShape<Selection = UseShapeResult>({\n selector = identity as (arg: UseShapeResult) => Selection,\n ...options\n}: UseShapeOptions<Selection>): Selection {\n const shapeStream = getShapeStream(options as ShapeStreamOptions)\n const shape = getShape(shapeStream)\n\n const latestShapeData = useRef(parseShapeData(shape))\n const getSnapshot = React.useCallback(() => latestShapeData.current, [])\n const shapeData = useSyncExternalStoreWithSelector(\n useCallback(\n (onStoreChange) =>\n shapeSubscribe(shape, () => {\n latestShapeData.current = parseShapeData(shape)\n onStoreChange()\n }),\n [shape]\n ),\n getSnapshot,\n getSnapshot,\n selector\n )\n\n return shapeData\n}\n"],"mappings":"geAAA,OAEE,SAAAA,EACA,eAAAC,MAEK,uBACP,OAAOC,GAAS,eAAAC,EAAa,UAAAC,MAAc,QAC3C,OAAS,oCAAAC,MAAwC,2CAEjD,IAAMC,EAAc,IAAI,IAClBC,EAAa,IAAI,IAEvB,SAAsBC,EACpBC,EACgB,QAAAC,EAAA,sBAChB,IAAMC,EAAcC,EAAeH,CAAO,EACpCI,EAAQC,EAASH,CAAW,EAClC,aAAME,EAAM,MACLA,CACT,GAEO,SAASE,EAAkBN,EAAqC,CACrE,OAAO,KAAK,UAAUA,EAAS,OAAO,KAAKA,CAAO,EAAE,KAAK,CAAC,CAC5D,CAEO,SAASG,EAAeH,EAA0C,CACvE,IAAMO,EAAYD,EAAkBN,CAAO,EAG3C,GAAIH,EAAY,IAAIU,CAAS,EAE3B,OAAOV,EAAY,IAAIU,CAAS,EAC3B,CACL,IAAMC,EAAiB,IAAIC,EAAYT,CAAO,EAE9C,OAAAH,EAAY,IAAIU,EAAWC,CAAc,EAGlCA,CACT,CACF,CAEO,SAASH,EAASH,EAAiC,CAExD,GAAIJ,EAAW,IAAII,CAAW,EAE5B,OAAOJ,EAAW,IAAII,CAAW,EAC5B,CACL,IAAMQ,EAAW,IAAIC,EAAMT,CAAW,EAEtC,OAAAJ,EAAW,IAAII,EAAaQ,CAAQ,EAG7BA,CACT,CACF,CAqBA,SAASE,EAAeR,EAAcS,EAAsB,CAC1D,IAAMC,EAAcV,EAAM,UAAUS,CAAQ,EAC5C,MAAO,IAAM,CACXC,EAAY,CACd,CACF,CAEA,SAASC,EAAeX,EAA8B,CACpD,MAAO,CACL,KAAM,CAAC,GAAGA,EAAM,UAAU,OAAO,CAAC,EAClC,WAAYA,EAAM,WAClB,QAASA,EAAM,QAAU,GACzB,MAAAA,EACA,MAAOA,EAAM,KACf,CACF,CAEA,SAASY,EAAYC,EAAW,CAC9B,OAAOA,CACT,CAMO,SAASC,EAAqCC,EAGX,CAHW,IAAAC,EAAAD,EACnD,UAAAE,EAAWL,CAtGb,EAqGqDI,EAEhDpB,EAAAsB,EAFgDF,EAEhD,CADH,aAGA,IAAMlB,EAAcC,EAAeH,CAA6B,EAC1DI,EAAQC,EAASH,CAAW,EAE5BqB,EAAkBC,EAAOT,EAAeX,CAAK,CAAC,EAC9CqB,EAAcC,EAAM,YAAY,IAAMH,EAAgB,QAAS,CAAC,CAAC,EAevE,OAdkBI,EAChBC,EACGC,GACCjB,EAAeR,EAAO,IAAM,CAC1BmB,EAAgB,QAAUR,EAAeX,CAAK,EAC9CyB,EAAc,CAChB,CAAC,EACH,CAACzB,CAAK,CACR,EACAqB,EACAA,EACAJ,CACF,CAGF","names":["Shape","ShapeStream","React","useCallback","useRef","useSyncExternalStoreWithSelector","streamCache","shapeCache","preloadShape","options","__async","shapeStream","getShapeStream","shape","getShape","sortedOptionsHash","shapeHash","newShapeStream","ShapeStream","newShape","Shape","shapeSubscribe","callback","unsubscribe","parseShapeData","identity","arg","useShape","_a","_b","selector","__objRest","latestShapeData","useRef","getSnapshot","React","useSyncExternalStoreWithSelector","useCallback","onStoreChange"]}
1
+ {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\nconst streamCache = new Map<string, ShapeStream>()\nconst shapeCache = new Map<ShapeStream, Shape>()\n\nexport async function preloadShape<T extends Row = Row>(\n options: ShapeStreamOptions\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.value\n return shape\n}\n\nexport function sortedOptionsHash(options: ShapeStreamOptions): string {\n return JSON.stringify(options, Object.keys(options).sort())\n}\n\nexport function getShapeStream<T extends Row = Row>(\n options: ShapeStreamOptions\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return\n if (streamCache.has(shapeHash)) {\n // Return the ShapeStream\n return streamCache.get(shapeHash)! as ShapeStream<T>\n } else {\n const newShapeStream = new ShapeStream<T>(options)\n\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n }\n}\n\nexport function getShape<T extends Row>(shapeStream: ShapeStream<T>): Shape<T> {\n // If the stream is already cached, return\n if (shapeCache.has(shapeStream)) {\n // Return the ShapeStream\n return shapeCache.get(shapeStream)! as Shape<T>\n } else {\n const newShape = new Shape<T>(shapeStream)\n\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n }\n}\n\nexport interface UseShapeResult<T extends Row = 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 error: Shape<T>[`error`]\n isError: boolean\n /**\n * Has the ShapeStream caught up with the replication log from Postgres.\n */\n isUpToDate: boolean\n}\n\nfunction shapeSubscribe<T extends Row>(shape: Shape<T>, callback: () => void) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row>(shape: Shape<T>): UseShapeResult<T> {\n return {\n data: [...shape.valueSync.values()],\n isUpToDate: shape.isUpToDate,\n isError: shape.error !== false,\n shape,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row, Selection>\n extends ShapeStreamOptions {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row = 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>(options as ShapeStreamOptions)\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,MAGK,uBACP,OAAOC,MAAW,QAClB,OAAS,oCAAAC,MAAwC,2CAEjD,IAAMC,EAAc,IAAI,IAClBC,EAAa,IAAI,IAEvB,SAAsBC,EACpBC,EACmB,QAAAC,EAAA,sBACnB,IAAMC,EAAcC,EAAkBH,CAAO,EACvCI,EAAQC,EAAYH,CAAW,EACrC,aAAME,EAAM,MACLA,CACT,GAEO,SAASE,EAAkBN,EAAqC,CACrE,OAAO,KAAK,UAAUA,EAAS,OAAO,KAAKA,CAAO,EAAE,KAAK,CAAC,CAC5D,CAEO,SAASG,EACdH,EACgB,CAChB,IAAMO,EAAYD,EAAkBN,CAAO,EAG3C,GAAIH,EAAY,IAAIU,CAAS,EAE3B,OAAOV,EAAY,IAAIU,CAAS,EAC3B,CACL,IAAMC,EAAiB,IAAIC,EAAeT,CAAO,EAEjD,OAAAH,EAAY,IAAIU,EAAWC,CAAc,EAGlCA,CACT,CACF,CAEO,SAASH,EAAwBH,EAAuC,CAE7E,GAAIJ,EAAW,IAAII,CAAW,EAE5B,OAAOJ,EAAW,IAAII,CAAW,EAC5B,CACL,IAAMQ,EAAW,IAAIC,EAAST,CAAW,EAEzC,OAAAJ,EAAW,IAAII,EAAaQ,CAAQ,EAG7BA,CACT,CACF,CAqBA,SAASE,EAA8BR,EAAiBS,EAAsB,CAC5E,IAAMC,EAAcV,EAAM,UAAUS,CAAQ,EAC5C,MAAO,IAAM,CACXC,EAAY,CACd,CACF,CAEA,SAASC,EAA8BX,EAAoC,CACzE,MAAO,CACL,KAAM,CAAC,GAAGA,EAAM,UAAU,OAAO,CAAC,EAClC,WAAYA,EAAM,WAClB,QAASA,EAAM,QAAU,GACzB,MAAAA,EACA,MAAOA,EAAM,KACf,CACF,CAEA,SAASY,EAAYC,EAAW,CAC9B,OAAOA,CACT,CAOO,SAASC,EAGdC,EAGoD,CAHpD,IAAAC,EAAAD,EACA,UAAAE,EAAWL,CA5Gb,EA2GEI,EAEGpB,EAAAsB,EAFHF,EAEG,CADH,aAGA,IAAMlB,EAAcC,EAA2BH,CAA6B,EACtEI,EAAQC,EAAqBH,CAAW,EAqB9C,OAnBqBqB,EAAM,QAAQ,IAAM,CACvC,IAAIC,EAAkBT,EAAeX,CAAK,EACpCqB,EAAc,IAAMD,EACpBE,EAAaC,GACjBf,EAAeR,EAAO,IAAM,CAC1BoB,EAAkBT,EAAeX,CAAK,EACtCuB,EAAc,CAChB,CAAC,EAEH,MAAO,IACEC,EACLF,EACAD,EACAA,EACAJ,CACF,CAEJ,EAAG,CAACjB,EAAOiB,CAAQ,CAAC,EAEA,CACtB","names":["Shape","ShapeStream","React","useSyncExternalStoreWithSelector","streamCache","shapeCache","preloadShape","options","__async","shapeStream","getShapeStream","shape","getShape","sortedOptionsHash","shapeHash","newShapeStream","ShapeStream","newShape","Shape","shapeSubscribe","callback","unsubscribe","parseShapeData","identity","arg","useShape","_a","_b","selector","__objRest","React","latestShapeData","getSnapshot","subscribe","onStoreChange","useSyncExternalStoreWithSelector"]}
package/dist/index.d.ts CHANGED
@@ -1,32 +1,30 @@
1
- import { ShapeStreamOptions, Shape, ShapeStream, Value } from '@electric-sql/client';
1
+ import { Row, ShapeStreamOptions, Shape, ShapeStream } from '@electric-sql/client';
2
2
 
3
- declare function preloadShape(options: ShapeStreamOptions): Promise<Shape>;
3
+ declare function preloadShape<T extends Row = Row>(options: ShapeStreamOptions): Promise<Shape<T>>;
4
4
  declare function sortedOptionsHash(options: ShapeStreamOptions): string;
5
- declare function getShapeStream(options: ShapeStreamOptions): ShapeStream;
6
- declare function getShape(shapeStream: ShapeStream): Shape;
7
- interface UseShapeResult {
5
+ declare function getShapeStream<T extends Row = Row>(options: ShapeStreamOptions): ShapeStream<T>;
6
+ declare function getShape<T extends Row>(shapeStream: ShapeStream<T>): Shape<T>;
7
+ interface UseShapeResult<T extends Row = Row> {
8
8
  /**
9
9
  * The array of rows that make up the Shape.
10
- * @type {{ [key: string]: Value }[]}
10
+ * @type {T[]}
11
11
  */
12
- data: {
13
- [key: string]: Value;
14
- }[];
12
+ data: T[];
15
13
  /**
16
14
  * The Shape instance used by this useShape
17
- * @type(Shape)
15
+ * @type {Shape<T>}
18
16
  */
19
- shape: Shape;
20
- error: Shape[`error`];
17
+ shape: Shape<T>;
18
+ error: Shape<T>[`error`];
21
19
  isError: boolean;
22
20
  /**
23
21
  * Has the ShapeStream caught up with the replication log from Postgres.
24
22
  */
25
23
  isUpToDate: boolean;
26
24
  }
27
- interface UseShapeOptions<Selection> extends ShapeStreamOptions {
28
- selector?: (value: UseShapeResult) => Selection;
25
+ interface UseShapeOptions<SourceData extends Row, Selection> extends ShapeStreamOptions {
26
+ selector?: (value: UseShapeResult<SourceData>) => Selection;
29
27
  }
30
- declare function useShape<Selection = UseShapeResult>({ selector, ...options }: UseShapeOptions<Selection>): Selection;
28
+ declare function useShape<SourceData extends Row = Row, Selection = UseShapeResult<SourceData>>({ selector, ...options }: UseShapeOptions<SourceData, Selection>): Selection;
31
29
 
32
30
  export { type UseShapeResult, getShape, getShapeStream, preloadShape, sortedOptionsHash, useShape };
@@ -19,7 +19,7 @@ import {
19
19
  Shape,
20
20
  ShapeStream
21
21
  } from "@electric-sql/client";
22
- import React, { useCallback, useRef } from "react";
22
+ import React from "react";
23
23
  import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";
24
24
  var streamCache = /* @__PURE__ */ new Map();
25
25
  var shapeCache = /* @__PURE__ */ new Map();
@@ -77,21 +77,23 @@ function useShape(_a) {
77
77
  ]);
78
78
  const shapeStream = getShapeStream(options);
79
79
  const shape = getShape(shapeStream);
80
- const latestShapeData = useRef(parseShapeData(shape));
81
- const getSnapshot = React.useCallback(() => latestShapeData.current, []);
82
- const shapeData = useSyncExternalStoreWithSelector(
83
- useCallback(
84
- (onStoreChange) => shapeSubscribe(shape, () => {
85
- latestShapeData.current = parseShapeData(shape);
86
- onStoreChange();
87
- }),
88
- [shape]
89
- ),
90
- getSnapshot,
91
- getSnapshot,
92
- selector
93
- );
94
- return shapeData;
80
+ const useShapeData = React.useMemo(() => {
81
+ let latestShapeData = parseShapeData(shape);
82
+ const getSnapshot = () => latestShapeData;
83
+ const subscribe = (onStoreChange) => shapeSubscribe(shape, () => {
84
+ latestShapeData = parseShapeData(shape);
85
+ onStoreChange();
86
+ });
87
+ return () => {
88
+ return useSyncExternalStoreWithSelector(
89
+ subscribe,
90
+ getSnapshot,
91
+ getSnapshot,
92
+ selector
93
+ );
94
+ };
95
+ }, [shape, selector]);
96
+ return useShapeData();
95
97
  }
96
98
  export {
97
99
  getShape,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Value,\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n} from '@electric-sql/client'\nimport React, { useCallback, useRef } from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\nconst streamCache = new Map<string, ShapeStream>()\nconst shapeCache = new Map<ShapeStream, Shape>()\n\nexport async function preloadShape(\n options: ShapeStreamOptions\n): Promise<Shape> {\n const shapeStream = getShapeStream(options)\n const shape = getShape(shapeStream)\n await shape.value\n return shape\n}\n\nexport function sortedOptionsHash(options: ShapeStreamOptions): string {\n return JSON.stringify(options, Object.keys(options).sort())\n}\n\nexport function getShapeStream(options: ShapeStreamOptions): ShapeStream {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return\n if (streamCache.has(shapeHash)) {\n // Return the ShapeStream\n return streamCache.get(shapeHash)!\n } else {\n const newShapeStream = new ShapeStream(options)\n\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n }\n}\n\nexport function getShape(shapeStream: ShapeStream): Shape {\n // If the stream is already cached, return\n if (shapeCache.has(shapeStream)) {\n // Return the ShapeStream\n return shapeCache.get(shapeStream)!\n } else {\n const newShape = new Shape(shapeStream)\n\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n }\n}\n\nexport interface UseShapeResult {\n /**\n * The array of rows that make up the Shape.\n * @type {{ [key: string]: Value }[]}\n */\n data: { [key: string]: Value }[]\n /**\n * The Shape instance used by this useShape\n * @type(Shape)\n */\n shape: Shape\n error: Shape[`error`]\n isError: boolean\n /**\n * Has the ShapeStream caught up with the replication log from Postgres.\n */\n isUpToDate: boolean\n}\n\nfunction shapeSubscribe(shape: Shape, callback: () => void) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData(shape: Shape): UseShapeResult {\n return {\n data: [...shape.valueSync.values()],\n isUpToDate: shape.isUpToDate,\n isError: shape.error !== false,\n shape,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<Selection> extends ShapeStreamOptions {\n selector?: (value: UseShapeResult) => Selection\n}\n\nexport function useShape<Selection = UseShapeResult>({\n selector = identity as (arg: UseShapeResult) => Selection,\n ...options\n}: UseShapeOptions<Selection>): Selection {\n const shapeStream = getShapeStream(options as ShapeStreamOptions)\n const shape = getShape(shapeStream)\n\n const latestShapeData = useRef(parseShapeData(shape))\n const getSnapshot = React.useCallback(() => latestShapeData.current, [])\n const shapeData = useSyncExternalStoreWithSelector(\n useCallback(\n (onStoreChange) =>\n shapeSubscribe(shape, () => {\n latestShapeData.current = parseShapeData(shape)\n onStoreChange()\n }),\n [shape]\n ),\n getSnapshot,\n getSnapshot,\n selector\n )\n\n return shapeData\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAAA;AAAA,EAEE;AAAA,EACA;AAAA,OAEK;AACP,OAAO,SAAS,aAAa,cAAc;AAC3C,SAAS,wCAAwC;AAEjD,IAAM,cAAc,oBAAI,IAAyB;AACjD,IAAM,aAAa,oBAAI,IAAwB;AAE/C,eAAsB,aACpB,SACgB;AAChB,QAAM,cAAc,eAAe,OAAO;AAC1C,QAAM,QAAQ,SAAS,WAAW;AAClC,QAAM,MAAM;AACZ,SAAO;AACT;AAEO,SAAS,kBAAkB,SAAqC;AACrE,SAAO,KAAK,UAAU,SAAS,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC;AAC5D;AAEO,SAAS,eAAe,SAA0C;AACvE,QAAM,YAAY,kBAAkB,OAAO;AAG3C,MAAI,YAAY,IAAI,SAAS,GAAG;AAE9B,WAAO,YAAY,IAAI,SAAS;AAAA,EAClC,OAAO;AACL,UAAM,iBAAiB,IAAI,YAAY,OAAO;AAE9C,gBAAY,IAAI,WAAW,cAAc;AAGzC,WAAO;AAAA,EACT;AACF;AAEO,SAAS,SAAS,aAAiC;AAExD,MAAI,WAAW,IAAI,WAAW,GAAG;AAE/B,WAAO,WAAW,IAAI,WAAW;AAAA,EACnC,OAAO;AACL,UAAM,WAAW,IAAI,MAAM,WAAW;AAEtC,eAAW,IAAI,aAAa,QAAQ;AAGpC,WAAO;AAAA,EACT;AACF;AAqBA,SAAS,eAAe,OAAc,UAAsB;AAC1D,QAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,SAAO,MAAM;AACX,gBAAY;AAAA,EACd;AACF;AAEA,SAAS,eAAe,OAA8B;AACpD,SAAO;AAAA,IACL,MAAM,CAAC,GAAG,MAAM,UAAU,OAAO,CAAC;AAAA,IAClC,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM,UAAU;AAAA,IACzB;AAAA,IACA,OAAO,MAAM;AAAA,EACf;AACF;AAEA,SAAS,SAAY,KAAW;AAC9B,SAAO;AACT;AAMO,SAAS,SAAqC,IAGX;AAHW,eACnD;AAAA,eAAW;AAAA,EAtGb,IAqGqD,IAEhD,oBAFgD,IAEhD;AAAA,IADH;AAAA;AAGA,QAAM,cAAc,eAAe,OAA6B;AAChE,QAAM,QAAQ,SAAS,WAAW;AAElC,QAAM,kBAAkB,OAAO,eAAe,KAAK,CAAC;AACpD,QAAM,cAAc,MAAM,YAAY,MAAM,gBAAgB,SAAS,CAAC,CAAC;AACvE,QAAM,YAAY;AAAA,IAChB;AAAA,MACE,CAAC,kBACC,eAAe,OAAO,MAAM;AAC1B,wBAAgB,UAAU,eAAe,KAAK;AAC9C,sBAAc;AAAA,MAChB,CAAC;AAAA,MACH,CAAC,KAAK;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\nconst streamCache = new Map<string, ShapeStream>()\nconst shapeCache = new Map<ShapeStream, Shape>()\n\nexport async function preloadShape<T extends Row = Row>(\n options: ShapeStreamOptions\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.value\n return shape\n}\n\nexport function sortedOptionsHash(options: ShapeStreamOptions): string {\n return JSON.stringify(options, Object.keys(options).sort())\n}\n\nexport function getShapeStream<T extends Row = Row>(\n options: ShapeStreamOptions\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return\n if (streamCache.has(shapeHash)) {\n // Return the ShapeStream\n return streamCache.get(shapeHash)! as ShapeStream<T>\n } else {\n const newShapeStream = new ShapeStream<T>(options)\n\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n }\n}\n\nexport function getShape<T extends Row>(shapeStream: ShapeStream<T>): Shape<T> {\n // If the stream is already cached, return\n if (shapeCache.has(shapeStream)) {\n // Return the ShapeStream\n return shapeCache.get(shapeStream)! as Shape<T>\n } else {\n const newShape = new Shape<T>(shapeStream)\n\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n }\n}\n\nexport interface UseShapeResult<T extends Row = 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 error: Shape<T>[`error`]\n isError: boolean\n /**\n * Has the ShapeStream caught up with the replication log from Postgres.\n */\n isUpToDate: boolean\n}\n\nfunction shapeSubscribe<T extends Row>(shape: Shape<T>, callback: () => void) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row>(shape: Shape<T>): UseShapeResult<T> {\n return {\n data: [...shape.valueSync.values()],\n isUpToDate: shape.isUpToDate,\n isError: shape.error !== false,\n shape,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row, Selection>\n extends ShapeStreamOptions {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row = 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>(options as ShapeStreamOptions)\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,OAGK;AACP,OAAO,WAAW;AAClB,SAAS,wCAAwC;AAEjD,IAAM,cAAc,oBAAI,IAAyB;AACjD,IAAM,aAAa,oBAAI,IAAwB;AAE/C,eAAsB,aACpB,SACmB;AACnB,QAAM,cAAc,eAAkB,OAAO;AAC7C,QAAM,QAAQ,SAAY,WAAW;AACrC,QAAM,MAAM;AACZ,SAAO;AACT;AAEO,SAAS,kBAAkB,SAAqC;AACrE,SAAO,KAAK,UAAU,SAAS,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC;AAC5D;AAEO,SAAS,eACd,SACgB;AAChB,QAAM,YAAY,kBAAkB,OAAO;AAG3C,MAAI,YAAY,IAAI,SAAS,GAAG;AAE9B,WAAO,YAAY,IAAI,SAAS;AAAA,EAClC,OAAO;AACL,UAAM,iBAAiB,IAAI,YAAe,OAAO;AAEjD,gBAAY,IAAI,WAAW,cAAc;AAGzC,WAAO;AAAA,EACT;AACF;AAEO,SAAS,SAAwB,aAAuC;AAE7E,MAAI,WAAW,IAAI,WAAW,GAAG;AAE/B,WAAO,WAAW,IAAI,WAAW;AAAA,EACnC,OAAO;AACL,UAAM,WAAW,IAAI,MAAS,WAAW;AAEzC,eAAW,IAAI,aAAa,QAAQ;AAGpC,WAAO;AAAA,EACT;AACF;AAqBA,SAAS,eAA8B,OAAiB,UAAsB;AAC5E,QAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,SAAO,MAAM;AACX,gBAAY;AAAA,EACd;AACF;AAEA,SAAS,eAA8B,OAAoC;AACzE,SAAO;AAAA,IACL,MAAM,CAAC,GAAG,MAAM,UAAU,OAAO,CAAC;AAAA,IAClC,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM,UAAU;AAAA,IACzB;AAAA,IACA,OAAO,MAAM;AAAA,EACf;AACF;AAEA,SAAS,SAAY,KAAW;AAC9B,SAAO;AACT;AAOO,SAAS,SAGd,IAGoD;AAHpD,eACA;AAAA,eAAW;AAAA,EA5Gb,IA2GE,IAEG,oBAFH,IAEG;AAAA,IADH;AAAA;AAGA,QAAM,cAAc,eAA2B,OAA6B;AAC5E,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":[]}
package/dist/index.mjs CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  Shape,
40
40
  ShapeStream
41
41
  } from "@electric-sql/client";
42
- import React, { useCallback, useRef } from "react";
42
+ import React from "react";
43
43
  import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";
44
44
  var streamCache = /* @__PURE__ */ new Map();
45
45
  var shapeCache = /* @__PURE__ */ new Map();
@@ -99,21 +99,23 @@ function useShape(_a) {
99
99
  ]);
100
100
  const shapeStream = getShapeStream(options);
101
101
  const shape = getShape(shapeStream);
102
- const latestShapeData = useRef(parseShapeData(shape));
103
- const getSnapshot = React.useCallback(() => latestShapeData.current, []);
104
- const shapeData = useSyncExternalStoreWithSelector(
105
- useCallback(
106
- (onStoreChange) => shapeSubscribe(shape, () => {
107
- latestShapeData.current = parseShapeData(shape);
108
- onStoreChange();
109
- }),
110
- [shape]
111
- ),
112
- getSnapshot,
113
- getSnapshot,
114
- selector
115
- );
116
- return shapeData;
102
+ const useShapeData = React.useMemo(() => {
103
+ let latestShapeData = parseShapeData(shape);
104
+ const getSnapshot = () => latestShapeData;
105
+ const subscribe = (onStoreChange) => shapeSubscribe(shape, () => {
106
+ latestShapeData = parseShapeData(shape);
107
+ onStoreChange();
108
+ });
109
+ return () => {
110
+ return useSyncExternalStoreWithSelector(
111
+ subscribe,
112
+ getSnapshot,
113
+ getSnapshot,
114
+ selector
115
+ );
116
+ };
117
+ }, [shape, selector]);
118
+ return useShapeData();
117
119
  }
118
120
  export {
119
121
  getShape,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Value,\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n} from '@electric-sql/client'\nimport React, { useCallback, useRef } from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\nconst streamCache = new Map<string, ShapeStream>()\nconst shapeCache = new Map<ShapeStream, Shape>()\n\nexport async function preloadShape(\n options: ShapeStreamOptions\n): Promise<Shape> {\n const shapeStream = getShapeStream(options)\n const shape = getShape(shapeStream)\n await shape.value\n return shape\n}\n\nexport function sortedOptionsHash(options: ShapeStreamOptions): string {\n return JSON.stringify(options, Object.keys(options).sort())\n}\n\nexport function getShapeStream(options: ShapeStreamOptions): ShapeStream {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return\n if (streamCache.has(shapeHash)) {\n // Return the ShapeStream\n return streamCache.get(shapeHash)!\n } else {\n const newShapeStream = new ShapeStream(options)\n\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n }\n}\n\nexport function getShape(shapeStream: ShapeStream): Shape {\n // If the stream is already cached, return\n if (shapeCache.has(shapeStream)) {\n // Return the ShapeStream\n return shapeCache.get(shapeStream)!\n } else {\n const newShape = new Shape(shapeStream)\n\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n }\n}\n\nexport interface UseShapeResult {\n /**\n * The array of rows that make up the Shape.\n * @type {{ [key: string]: Value }[]}\n */\n data: { [key: string]: Value }[]\n /**\n * The Shape instance used by this useShape\n * @type(Shape)\n */\n shape: Shape\n error: Shape[`error`]\n isError: boolean\n /**\n * Has the ShapeStream caught up with the replication log from Postgres.\n */\n isUpToDate: boolean\n}\n\nfunction shapeSubscribe(shape: Shape, callback: () => void) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData(shape: Shape): UseShapeResult {\n return {\n data: [...shape.valueSync.values()],\n isUpToDate: shape.isUpToDate,\n isError: shape.error !== false,\n shape,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<Selection> extends ShapeStreamOptions {\n selector?: (value: UseShapeResult) => Selection\n}\n\nexport function useShape<Selection = UseShapeResult>({\n selector = identity as (arg: UseShapeResult) => Selection,\n ...options\n}: UseShapeOptions<Selection>): Selection {\n const shapeStream = getShapeStream(options as ShapeStreamOptions)\n const shape = getShape(shapeStream)\n\n const latestShapeData = useRef(parseShapeData(shape))\n const getSnapshot = React.useCallback(() => latestShapeData.current, [])\n const shapeData = useSyncExternalStoreWithSelector(\n useCallback(\n (onStoreChange) =>\n shapeSubscribe(shape, () => {\n latestShapeData.current = parseShapeData(shape)\n onStoreChange()\n }),\n [shape]\n ),\n getSnapshot,\n getSnapshot,\n selector\n )\n\n return shapeData\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EAEE;AAAA,EACA;AAAA,OAEK;AACP,OAAO,SAAS,aAAa,cAAc;AAC3C,SAAS,wCAAwC;AAEjD,IAAM,cAAc,oBAAI,IAAyB;AACjD,IAAM,aAAa,oBAAI,IAAwB;AAE/C,SAAsB,aACpB,SACgB;AAAA;AAChB,UAAM,cAAc,eAAe,OAAO;AAC1C,UAAM,QAAQ,SAAS,WAAW;AAClC,UAAM,MAAM;AACZ,WAAO;AAAA,EACT;AAAA;AAEO,SAAS,kBAAkB,SAAqC;AACrE,SAAO,KAAK,UAAU,SAAS,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC;AAC5D;AAEO,SAAS,eAAe,SAA0C;AACvE,QAAM,YAAY,kBAAkB,OAAO;AAG3C,MAAI,YAAY,IAAI,SAAS,GAAG;AAE9B,WAAO,YAAY,IAAI,SAAS;AAAA,EAClC,OAAO;AACL,UAAM,iBAAiB,IAAI,YAAY,OAAO;AAE9C,gBAAY,IAAI,WAAW,cAAc;AAGzC,WAAO;AAAA,EACT;AACF;AAEO,SAAS,SAAS,aAAiC;AAExD,MAAI,WAAW,IAAI,WAAW,GAAG;AAE/B,WAAO,WAAW,IAAI,WAAW;AAAA,EACnC,OAAO;AACL,UAAM,WAAW,IAAI,MAAM,WAAW;AAEtC,eAAW,IAAI,aAAa,QAAQ;AAGpC,WAAO;AAAA,EACT;AACF;AAqBA,SAAS,eAAe,OAAc,UAAsB;AAC1D,QAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,SAAO,MAAM;AACX,gBAAY;AAAA,EACd;AACF;AAEA,SAAS,eAAe,OAA8B;AACpD,SAAO;AAAA,IACL,MAAM,CAAC,GAAG,MAAM,UAAU,OAAO,CAAC;AAAA,IAClC,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM,UAAU;AAAA,IACzB;AAAA,IACA,OAAO,MAAM;AAAA,EACf;AACF;AAEA,SAAS,SAAY,KAAW;AAC9B,SAAO;AACT;AAMO,SAAS,SAAqC,IAGX;AAHW,eACnD;AAAA,eAAW;AAAA,EAtGb,IAqGqD,IAEhD,oBAFgD,IAEhD;AAAA,IADH;AAAA;AAGA,QAAM,cAAc,eAAe,OAA6B;AAChE,QAAM,QAAQ,SAAS,WAAW;AAElC,QAAM,kBAAkB,OAAO,eAAe,KAAK,CAAC;AACpD,QAAM,cAAc,MAAM,YAAY,MAAM,gBAAgB,SAAS,CAAC,CAAC;AACvE,QAAM,YAAY;AAAA,IAChB;AAAA,MACE,CAAC,kBACC,eAAe,OAAO,MAAM;AAC1B,wBAAgB,UAAU,eAAe,KAAK;AAC9C,sBAAc;AAAA,MAChB,CAAC;AAAA,MACH,CAAC,KAAK;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/react-hooks.tsx"],"sourcesContent":["import {\n Shape,\n ShapeStream,\n ShapeStreamOptions,\n Row,\n} from '@electric-sql/client'\nimport React from 'react'\nimport { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'\n\nconst streamCache = new Map<string, ShapeStream>()\nconst shapeCache = new Map<ShapeStream, Shape>()\n\nexport async function preloadShape<T extends Row = Row>(\n options: ShapeStreamOptions\n): Promise<Shape<T>> {\n const shapeStream = getShapeStream<T>(options)\n const shape = getShape<T>(shapeStream)\n await shape.value\n return shape\n}\n\nexport function sortedOptionsHash(options: ShapeStreamOptions): string {\n return JSON.stringify(options, Object.keys(options).sort())\n}\n\nexport function getShapeStream<T extends Row = Row>(\n options: ShapeStreamOptions\n): ShapeStream<T> {\n const shapeHash = sortedOptionsHash(options)\n\n // If the stream is already cached, return\n if (streamCache.has(shapeHash)) {\n // Return the ShapeStream\n return streamCache.get(shapeHash)! as ShapeStream<T>\n } else {\n const newShapeStream = new ShapeStream<T>(options)\n\n streamCache.set(shapeHash, newShapeStream)\n\n // Return the created shape\n return newShapeStream\n }\n}\n\nexport function getShape<T extends Row>(shapeStream: ShapeStream<T>): Shape<T> {\n // If the stream is already cached, return\n if (shapeCache.has(shapeStream)) {\n // Return the ShapeStream\n return shapeCache.get(shapeStream)! as Shape<T>\n } else {\n const newShape = new Shape<T>(shapeStream)\n\n shapeCache.set(shapeStream, newShape)\n\n // Return the created shape\n return newShape\n }\n}\n\nexport interface UseShapeResult<T extends Row = 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 error: Shape<T>[`error`]\n isError: boolean\n /**\n * Has the ShapeStream caught up with the replication log from Postgres.\n */\n isUpToDate: boolean\n}\n\nfunction shapeSubscribe<T extends Row>(shape: Shape<T>, callback: () => void) {\n const unsubscribe = shape.subscribe(callback)\n return () => {\n unsubscribe()\n }\n}\n\nfunction parseShapeData<T extends Row>(shape: Shape<T>): UseShapeResult<T> {\n return {\n data: [...shape.valueSync.values()],\n isUpToDate: shape.isUpToDate,\n isError: shape.error !== false,\n shape,\n error: shape.error,\n }\n}\n\nfunction identity<T>(arg: T): T {\n return arg\n}\n\ninterface UseShapeOptions<SourceData extends Row, Selection>\n extends ShapeStreamOptions {\n selector?: (value: UseShapeResult<SourceData>) => Selection\n}\n\nexport function useShape<\n SourceData extends Row = 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>(options as ShapeStreamOptions)\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,OAGK;AACP,OAAO,WAAW;AAClB,SAAS,wCAAwC;AAEjD,IAAM,cAAc,oBAAI,IAAyB;AACjD,IAAM,aAAa,oBAAI,IAAwB;AAE/C,SAAsB,aACpB,SACmB;AAAA;AACnB,UAAM,cAAc,eAAkB,OAAO;AAC7C,UAAM,QAAQ,SAAY,WAAW;AACrC,UAAM,MAAM;AACZ,WAAO;AAAA,EACT;AAAA;AAEO,SAAS,kBAAkB,SAAqC;AACrE,SAAO,KAAK,UAAU,SAAS,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC;AAC5D;AAEO,SAAS,eACd,SACgB;AAChB,QAAM,YAAY,kBAAkB,OAAO;AAG3C,MAAI,YAAY,IAAI,SAAS,GAAG;AAE9B,WAAO,YAAY,IAAI,SAAS;AAAA,EAClC,OAAO;AACL,UAAM,iBAAiB,IAAI,YAAe,OAAO;AAEjD,gBAAY,IAAI,WAAW,cAAc;AAGzC,WAAO;AAAA,EACT;AACF;AAEO,SAAS,SAAwB,aAAuC;AAE7E,MAAI,WAAW,IAAI,WAAW,GAAG;AAE/B,WAAO,WAAW,IAAI,WAAW;AAAA,EACnC,OAAO;AACL,UAAM,WAAW,IAAI,MAAS,WAAW;AAEzC,eAAW,IAAI,aAAa,QAAQ;AAGpC,WAAO;AAAA,EACT;AACF;AAqBA,SAAS,eAA8B,OAAiB,UAAsB;AAC5E,QAAM,cAAc,MAAM,UAAU,QAAQ;AAC5C,SAAO,MAAM;AACX,gBAAY;AAAA,EACd;AACF;AAEA,SAAS,eAA8B,OAAoC;AACzE,SAAO;AAAA,IACL,MAAM,CAAC,GAAG,MAAM,UAAU,OAAO,CAAC;AAAA,IAClC,YAAY,MAAM;AAAA,IAClB,SAAS,MAAM,UAAU;AAAA,IACzB;AAAA,IACA,OAAO,MAAM;AAAA,EACf;AACF;AAEA,SAAS,SAAY,KAAW;AAC9B,SAAO;AACT;AAOO,SAAS,SAGd,IAGoD;AAHpD,eACA;AAAA,eAAW;AAAA,EA5Gb,IA2GE,IAEG,oBAFH,IAEG;AAAA,IADH;AAAA;AAGA,QAAM,cAAc,eAA2B,OAA6B;AAC5E,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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electric-sql/react",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "React hooks for ElectricSQL",
5
5
  "type": "module",
6
6
  "main": "dist/cjs/index.cjs",
@@ -31,7 +31,7 @@
31
31
  "homepage": "https://next.electric-sql.com",
32
32
  "dependencies": {
33
33
  "use-sync-external-store": "^1.2.2",
34
- "@electric-sql/client": "0.3.3"
34
+ "@electric-sql/client": "0.3.4"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@testing-library/react": "^16.0.0",
@@ -46,7 +46,7 @@
46
46
  "eslint-config-prettier": "^9.1.0",
47
47
  "eslint-plugin-prettier": "^5.1.3",
48
48
  "glob": "^10.3.10",
49
- "global-jsdom": "24.0.0",
49
+ "jsdom": "^25.0.0",
50
50
  "pg": "^8.12.0",
51
51
  "prettier": "^3.3.2",
52
52
  "react": "^18.3.1",
@@ -1,20 +1,20 @@
1
1
  import {
2
- Value,
3
2
  Shape,
4
3
  ShapeStream,
5
4
  ShapeStreamOptions,
5
+ Row,
6
6
  } from '@electric-sql/client'
7
- import React, { useCallback, useRef } from 'react'
7
+ import React from 'react'
8
8
  import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/with-selector.js'
9
9
 
10
10
  const streamCache = new Map<string, ShapeStream>()
11
11
  const shapeCache = new Map<ShapeStream, Shape>()
12
12
 
13
- export async function preloadShape(
13
+ export async function preloadShape<T extends Row = Row>(
14
14
  options: ShapeStreamOptions
15
- ): Promise<Shape> {
16
- const shapeStream = getShapeStream(options)
17
- const shape = getShape(shapeStream)
15
+ ): Promise<Shape<T>> {
16
+ const shapeStream = getShapeStream<T>(options)
17
+ const shape = getShape<T>(shapeStream)
18
18
  await shape.value
19
19
  return shape
20
20
  }
@@ -23,15 +23,17 @@ export function sortedOptionsHash(options: ShapeStreamOptions): string {
23
23
  return JSON.stringify(options, Object.keys(options).sort())
24
24
  }
25
25
 
26
- export function getShapeStream(options: ShapeStreamOptions): ShapeStream {
26
+ export function getShapeStream<T extends Row = Row>(
27
+ options: ShapeStreamOptions
28
+ ): ShapeStream<T> {
27
29
  const shapeHash = sortedOptionsHash(options)
28
30
 
29
31
  // If the stream is already cached, return
30
32
  if (streamCache.has(shapeHash)) {
31
33
  // Return the ShapeStream
32
- return streamCache.get(shapeHash)!
34
+ return streamCache.get(shapeHash)! as ShapeStream<T>
33
35
  } else {
34
- const newShapeStream = new ShapeStream(options)
36
+ const newShapeStream = new ShapeStream<T>(options)
35
37
 
36
38
  streamCache.set(shapeHash, newShapeStream)
37
39
 
@@ -40,13 +42,13 @@ export function getShapeStream(options: ShapeStreamOptions): ShapeStream {
40
42
  }
41
43
  }
42
44
 
43
- export function getShape(shapeStream: ShapeStream): Shape {
45
+ export function getShape<T extends Row>(shapeStream: ShapeStream<T>): Shape<T> {
44
46
  // If the stream is already cached, return
45
47
  if (shapeCache.has(shapeStream)) {
46
48
  // Return the ShapeStream
47
- return shapeCache.get(shapeStream)!
49
+ return shapeCache.get(shapeStream)! as Shape<T>
48
50
  } else {
49
- const newShape = new Shape(shapeStream)
51
+ const newShape = new Shape<T>(shapeStream)
50
52
 
51
53
  shapeCache.set(shapeStream, newShape)
52
54
 
@@ -55,18 +57,18 @@ export function getShape(shapeStream: ShapeStream): Shape {
55
57
  }
56
58
  }
57
59
 
58
- export interface UseShapeResult {
60
+ export interface UseShapeResult<T extends Row = Row> {
59
61
  /**
60
62
  * The array of rows that make up the Shape.
61
- * @type {{ [key: string]: Value }[]}
63
+ * @type {T[]}
62
64
  */
63
- data: { [key: string]: Value }[]
65
+ data: T[]
64
66
  /**
65
67
  * The Shape instance used by this useShape
66
- * @type(Shape)
68
+ * @type {Shape<T>}
67
69
  */
68
- shape: Shape
69
- error: Shape[`error`]
70
+ shape: Shape<T>
71
+ error: Shape<T>[`error`]
70
72
  isError: boolean
71
73
  /**
72
74
  * Has the ShapeStream caught up with the replication log from Postgres.
@@ -74,14 +76,14 @@ export interface UseShapeResult {
74
76
  isUpToDate: boolean
75
77
  }
76
78
 
77
- function shapeSubscribe(shape: Shape, callback: () => void) {
79
+ function shapeSubscribe<T extends Row>(shape: Shape<T>, callback: () => void) {
78
80
  const unsubscribe = shape.subscribe(callback)
79
81
  return () => {
80
82
  unsubscribe()
81
83
  }
82
84
  }
83
85
 
84
- function parseShapeData(shape: Shape): UseShapeResult {
86
+ function parseShapeData<T extends Row>(shape: Shape<T>): UseShapeResult<T> {
85
87
  return {
86
88
  data: [...shape.valueSync.values()],
87
89
  isUpToDate: shape.isUpToDate,
@@ -95,32 +97,39 @@ function identity<T>(arg: T): T {
95
97
  return arg
96
98
  }
97
99
 
98
- interface UseShapeOptions<Selection> extends ShapeStreamOptions {
99
- selector?: (value: UseShapeResult) => Selection
100
+ interface UseShapeOptions<SourceData extends Row, Selection>
101
+ extends ShapeStreamOptions {
102
+ selector?: (value: UseShapeResult<SourceData>) => Selection
100
103
  }
101
104
 
102
- export function useShape<Selection = UseShapeResult>({
103
- selector = identity as (arg: UseShapeResult) => Selection,
105
+ export function useShape<
106
+ SourceData extends Row = Row,
107
+ Selection = UseShapeResult<SourceData>,
108
+ >({
109
+ selector = identity as (arg: UseShapeResult<SourceData>) => Selection,
104
110
  ...options
105
- }: UseShapeOptions<Selection>): Selection {
106
- const shapeStream = getShapeStream(options as ShapeStreamOptions)
107
- const shape = getShape(shapeStream)
108
-
109
- const latestShapeData = useRef(parseShapeData(shape))
110
- const getSnapshot = React.useCallback(() => latestShapeData.current, [])
111
- const shapeData = useSyncExternalStoreWithSelector(
112
- useCallback(
113
- (onStoreChange) =>
114
- shapeSubscribe(shape, () => {
115
- latestShapeData.current = parseShapeData(shape)
116
- onStoreChange()
117
- }),
118
- [shape]
119
- ),
120
- getSnapshot,
121
- getSnapshot,
122
- selector
123
- )
124
-
125
- return shapeData
111
+ }: UseShapeOptions<SourceData, Selection>): Selection {
112
+ const shapeStream = getShapeStream<SourceData>(options as ShapeStreamOptions)
113
+ const shape = getShape<SourceData>(shapeStream)
114
+
115
+ const useShapeData = React.useMemo(() => {
116
+ let latestShapeData = parseShapeData(shape)
117
+ const getSnapshot = () => latestShapeData
118
+ const subscribe = (onStoreChange: () => void) =>
119
+ shapeSubscribe(shape, () => {
120
+ latestShapeData = parseShapeData(shape)
121
+ onStoreChange()
122
+ })
123
+
124
+ return () => {
125
+ return useSyncExternalStoreWithSelector(
126
+ subscribe,
127
+ getSnapshot,
128
+ getSnapshot,
129
+ selector
130
+ )
131
+ }
132
+ }, [shape, selector])
133
+
134
+ return useShapeData()
126
135
  }