muya 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var V=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var W=(e,t)=>{for(var o in t)V(e,o,{get:t[o],enumerable:!0})},J=(e,t,o,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of K(t))!Y.call(e,s)&&s!==o&&V(e,s,{get:()=>t[s],enumerable:!(a=N(t,s))||a.enumerable});return e};var Q=e=>J(V({},"__esModule",{value:!0}),e);var $={};W($,{EMPTY_SELECTOR:()=>D,create:()=>z,isAbortError:()=>I,isArray:()=>g,isEqualBase:()=>m,isError:()=>v,isFunction:()=>h,isMap:()=>x,isPromise:()=>f,isSet:()=>E,isSetValueFunction:()=>O,isState:()=>Z,isUndefined:()=>d,select:()=>k,shallow:()=>F,useValue:()=>C});module.exports=Q($);var T=class extends Error{static Error="AbortError"};function X(e,t){t&&t.abort();let o=new AbortController,{signal:a}=o;return{promise:new Promise((n,u)=>{a.addEventListener("abort",()=>{u(new T)}),e.then(n).catch(u)}),controller:o}}function y(e,t=m){if(!d(e.current)){if(!d(e.previous)&&t(e.current,e.previous))return!1;e.previous=e.current}return!0}function p(e,t){let{cache:o,emitter:{emit:a}}=e;if(!f(t))return t;o.abortController&&o.abortController.abort();let{promise:s,controller:n}=X(t,o.abortController);return o.abortController=n,s.then(u=>{o.current=u,a()}).catch(u=>{I(u)||(o.current=u,a())})}function f(e){return e instanceof Promise}function h(e){return typeof e=="function"}function x(e){return e instanceof Map}function E(e){return e instanceof Set}function g(e){return Array.isArray(e)}function m(e,t){return e===t?!0:!!Object.is(e,t)}function O(e){return typeof e=="function"}function I(e){return e instanceof T}function v(e){return e instanceof Error}function d(e){return e===void 0}function Z(e){return h(e)&&"get"in e&&"set"in e&&"isSet"in e&&e.isSet===!0}var D=e=>e;function U(){let e=new Map,t=new Set,o=performance.now(),a=!1;function s(){let u=performance.now(),r=u-o,{size:i}=t;if(r<.2&&i>0&&i<10){o=u,n();return}a||(a=!0,Promise.resolve().then(()=>{a=!1,o=performance.now(),n()}))}function n(){if(t.size===0)return;let u=new Set,r=new Map;for(let i of t){if(e.has(i.id)){u.add(i.id);let{onResolveItem:c}=e.get(i.id);c&&c(i.value),r.has(i.id)||r.set(i.id,[]),r.get(i.id).push(i.value)}t.delete(i)}if(t.size>0){s();return}for(let i of u){let c=r.get(i);e.get(i)?.onScheduleDone(c)}}return{add(u,r){return e.set(u,r),()=>{e.delete(u)}},schedule(u,r){t.add({value:r,id:u}),s()}}}function k(e,t,o){function a(){let c=!1,l=e.map(A=>{let w=A.get();return f(w)&&(c=!0),w});return c?new Promise((A,w)=>{Promise.all(l).then(G=>{if(G.some(_=>d(_)))return w(new T);let j=t(...G);A(j)})}):t(...l)}function s(){if(d(r.cache.current)){let c=a();r.cache.current=p(r,c)}return r.cache.current}function n(){if(d(r.cache.current)){let l=a();r.cache.current=p(r,l)}let{current:c}=r.cache;return f(c)?new Promise(l=>{c.then(S=>{if(d(S)){l(n());return}l(S)})}):r.cache.current}let u=[];for(let c of e){let l=c.emitter.subscribe(()=>{b.schedule(r.id,null)});u.push(l)}let r=P({destroy(){for(let c of u)c();i(),r.emitter.clear(),r.cache.current=void 0},get:n,getSnapshot:s}),i=b.add(r.id,{onScheduleDone(){let c=a();r.cache.current=p(r,c),y(r.cache,o)&&r.emitter.emit()}});return r}var R=require("react");var L=require("use-sync-external-store/shim/with-selector");function C(e,t=D){let{emitter:o}=e,a=(0,L.useSyncExternalStoreWithSelector)(e.emitter.subscribe,o.getSnapshot,o.getInitialSnapshot,t);if((0,R.useDebugValue)(a),f(a)||v(a))throw a;return a}function q(e,t){let o=new Set,a=[];return{clear:()=>{for(let s of a)s();o.clear()},subscribe:s=>(o.add(s),()=>{o.delete(s)}),emit:(...s)=>{for(let n of o)n(...s)},contains:s=>o.has(s),getSnapshot:e,getInitialSnapshot:t,getSize:()=>o.size,subscribeToOtherEmitter(s){let n=s.subscribe(()=>{this.emit()});a.push(n)}}}var M=0;function H(){return M++,M.toString(36)}function P(e){let{get:t,destroy:o,set:a,getSnapshot:s}=e,n=!!a,u={},r=function(i){return C(r,i)};return r.isSet=n,r.id=H(),r.emitter=q(s),r.destroy=o,r.listen=function(i){return this.emitter.subscribe(()=>{let c=t();f(c)||i(t())})},r.withName=function(i){return this.stateName=i,this},r.select=function(i,c=m){return k([r],i,c)},r.get=t,r.set=a,r.cache=u,r}var b=U();function z(e,t=m){function o(){try{if(d(n.cache.current)){let r=h(e)?e():e,i=p(n,r);return n.cache.current=i,n.cache.current}return n.cache.current}catch(r){n.cache.current=r}return n.cache.current}async function a(r,i){await r;let c=i(n.cache.current),l=p(n,c);n.cache.current=l}function s(r){let i=o(),c=O(r);if(c&&f(i)){a(i,r);return}n.cache.abortController&&n.cache.abortController.abort();let l=c?r(i):r,S=p(n,l);n.cache.current=S}let n=P({get:o,destroy(){o(),u(),n.emitter.clear(),n.cache.current=void 0},set(r){b.schedule(n.id,r)},getSnapshot:o}),u=b.add(n.id,{onScheduleDone(){n.cache.current=o(),y(n.cache,t)&&n.emitter.emit()},onResolveItem:s});return h(e)||o(),n}function F(e,t){if(e==t)return!0;if(typeof e!="object"||e==null||typeof t!="object"||t==null)return!1;if(x(e)&&x(t)){if(e.size!==t.size)return!1;for(let[s,n]of e)if(!Object.is(n,t.get(s)))return!1;return!0}if(E(e)&&E(t)){if(e.size!==t.size)return!1;for(let s of e)if(!t.has(s))return!1;return!0}if(g(e)&&g(t)){if(e.length!==t.length)return!1;for(let[s,n]of e.entries())if(!Object.is(n,t[s]))return!1;return!0}let o=Object.keys(e),a=Object.keys(t);if(o.length!==a.length)return!1;for(let s of o)if(!Object.prototype.hasOwnProperty.call(t,s)||!Object.is(e[s],t[s]))return!1;return!0}
1
+ "use strict";var O=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var J=Object.prototype.hasOwnProperty;var Q=(e,t)=>{for(var n in t)O(e,n,{get:t[n],enumerable:!0})},X=(e,t,n,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of W(t))!J.call(e,s)&&s!==n&&O(e,s,{get:()=>t[s],enumerable:!(a=Y(t,s))||a.enumerable});return e};var Z=e=>X(O({},"__esModule",{value:!0}),e);var ee={};Q(ee,{EMPTY_SELECTOR:()=>w,create:()=>F,isAbortError:()=>v,isArray:()=>k,isEqualBase:()=>S,isError:()=>b,isFunction:()=>h,isMap:()=>g,isPromise:()=>l,isSet:()=>P,isSetValueFunction:()=>D,isState:()=>B,isUndefined:()=>f,select:()=>V,shallow:()=>j,useValue:()=>C,useValueLoadable:()=>_});module.exports=Z(ee);var T=class extends Error{static Error="AbortError"};function $(e,t){t&&t.abort();let n=new AbortController,{signal:a}=n;return{promise:new Promise((o,c)=>{a.addEventListener("abort",()=>{c(new T)}),e.then(o).catch(c)}),controller:n}}function E(e,t=S){if(!f(e.current)){if(!f(e.previous)&&t(e.current,e.previous))return!1;e.previous=e.current}return!0}function p(e,t){let{cache:n,emitter:{emit:a}}=e;if(!l(t))return t;n.abortController&&n.abortController.abort();let{promise:s,controller:o}=$(t,n.abortController);return n.abortController=o,s.then(c=>{n.current=c,a()}).catch(c=>{v(c)||(n.current=c,a())})}function l(e){return e instanceof Promise}function h(e){return typeof e=="function"}function g(e){return e instanceof Map}function P(e){return e instanceof Set}function k(e){return Array.isArray(e)}function S(e,t){return e===t?!0:!!Object.is(e,t)}function D(e){return typeof e=="function"}function v(e){return e instanceof T}function b(e){return e instanceof Error}function f(e){return e===void 0}function B(e){return h(e)&&"get"in e&&"set"in e&&"isSet"in e&&e.isSet===!0}var w=e=>e;function G(){let e=new Map,t=new Set,n=performance.now(),a=!1;function s(){let c=performance.now(),r=c-n,{size:i}=t;if(r<.2&&i>0&&i<10){n=c,o();return}a||(a=!0,Promise.resolve().then(()=>{a=!1,n=performance.now(),o()}))}function o(){if(t.size===0)return;let c=new Set,r=new Map;for(let i of t){if(e.has(i.id)){c.add(i.id);let{onResolveItem:u}=e.get(i.id);u&&u(i.value),r.has(i.id)||r.set(i.id,[]),r.get(i.id).push(i.value)}t.delete(i)}if(t.size>0){s();return}for(let i of c){let u=r.get(i);e.get(i)?.onScheduleDone(u)}}return{add(c,r){return e.set(c,r),()=>{e.delete(c)}},schedule(c,r){t.add({value:r,id:c}),s()}}}function V(e,t,n){function a(){let u=!1,d=e.map(L=>{let x=L.get();return l(x)&&(u=!0),x});return u?new Promise((L,x)=>{Promise.all(d).then(R=>{if(R.some(K=>f(K)))return x(new T);let N=t(...R);L(N)})}):t(...d)}function s(){if(f(r.cache.current)){let u=a();r.cache.current=p(r,u)}return r.cache.current}function o(){if(f(r.cache.current)){let d=a();r.cache.current=p(r,d)}let{current:u}=r.cache;return l(u)?new Promise(d=>{u.then(m=>{if(f(m)){d(o());return}d(m)})}):r.cache.current}let c=[];for(let u of e){let d=u.emitter.subscribe(()=>{y.schedule(r.id,null)});c.push(d)}let r=A({destroy(){for(let u of c)u();i(),r.emitter.clear(),r.cache.current=void 0},get:o,getSnapshot:s}),i=y.add(r.id,{onScheduleDone(){let u=a();r.cache.current=p(r,u),E(r.cache,n)&&r.emitter.emit()}});return r}var U=require("react");var M=require("use-sync-external-store/shim/with-selector");function C(e,t=w){let{emitter:n}=e,a=(0,M.useSyncExternalStoreWithSelector)(e.emitter.subscribe,n.getSnapshot,n.getInitialSnapshot,t);if((0,U.useDebugValue)(a),l(a)||b(a))throw a;return a}function q(e,t){let n=new Set,a=[];return{clear:()=>{for(let s of a)s();n.clear()},subscribe:s=>(n.add(s),()=>{n.delete(s)}),emit:(...s)=>{for(let o of n)o(...s)},contains:s=>n.has(s),getSnapshot:e,getInitialSnapshot:t,getSize:()=>n.size,subscribeToOtherEmitter(s){let o=s.subscribe(()=>{this.emit()});a.push(o)}}}var H=0;function z(){return H++,H.toString(36)}function A(e){let{get:t,destroy:n,set:a,getSnapshot:s}=e,o=!!a,c={},r=function(i){return C(r,i)};return r.isSet=o,r.id=z(),r.emitter=q(s),r.destroy=n,r.listen=function(i){return this.emitter.subscribe(()=>{let u=t();l(u)||i(t())})},r.withName=function(i){return this.stateName=i,this},r.select=function(i,u=S){return V([r],i,u)},r.get=t,r.set=a,r.cache=c,r}var y=G();function F(e,t=S){function n(){try{if(f(o.cache.current)){let r=h(e)?e():e,i=p(o,r);return o.cache.current=i,o.cache.current}return o.cache.current}catch(r){o.cache.current=r}return o.cache.current}async function a(r,i){await r;let u=i(o.cache.current),d=p(o,u);o.cache.current=d}function s(r){let i=n(),u=D(r);if(u&&l(i)){a(i,r);return}o.cache.abortController&&o.cache.abortController.abort();let d=u?r(i):r,m=p(o,d);o.cache.current=m}let o=A({get:n,destroy(){n(),c(),o.emitter.clear(),o.cache.current=void 0},set(r){y.schedule(o.id,r)},getSnapshot:n}),c=y.add(o.id,{onScheduleDone(){o.cache.current=n(),E(o.cache,t)&&o.emitter.emit()},onResolveItem:s});return h(e)||n(),o}var I=require("react");function _(e,t=w){let{emitter:n}=e,a=(0,I.useSyncExternalStore)(n.subscribe,n.getSnapshot,n.getInitialSnapshot);return(0,I.useDebugValue)(a),l(a)?[void 0,!0,!1,void 0]:b(a)?[void 0,!1,!0,a]:[t(a),!1,!1,void 0]}function j(e,t){if(e==t)return!0;if(typeof e!="object"||e==null||typeof t!="object"||t==null)return!1;if(g(e)&&g(t)){if(e.size!==t.size)return!1;for(let[s,o]of e)if(!Object.is(o,t.get(s)))return!1;return!0}if(P(e)&&P(t)){if(e.size!==t.size)return!1;for(let s of e)if(!t.has(s))return!1;return!0}if(k(e)&&k(t)){if(e.length!==t.length)return!1;for(let[s,o]of e.entries())if(!Object.is(o,t[s]))return!1;return!0}let n=Object.keys(e),a=Object.keys(t);if(n.length!==a.length)return!1;for(let s of n)if(!Object.prototype.hasOwnProperty.call(t,s)||!Object.is(e[s],t[s]))return!1;return!0}
package/esm/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./utils/is";export*from"./types";import{create as f}from"./create";import{select as p}from"./select";import{useValue as l}from"./use-value";import{shallow as s}from"./utils/shallow";export{f as create,p as select,s as shallow,l as useValue};
1
+ export*from"./utils/is";export*from"./types";import{create as a}from"./create";import{select as p}from"./select";import{useValue as m}from"./use-value";import{useValueLoadable as s}from"./use-value-loadable";import{shallow as b}from"./utils/shallow";export{a as create,p as select,b as shallow,m as useValue,s as useValueLoadable};
@@ -1 +1 @@
1
- import{useCallback as y,useLayoutEffect as T,useReducer as C,useRef as w}from"react";import{DEFAULT_PAGE_SIZE as E}from"./table";import{shallow as K}from"../utils/shallow";const q=1e4;function F(d,k={},I=[]){const{select:f,pageSize:R=E}=k,e=w(null),[,A]=C(c=>c+1,0),t=w(new Map),g=w(null),L=y(()=>{const{select:c,...a}=k;g.current=d.search({select:(u,i)=>({doc:u,meta:i}),...a})},[d,...I]),m=y(()=>{e.current=[],t.current.clear(),L()},[L]),p=y(async c=>{e.current===null&&(e.current=[]),c===!0&&m();const{current:a}=g;if(!a)return!0;let u=!1;for(let i=0;i<R;i++){const o=await a.next();if(o.done){g.current=null,u=!0;break}t.current.has(o.value.meta.key)||(e.current.push(f?f(o.value.doc):o.value.doc),t.current.set(o.value.meta.key,e.current.length-1))}return e.current=[...e.current],u},[]),h=y(async()=>{const c=await p(!1);return A(),c},[p]);T(()=>{const c=d.subscribe(async a=>{const{mutations:u,removedAll:i}=a;if(i&&m(),!u)return;const o=e.current?.length??0;let D=o,S=!1;const b=new Set;for(const s of u){const{key:n,op:x,document:l}=s;switch(x){case"insert":{D+=1;break}case"delete":{if(e.current&&e.current.length>0&&t.current.has(n)){const r=t.current.get(n);if(r===void 0)break;b.add(r),S=!0}break}case"update":{if(t.current.has(n)){const r=t.current.get(n);if(r!==void 0&&e.current){const P=f?f(l):l,_=e.current[r];K(_,P)||(e.current[r]=P,e.current=[...e.current],S=!0)}}else{const r=await d.get(n,f);r&&(e.current=[...e.current??[],r],t.current.set(n,e.current.length-1),S=!0)}break}}}if(b.size>0&&e.current&&e.current.length>0){const s=new Map;e.current=e.current?.filter((x,l)=>!b.has(l));let n=0;for(const[x,l]of t.current)b.has(l)||(s.set(x,n),n++);t.current=s}const v=o!==D;if(v||S){if(v){await p(!0);let s=0;for(;(e.current?.length??0)<D&&s<q;)await p(!1),s++;s===q&&console.warn("Reached maximum iterations in fillNextPage loop. Possible duplicate or data issue.")}A()}});return()=>{c()}},[d]),T(()=>{m(),h()},I);const O=y(async()=>{m(),await h()},[h,m]);return[e.current,{nextPage:h,reset:O,keysIndex:t.current}]}export{F as useSqliteValue};
1
+ import{useCallback as h,useLayoutEffect as O,useReducer as U,useRef as L,useState as M}from"react";import{DEFAULT_PAGE_SIZE as N}from"./table";import{shallow as z}from"../utils/shallow";const E=1e4;function j(s,y){if(s.length!==y.length)return!1;for(const[o,l]of s.entries())if(!Object.is(l,y[o]))return!1;return!0}function Z(s,y={},o=[]){const{select:l,pageSize:R=N}=y,e=L(null),[,A]=U(n=>n+1,0),t=L(new Map),w=L(null),[q,k]=M(null),_=q===null||!j(q,o),v=h(()=>{const{select:n,...d}=y;w.current=s.search({select:(i,f)=>({doc:i,meta:f}),...d})},[s,...o]),p=h(()=>{e.current=[],t.current.clear(),v()},[v]),S=h(async n=>{e.current===null&&(e.current=[]),n===!0&&p();const{current:d}=w;if(!d)return!0;let i=!1;for(let f=0;f<R;f++){const u=await d.next();if(u.done){w.current=null,i=!0;break}t.current.has(u.value.meta.key)||(e.current.push(l?l(u.value.doc):u.value.doc),t.current.set(u.value.meta.key,e.current.length-1))}return e.current=[...e.current],i},[]),D=h(async()=>{const n=await S(!1);return A(),n},[S]);O(()=>{const n=s.subscribe(async d=>{const{mutations:i,removedAll:f}=d;if(f&&p(),!i)return;const u=e.current?.length??0;let I=u,b=!1;const x=new Set;for(const a of i){const{key:r,op:g,document:m}=a;switch(g){case"insert":{I+=1;break}case"delete":{if(e.current&&e.current.length>0&&t.current.has(r)){const c=t.current.get(r);if(c===void 0)break;x.add(c),b=!0}break}case"update":{if(t.current.has(r)){const c=t.current.get(r);if(c!==void 0&&e.current){const T=l?l(m):m,K=e.current[c];z(K,T)||(e.current[c]=T,e.current=[...e.current],b=!0)}}else{const c=await s.get(r,l);c&&(e.current=[...e.current??[],c],t.current.set(r,e.current.length-1),b=!0)}break}}}if(x.size>0&&e.current&&e.current.length>0){const a=new Map;e.current=e.current?.filter((g,m)=>!x.has(m));let r=0;for(const[g,m]of t.current)x.has(m)||(a.set(g,r),r++);t.current=a}const P=u!==I;if(P||b){if(P){await S(!0);let a=0;for(;(e.current?.length??0)<I&&a<E;)await S(!1),a++;a===E&&console.warn("Reached maximum iterations in fillNextPage loop. Possible duplicate or data issue.")}A()}});return()=>{n()}},[s]),O(()=>{const n=o;p(),D().then(()=>{k(n)})},o);const C=h(async()=>{k(null),p(),await D(),k(o)},[D,p,o]);return[e.current,{nextPage:D,reset:C,keysIndex:t.current,isStale:_}]}export{Z as useSqliteValue};
@@ -0,0 +1 @@
1
+ import{useDebugValue as n,useSyncExternalStore as s}from"react";import{EMPTY_SELECTOR as u}from"./types";import{isError as r,isPromise as l}from"./utils/is";function b(t,d=u){const{emitter:a}=t,e=s(a.subscribe,a.getSnapshot,a.getInitialSnapshot);return n(e),l(e)?[void 0,!0,!1,void 0]:r(e)?[void 0,!1,!0,e]:[d(e),!1,!1,void 0]}export{b as useValueLoadable};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muya",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "author": "samuel.gjabel@gmail.com",
5
5
  "repository": "https://github.com/samuelgjabel/muya",
