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/README.md +409 -155
- package/cjs/index.js +1 -1
- package/esm/index.js +1 -1
- package/esm/sqlite/use-sqlite.js +1 -1
- package/esm/use-value-loadable.js +1 -0
- package/package.json +1 -1
- package/src/__tests__/use-value-loadable.test.tsx +135 -0
- package/src/index.ts +1 -0
- package/src/sqlite/__tests__/use-sqlite.more.test.tsx +6 -6
- package/src/sqlite/__tests__/use-sqlite.test.tsx +510 -25
- package/src/sqlite/use-sqlite.ts +40 -5
- package/src/use-value-loadable.ts +39 -0
- package/types/index.d.ts +1 -0
- package/types/sqlite/use-sqlite.d.ts +8 -1
- package/types/use-value-loadable.d.ts +14 -0
- package/esm/sqlite/select-sql.js +0 -1
- package/src/sqlite/select-sql.ts +0 -65
- package/types/sqlite/select-sql.d.ts +0 -20
package/cjs/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
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
|
|
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};
|
package/esm/sqlite/use-sqlite.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useCallback as
|
|
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
|
@@ -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
|
@@ -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
|
-
//
|
|
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(
|
|
635
|
+
expect(renders).toBe(initialRenders)
|
|
636
636
|
})
|
|
637
637
|
})
|