6
6
  "main": "cjs/index.js",
@@ -0,0 +1,135 @@
1
+ import { renderHook, act } from '@testing-library/react-hooks'
2
+ import { waitFor } from '@testing-library/react'
3
+ import { create } from '../create'
4
+ import { useValueLoadable } from '../use-value-loadable'
5
+ import { longPromise } from './test-utils'
6
+
7
+ describe('useValueLoadable', () => {
8
+ it('should return value immediately for sync state', () => {
9
+ const state = create(42)
10
+ const { result } = renderHook(() => useValueLoadable(state))
11
+
12
+ const [value, isLoading, isError, error] = result.current
13
+ expect(value).toBe(42)
14
+ expect(isLoading).toBe(false)
15
+ expect(isError).toBe(false)
16
+ expect(error).toBeUndefined()
17
+ })
18
+
19
+ it('should return loading state for async state', async () => {
20
+ const state = create(longPromise(50))
21
+ const { result } = renderHook(() => useValueLoadable(state))
22
+
23
+ // Initially loading
24
+ expect(result.current[0]).toBeUndefined()
25
+ expect(result.current[1]).toBe(true)
26
+ expect(result.current[2]).toBe(false)
27
+ expect(result.current[3]).toBeUndefined()
28
+
29
+ // After resolution
30
+ await waitFor(() => {
31
+ expect(result.current[0]).toBe(0)
32
+ expect(result.current[1]).toBe(false)
33
+ expect(result.current[2]).toBe(false)
34
+ expect(result.current[3]).toBeUndefined()
35
+ })
36
+ })
37
+
38
+ it('should return error state when state throws', () => {
39
+ const testError = new Error('Test error')
40
+ const state = create(() => {
41
+ throw testError
42
+ })
43
+ const { result } = renderHook(() => useValueLoadable(state))
44
+
45
+ const [value, isLoading, isError, error] = result.current
46
+ expect(value).toBeUndefined()
47
+ expect(isLoading).toBe(false)
48
+ expect(isError).toBe(true)
49
+ expect(error).toBe(testError)
50
+ })
51
+
52
+ it('should return error state when async state rejects', async () => {
53
+ const testError = new Error('Async error')
54
+ const state = create(Promise.reject(testError))
55
+ const { result } = renderHook(() => useValueLoadable(state))
56
+
57
+ await waitFor(() => {
58
+ expect(result.current[0]).toBeUndefined()
59
+ expect(result.current[1]).toBe(false)
60
+ expect(result.current[2]).toBe(true)
61
+ expect(result.current[3]).toBe(testError)
62
+ })
63
+ })
64
+
65
+ it('should update when sync state changes', async () => {
66
+ const state = create(1)
67
+ const { result } = renderHook(() => useValueLoadable(state))
68
+
69
+ expect(result.current[0]).toBe(1)
70
+
71
+ act(() => {
72
+ state.set(2)
73
+ })
74
+
75
+ await waitFor(() => {
76
+ expect(result.current[0]).toBe(2)
77
+ expect(result.current[1]).toBe(false)
78
+ expect(result.current[2]).toBe(false)
79
+ })
80
+ })
81
+
82
+ it('should work with selector', () => {
83
+ const state = create({ count: 10, name: 'test' })
84
+ const { result } = renderHook(() => useValueLoadable(state, (s) => s.count))
85
+
86
+ const [value, isLoading, isError] = result.current
87
+ expect(value).toBe(10)
88
+ expect(isLoading).toBe(false)
89
+ expect(isError).toBe(false)
90
+ })
91
+
92
+ it('should work with selector on async state', async () => {
93
+ const state = create(Promise.resolve({ count: 5, name: 'async' }))
94
+ const { result } = renderHook(() => useValueLoadable(state, (s) => s.count))
95
+
96
+ // Initially loading
97
+ expect(result.current[1]).toBe(true)
98
+
99
+ await waitFor(() => {
100
+ expect(result.current[0]).toBe(5)
101
+ expect(result.current[1]).toBe(false)
102
+ })
103
+ })
104
+
105
+ it('should not throw to suspense boundary', async () => {
106
+ const state = create(longPromise(50))
107
+ const renderCount = jest.fn()
108
+
109
+ const { result } = renderHook(() => {
110
+ renderCount()
111
+ return useValueLoadable(state)
112
+ })
113
+
114
+ // Should render without throwing
115
+ expect(renderCount).toHaveBeenCalled()
116
+ expect(result.current[1]).toBe(true)
117
+
118
+ await waitFor(() => {
119
+ expect(result.current[1]).toBe(false)
120
+ })
121
+ })
122
+
123
+ it('should provide type narrowing when isLoading is false', () => {
124
+ const state = create(42)
125
+ const { result } = renderHook(() => useValueLoadable(state))
126
+
127
+ const [value, isLoading, isError] = result.current
128
+
129
+ if (!isLoading && !isError) {
130
+ // TypeScript should know value is number here
131
+ const number_: number = value
132
+ expect(number_).toBe(42)
133
+ }
134
+ })
135
+ })
package/src/index.ts CHANGED
@@ -3,4 +3,5 @@ export * from './types'
3
3
  export { create } from './create'
4
4
  export { select } from './select'
5
5
  export { useValue } from './use-value'
6
+ export { useValueLoadable, type LoadableResult } from './use-value-loadable'
6
7
  export { shallow } from './utils/shallow'
@@ -591,9 +591,9 @@ describe('use-sqlite edge cases', () => {
591
591
  return useSqliteValue(sql, { pageSize: 50 }, [])
592
592
  })
593
593
  await waitFor(() => {
594
- expect(renderCount).toBe(2) // initial + after load
595
594
  expect(result.current[0]?.length).toBe(50)
596
595
  })
596
+ const initialRenders = renderCount
597
597
 
598
598
  // Delete item outside current page
599
599
  await act(async () => {
@@ -601,9 +601,10 @@ describe('use-sqlite edge cases', () => {
601
601
  })
602
602
 
603
603
  await waitFor(() => {
604
- expect(renderCount).toBe(2) // no re-render
605
604
  expect(result.current[0]?.length).toBe(50) // unchanged page size
606
605
  })
606
+ // No re-render for non-visible item deletion
607
+ expect(renderCount).toBe(initialRenders)
607
608
  })
608
609
  it('should not rerender when using select if the selected value does not change', async () => {
609
610
  const sql = createSqliteState<Person>({ backend, tableName: 'SelectNoReRender', key: 'id' })
@@ -618,8 +619,8 @@ describe('use-sqlite edge cases', () => {
618
619
 
619
620
  await waitFor(() => {
620
621
  expect(result.current[0]).toEqual(['Alice'])
621
- expect(renders).toBe(2) // initial + after first load
622
622
  })
623
+ const initialRenders = renders
623
624
 
624
625
  // Update age (not part of select projection)
625
626
  await act(async () => {
@@ -629,9 +630,8 @@ describe('use-sqlite edge cases', () => {
629
630
  // Wait a bit to let subscription flush
630
631
  await new Promise((r) => setTimeout(r, 20))
631
632
 
632
- // Buggy: renders increments again, even though "Alice" didn't change
633
- // ✅ Expected: still 2 renders
633
+ // No re-render since selected value "Alice" didn't change
634
634
  expect(result.current[0]).toEqual(['Alice'])
635
- expect(renders).toBe(2)
635
+ expect(renders).toBe(initialRenders)
636
636
  })
637
637
  })