muya 2.1.0 → 2.1.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 +1 -1
- package/esm/create-state.js +1 -1
- package/esm/create.js +1 -1
- package/esm/debug/development-tools.js +1 -1
- package/esm/scheduler.js +1 -1
- package/esm/select.js +1 -1
- package/esm/sqlite/__tests__/create-sqlite-state.test.js +1 -0
- package/esm/sqlite/__tests__/map-deque.test.js +1 -0
- package/esm/sqlite/__tests__/table.test.js +1 -0
- package/esm/sqlite/__tests__/use-sqlite-state.test.js +1 -0
- package/esm/sqlite/create-sqlite-state.js +1 -0
- package/esm/sqlite/table/backend.js +1 -0
- package/esm/sqlite/table/bun-backend.js +1 -0
- package/esm/sqlite/table/map-deque.js +1 -0
- package/esm/sqlite/table/table.js +10 -0
- package/esm/sqlite/table/table.types.js +0 -0
- package/esm/sqlite/table/where.js +1 -0
- package/esm/sqlite/use-sqlite-value.js +1 -0
- package/esm/use-value.js +1 -1
- package/esm/utils/common.js +1 -1
- package/package.json +4 -12
- package/src/__tests__/scheduler.test.tsx +2 -2
- package/src/__tests__/test-utils.ts +1 -0
- package/src/create-state.ts +3 -2
- package/src/create.ts +22 -24
- package/src/debug/development-tools.ts +1 -1
- package/src/scheduler.ts +15 -7
- package/src/select.ts +15 -17
- package/src/sqlite/__tests__/create-sqlite-state.test.ts +81 -0
- package/src/sqlite/__tests__/map-deque.test.ts +61 -0
- package/src/sqlite/__tests__/table.test.ts +142 -0
- package/src/sqlite/__tests__/use-sqlite-state.test.ts +213 -0
- package/src/sqlite/create-sqlite-state.ts +256 -0
- package/src/sqlite/table/backend.ts +21 -0
- package/src/sqlite/table/bun-backend.ts +38 -0
- package/src/sqlite/table/map-deque.ts +29 -0
- package/src/sqlite/table/table.ts +200 -0
- package/src/sqlite/table/table.types.ts +55 -0
- package/src/sqlite/table/where.ts +267 -0
- package/src/sqlite/use-sqlite-value.ts +76 -0
- package/src/types.ts +1 -0
- package/src/use-value.ts +6 -4
- package/src/utils/common.ts +6 -2
- package/types/create.d.ts +3 -3
- package/types/scheduler.d.ts +12 -3
- package/types/sqlite/create-sqlite-state.d.ts +22 -0
- package/types/sqlite/table/backend.d.ts +20 -0
- package/types/sqlite/table/bun-backend.d.ts +2 -0
- package/types/sqlite/table/map-deque.d.ts +5 -0
- package/types/sqlite/table/table.d.ts +3 -0
- package/types/sqlite/table/table.types.d.ts +52 -0
- package/types/sqlite/table/where.d.ts +32 -0
- package/types/sqlite/use-sqlite-value.d.ts +21 -0
- package/types/types.d.ts +1 -0
- package/types/utils/common.d.ts +2 -2
package/cjs/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var V=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var K=(e,r)=>{for(var o in r)V(e,o,{get:r[o],enumerable:!0})},Y=(e,r,o,i)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of _(r))!N.call(e,s)&&s!==o&&V(e,s,{get:()=>r[s],enumerable:!(i=j(r,s))||i.enumerable});return e};var W=e=>Y(V({},"__esModule",{value:!0}),e);var $={};K($,{EMPTY_SELECTOR:()=>v,create:()=>H,isAbortError:()=>I,isArray:()=>P,isEqualBase:()=>p,isError:()=>D,isFunction:()=>h,isMap:()=>E,isPromise:()=>f,isSet:()=>x,isSetValueFunction:()=>O,isState:()=>Q,isUndefined:()=>d,select:()=>g,shallow:()=>z,useValue:()=>C});module.exports=W($);var m=class extends Error{static Error="AbortError"};function J(e,r){r&&r.abort();let o=new AbortController,{signal:i}=o;return{promise:new Promise((n,a)=>{i.addEventListener("abort",()=>{a(new m)}),e.then(n).catch(a)}),controller:o}}function y(e,r=p){if(!d(e.current)){if(!d(e.previous)&&r(e.current,e.previous))return!1;e.previous=e.current}return!0}function T(e,r){let{cache:o,emitter:{emit:i}}=e;if(!f(r))return r;o.abortController&&o.abortController.abort();let{promise:s,controller:n}=J(r,o.abortController);return o.abortController=n,s.then(a=>{o.current=a,i()}).catch(a=>{I(a)||(o.current=a,i())})}function f(e){return e instanceof Promise}function h(e){return typeof e=="function"}function E(e){return e instanceof Map}function x(e){return e instanceof Set}function P(e){return Array.isArray(e)}function p(e,r){return e===r?!0:!!Object.is(e,r)}function O(e){return typeof e=="function"}function I(e){return e instanceof m}function D(e){return e instanceof Error}function d(e){return e===void 0}function Q(e){return h(e)&&"get"in e&&"set"in e&&"isSet"in e&&e.isSet===!0}var v=e=>e;function U(){let e=new Map,r=new Set,o=performance.now(),i=!1;function s(){let a=performance.now(),t=a-o,{size:c}=r;if(t<.2&&c>0&&c<10){o=a,n();return}i||(i=!0,Promise.resolve().then(()=>{i=!1,o=performance.now(),n()}))}function n(){if(r.size===0)return;let a=new Set;for(let t of r){if(e.has(t.id)){a.add(t.id);let{onResolveItem:c}=e.get(t.id);c&&c(t.value)}r.delete(t)}if(r.size>0){s();return}for(let t of a)e.get(t)?.onScheduleDone()}return{add(a,t){return e.set(a,t),()=>{e.delete(a)}},schedule(a,t){r.add({value:t,id:a}),s()}}}function g(e,r,o){function i(){let u=!1,l=e.map(A=>{let w=A.get();return f(w)&&(u=!0),w});return u?new Promise((A,w)=>{Promise.all(l).then(G=>{if(G.some(M=>d(M)))return w(new m);let F=r(...G);A(F)})}):r(...l)}function s(){if(d(t.cache.current)){let u=i();t.cache.current=T(t,u)}return t.cache.current}function n(){if(d(t.cache.current)){let l=i();t.cache.current=T(t,l)}let{current:u}=t.cache;return f(u)?new Promise(l=>{u.then(S=>{if(d(S)){l(n());return}l(S)})}):t.cache.current}let a=[];for(let u of e){let l=u.emitter.subscribe(()=>{b.schedule(t.id,null)});a.push(l)}let t=k({destroy(){for(let u of a)u();c(),t.emitter.clear(),t.cache.current=void 0},get:n,getSnapshot:s}),c=b.add(t.id,{onScheduleDone(){let u=i();t.cache.current=T(t,u),y(t.cache,o)&&t.emitter.emit()}});return t}var R=require("react");var L=require("use-sync-external-store/shim/with-selector");function C(e,r=v){let{emitter:o}=e,i=(0,L.useSyncExternalStoreWithSelector)(e.emitter.subscribe,o.getSnapshot,o.getInitialSnapshot,r);if((0,R.useDebugValue)(i),f(i)||D(i))throw i;return i}function q(e,r){let o=new Set,i=[];return{clear:()=>{for(let s of i)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:r,getSize:()=>o.size,subscribeToOtherEmitter(s){let n=s.subscribe(()=>{this.emit()});i.push(n)}}}var X=0;function Z(){return X++}function k(e){let{get:r,destroy:o,set:i,getSnapshot:s}=e,n=!!i,a={},t=function(c){return C(t,c)};return t.isSet=n,t.id=Z(),t.emitter=q(s),t.destroy=o,t.listen=function(c){return this.emitter.subscribe(()=>{let u=r();f(u)||c(r())})},t.withName=function(c){return this.stateName=c,this},t.select=function(c,u=p){return g([t],c,u)},t.get=r,t.set=i,t.cache=a,t}var b=U();function H(e,r=p){function o(){try{if(d(n.cache.current)){let t=h(e)?e():e,c=T(n,t);return n.cache.current=c,n.cache.current}return n.cache.current}catch(t){n.cache.current=t}return n.cache.current}async function i(t,c){await t;let u=c(n.cache.current),l=T(n,u);n.cache.current=l}function s(t){let c=o(),u=O(t);if(u&&f(c)){i(c,t);return}n.cache.abortController&&n.cache.abortController.abort();let l=u?t(c):t,S=T(n,l);n.cache.current=S}let n=k({get:o,destroy(){o(),a(),n.emitter.clear(),n.cache.current=void 0},set(t){b.schedule(n.id,t)},getSnapshot:o}),a=b.add(n.id,{onScheduleDone(){n.cache.current=o(),y(n.cache,r)&&n.emitter.emit()},onResolveItem:s});return h(e)||o(),n}function z(e,r){if(e==r)return!0;if(typeof e!="object"||e==null||typeof r!="object"||r==null)return!1;if(E(e)&&E(r)){if(e.size!==r.size)return!1;for(let[s,n]of e)if(!Object.is(n,r.get(s)))return!1;return!0}if(x(e)&&x(r)){if(e.size!==r.size)return!1;for(let s of e)if(!r.has(s))return!1;return!0}if(P(e)&&P(r)){if(e.length!==r.length)return!1;for(let[s,n]of e.entries())if(!Object.is(n,r[s]))return!1;return!0}let o=Object.keys(e),i=Object.keys(r);if(o.length!==i.length)return!1;for(let s of o)if(!Object.prototype.hasOwnProperty.call(r,s)||!Object.is(e[s],r[s]))return!1;return!0}
|
package/esm/create-state.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{select as l}from"./select";import{useValue as S}from"./use-value";import{createEmitter as
|
|
1
|
+
import{select as l}from"./select";import{useValue as S}from"./use-value";import{createEmitter as d}from"./utils/create-emitter";import{isEqualBase as m,isPromise as T}from"./utils/is";let f=0;function p(){return f++}function G(n){const{get:a,destroy:o,set:s,getSnapshot:i}=n,u=!!s,c={},t=function(e){return S(t,e)};return t.isSet=u,t.id=p(),t.emitter=d(i),t.destroy=o,t.listen=function(e){return this.emitter.subscribe(()=>{const r=a();T(r)||e(a())})},t.withName=function(e){return this.stateName=e,this},t.select=function(e,r=m){return l([t],e,r)},t.get=a,t.set=s,t.cache=c,t}export{G as createState};
|
package/esm/create.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{canUpdate as
|
|
1
|
+
import{canUpdate as S,handleAsyncUpdate as u}from"./utils/common";import{isEqualBase as T,isFunction as s,isPromise as p,isSetValueFunction as V,isUndefined as b}from"./utils/is";import{createScheduler as y}from"./scheduler";import{subscribeToDevelopmentTools as E}from"./debug/development-tools";import{createState as w}from"./create-state";const i=y();function g(a,l=T){function c(){try{if(b(e.cache.current)){const t=s(a)?a():a,r=u(e,t);return e.cache.current=r,e.cache.current}return e.cache.current}catch(t){e.cache.current=t}return e.cache.current}async function d(t,r){await t;const n=r(e.cache.current),o=u(e,n);e.cache.current=o}function h(t){const r=c(),n=V(t);if(n&&p(r)){d(r,t);return}e.cache.abortController&&e.cache.abortController.abort();const o=n?t(r):t,f=u(e,o);e.cache.current=f}const e=w({get:c,destroy(){c(),m(),e.emitter.clear(),e.cache.current=void 0},set(t){i.schedule(e.id,t)},getSnapshot:c}),m=i.add(e.id,{onScheduleDone(){e.cache.current=c(),S(e.cache,l)&&e.emitter.emit()},onResolveItem:h});return s(a)||c(),E(e),e}export{i as STATE_SCHEDULER,g as create};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isPromise as r,isState as a}from"../utils/is";const o=
|
|
1
|
+
import{isPromise as r,isState as a}from"../utils/is";const o=globalThis?.__REDUX_DEVTOOLS_EXTENSION__?.connect({name:"CustomState",trace:!0});o&&o.init({message:"Initial state"});function p(e){if(!o)return;const{message:t,type:n,value:s,name:i}=e;r(s)||o.send(i,{value:s,type:n,message:t},n)}function m(e,t){return n=>{p({name:e,type:t,value:n,message:"update"})}}function l(e){}export{l as subscribeToDevelopmentTools};
|
package/esm/scheduler.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const u=.2,i=10,f=0;function a(){const t=new Map,o=new Set;let d=performance.now(),c=!1;function s(){const n=performance.now(),e=n-d,{size:r}=o;if(e<.2&&r>0&&r<10){d=n,l();return}c||(c=!0,Promise.resolve().then(()=>{c=!1,d=performance.now(),l()}))}function l(){if(o.size===0)return;const n=new Set;for(const e of o){if(t.has(e.id)){n.add(e.id);const{onResolveItem:r}=t.get(e.id);r&&r(e.value)}o.delete(e)}if(o.size>0){s();return}for(const e of n)t.get(e)?.onScheduleDone()}return{add(n,e){return t.set(n,e),()=>{t.delete(n)}},schedule(n,e){o.add({value:e,id:n}),s()}}}export{f as RESCHEDULE_COUNT,u as THRESHOLD,i as THRESHOLD_ITEMS,a as createScheduler};
|
package/esm/select.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{STATE_SCHEDULER as p}from"./create";import{createState as k}from"./create-state";import{subscribeToDevelopmentTools as E}from"./debug/development-tools";import{AbortError as b,canUpdate as D,handleAsyncUpdate as s}from"./utils/common";import{isPromise as T,isUndefined as c}from"./utils/is";function U(i,d,h){function o(){let t=!1;const n=i.map(u=>{const r=u.get();return T(r)&&(t=!0),r});return t?new Promise((u,r)=>{Promise.all(n).then(m=>{if(m.some(A=>c(A)))return r(new b);const w=d(...m);u(w)})}):d(...n)}function S(){if(c(e.cache.current)){const t=o();e.cache.current=s(e,t)}return e.cache.current}function f(){if(c(e.cache.current)){const n=o();e.cache.current=s(e,n)}const{current:t}=e.cache;return T(t)?new Promise(n=>{t.then(a=>{if(c(a)){n(f());return}n(a)})}):e.cache.current}const l=[];for(const t of i){const n=t.emitter.subscribe(()=>{p.schedule(e.id,null)});l.push(n)}const e=k({destroy(){for(const t of l)t();y(),e.emitter.clear(),e.cache.current=void 0},get:f,getSnapshot:S}),y=p.add(e.id,{onScheduleDone(){const t=o();e.cache.current=s(e,t),D(e.cache,h)&&e.emitter.emit()}});return E(e),e}export{U as select};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createSqliteState as o}from"../create-sqlite-state";import{bunMemoryBackend as s}from"../table/bun-backend";const n=s();describe("create-sqlite-state",()=>{it("should batchSet and update multiple documents",async()=>{const e=await o({backend:n,tableName:"State2",key:"id"});await e.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25}]);const t=[];for await(const i of e.search())t.push(i);expect(t).toHaveLength(2),await e.batchSet([{id:"1",name:"Alice2",age:31},{id:"2",name:"Bob2",age:26}]);const a=[];for await(const i of e.search())a.push(i);expect(a).toEqual([{id:"1",name:"Alice2",age:31},{id:"2",name:"Bob2",age:26}])}),it("should deleteBy condition",async()=>{const e=await o({backend:n,tableName:"State3",key:"id"});await e.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25},{id:"3",name:"Carol",age:40}]);const t=await e.deleteBy({age:{gt:30}});expect(t.length).toBe(1);const a=[];for await(const i of e.search())a.push(i);expect(a.map(i=>i.id)).toEqual(["1","2"])}),it("should get by key and with selector",async()=>{const e=await o({backend:n,tableName:"State4",key:"id"});await e.set({id:"1",name:"Alice",age:30});const t=await e.get("1");expect(t).toEqual({id:"1",name:"Alice",age:30});const a=await e.get("1",c=>c.name);expect(a).toBe("Alice");const i=await e.get("999");expect(i).toBeUndefined()}),it("should count documents with and without where",async()=>{const e=await o({backend:n,tableName:"State5",key:"id"});await e.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25},{id:"3",name:"Carol",age:40}]),expect(await e.count()).toBe(3),expect(await e.count({where:{age:{gt:30}}})).toBe(1)}),it("should support search with options",async()=>{const e=await o({backend:n,tableName:"State6",key:"id"});await e.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25},{id:"3",name:"Carol",age:40}]);const t=[];for await(const a of e.search({where:{age:{lt:35}}}))t.push(a);expect(t.map(a=>a.id)).toEqual(["1","2"])})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{MapDeque as t}from"../table/map-deque";describe("MapDeque",()=>{it("should throw if maxSize <= 0",()=>{expect(()=>new t(0)).toThrow(RangeError),expect(()=>new t(-1)).toThrow(RangeError)}),it("should add items up to maxSize",()=>{const e=new t(2);e.set("a",1),e.set("b",2),expect(e.size).toBe(2),expect(e.get("a")).toBe(1),expect(e.get("b")).toBe(2)}),it("should evict the oldest item when maxSize is exceeded",()=>{const e=new t(2);e.set("a",1),e.set("b",2),e.set("c",3),expect(e.size).toBe(2),expect(e.has("a")).toBe(!1),expect(e.get("b")).toBe(2),expect(e.get("c")).toBe(3)}),it("should update value if key already exists and not evict",()=>{const e=new t(2);e.set("a",1),e.set("b",2),e.set("a",42),expect(e.size).toBe(2),expect(e.get("a")).toBe(42),expect(e.get("b")).toBe(2)}),it("should work with initial entries",()=>{const e=[["x",10],["y",20]],s=new t(3,e);expect(s.size).toBe(2),expect(s.get("x")).toBe(10),expect(s.get("y")).toBe(20)}),it("should evict in insertion order, not key order",()=>{const e=new t(2);e.set("b",1),e.set("a",2),e.set("c",3),expect(e.size).toBe(2),expect(e.has("b")).toBe(!1),expect(e.has("a")).toBe(!0),expect(e.has("c")).toBe(!0)})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bunMemoryBackend as c}from"../table/bun-backend";import{createTable as r}from"../table/table";describe("table",()=>{let n=c(),e;beforeEach(async()=>{n=c(),e=await r({backend:n,tableName:"TestTable",key:"name"})}),it("should set and get items",async()=>{const a=await e.set({name:"Alice",age:30,city:"Paris"});expect(a.key).toBe("Alice"),expect(a.op).toBe("insert");const i=await e.get("Alice");expect(i).toEqual({name:"Alice",age:30,city:"Paris"});const t=await e.set({name:"Alice",age:31,city:"Paris"});expect(t.key).toBe("Alice"),expect(t.op).toBe("update");const s=await e.get("Alice");expect(s).toEqual({name:"Alice",age:31,city:"Paris"})}),it("should count items and count with where",async()=>{await e.set({name:"Alice",age:30,city:"Paris"}),await e.set({name:"Bob",age:25,city:"London"}),expect(await e.count()).toBe(2),expect(await e.count({where:{city:"Paris"}})).toBe(1)}),it("should search with ordering, limit and offset",async()=>{const a=[{name:"Alice",age:30,city:"Paris"},{name:"Bob",age:25,city:"London"},{name:"Carol",age:35,city:"Berlin"}];for(const o of a)await e.set(o);const i=[];for await(const o of e.search({sorBy:"age",order:"asc"}))i.push(o);expect(i.map(o=>o.name)).toEqual(["Bob","Alice","Carol"]);const t=[];for await(const o of e.search({sorBy:"age",order:"asc",limit:2}))t.push(o);expect(t.map(o=>o.name)).toEqual(["Bob","Alice"]);const s=[];for await(const o of e.search({sorBy:"age",order:"asc",offset:1,limit:2}))s.push(o);expect(s.map(o=>o.name)).toEqual(["Alice","Carol"])}),it("should deleteBy where clause",async()=>{await e.set({name:"Dave",age:40,city:"NY"}),await e.set({name:"Eve",age:45,city:"NY"}),await e.set({name:"Frank",age:50,city:"LA"}),expect(await e.count()).toBe(3),await e.deleteBy({city:"NY"}),expect(await e.count()).toBe(1),expect(await e.get("Frank")).toEqual({name:"Frank",age:50,city:"LA"}),expect(await e.get("Dave")).toBeUndefined()}),it("should use selector in get and search",async()=>{await e.set({name:"Gary",age:60,city:"SF"});const a=await e.get("Gary",({age:t})=>t);expect(a).toBe(60);const i=[];for await(const t of e.search({select:({city:s})=>s}))i.push(t);expect(i).toEqual(["SF"])}),it("should delete items by key",async()=>{await e.set({name:"Helen",age:28,city:"Rome"}),expect(await e.get("Helen")).toBeDefined(),await e.delete("Helen"),expect(await e.get("Helen")).toBeUndefined()}),it("should test search with 1000 items",async()=>{const a=[];for(let t=0;t<1e3;t++)a.push({name:`Person${t}`,age:Math.floor(Math.random()*100),city:"City"+t%10});for(const t of a)await e.set(t);const i=[];for await(const t of e.search({sorBy:"age",order:"asc",limit:100}))i.push(t);expect(i.length).toBe(100)}),it("should handle operations on an empty table",async()=>{expect(await e.count()).toBe(0),expect(await e.get("NonExistent")).toBeUndefined();const a=[];for await(const i of e.search({sorBy:"age",order:"asc"}))a.push(i);expect(a.length).toBe(0)}),it("should handle duplicate keys gracefully",async()=>{await e.set({name:"Alice",age:30,city:"Paris"}),await e.set({name:"Alice",age:35,city:"Berlin"});const a=await e.get("Alice");expect(a).toEqual({name:"Alice",age:35,city:"Berlin"})}),it("should handle edge cases in selectors",async()=>{await e.set({name:"Charlie",age:40,city:"NY"});const a=await e.get("Charlie",()=>null);expect(a).toBeNull();const i=await e.get("Charlie",()=>{});expect(i).toBeUndefined()})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{act as o,renderHook as c}from"@testing-library/react-hooks";import{createSqliteState as s}from"../create-sqlite-state";import{useSqliteValue as l}from"../use-sqlite-value";import{waitFor as i}from"@testing-library/react";import{bunMemoryBackend as m}from"../table/bun-backend";import{useState as p}from"react";import{DEFAULT_STEP_SIZE as d}from"../table/table";const u=m();describe("use-sqlite-state",()=>{it("should get basic value states",async()=>{const r=await s({backend:u,tableName:"State1",key:"id"});let t=0;const{result:n}=c(()=>(t++,l(r,{},[])));expect(t).toBe(1),o(()=>{r.set({id:"1",name:"Alice",age:30})}),await i(()=>{expect(n.current[0]).toEqual([{id:"1",name:"Alice",age:30}]),expect(t).toBe(3)}),o(()=>{r.set({id:"1",name:"Alice2",age:30})}),await i(()=>{expect(n.current[0]).toEqual([{id:"1",name:"Alice2",age:30}]),expect(t).toBe(4)}),o(()=>{r.delete("1")}),await i(()=>{expect(n.current[0]).toEqual([]),expect(t).toBe(5)}),o(()=>{r.set({id:"1",name:"Alice",age:30}),r.set({id:"2",name:"Bob",age:25})}),await i(()=>{expect(n.current[0].length).toBe(2),expect(t).toBe(6)})}),it("should use where clause changed via state",async()=>{const r=await s({backend:u,tableName:"State2",key:"id"});await r.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25},{id:"3",name:"Carol",age:40}]);let t=0;const{result:n}=c(()=>{t++;const[a,e]=p(20);return[l(r,{where:{age:{gt:a}},sorBy:"age"},[a]),e]});await i(()=>{expect(n.current[0][0].map(a=>a.name)).toEqual(["Bob","Alice","Carol"]),expect(t).toBe(2)}),o(()=>{n.current[1](29)}),await i(()=>{expect(n.current[0][0].map(a=>a.name)).toEqual(["Alice","Carol"]),expect(t).toBe(4)})}),it("should support like in where clause and update results",async()=>{const r=await s({backend:u,tableName:"State3",key:"id"});await r.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Alicia",age:25},{id:"3",name:"Bob",age:40}]);let t=0;const{result:n,rerender:a}=c(({like:e})=>(t++,l(r,{where:{name:{like:e}}},[e])),{initialProps:{like:"%Ali%"}});await i(()=>{expect(n.current[0].map(e=>e.name)).toEqual(["Alice","Alicia"])}),o(()=>{a({like:"%Bob%"})}),await i(()=>{expect(n.current[0].map(e=>e.name)).toEqual(["Bob"])}),expect(t).toBeGreaterThanOrEqual(2)}),it("should update results when changing order and limit options",async()=>{const r=await s({backend:u,tableName:"State4",key:"id"});await r.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25},{id:"3",name:"Carol",age:40}]);const{result:t,rerender:n}=c(({order:a,limit:e})=>l(r,{sorBy:"age",order:a,limit:e},[a,e]),{initialProps:{order:"asc",limit:2}});await i(()=>{expect(t.current[0].map(a=>a.name)).toEqual(["Bob","Alice"])}),o(()=>{n({order:"desc",limit:2})}),await i(()=>{expect(t.current[0].map(a=>a.name)).toEqual(["Carol","Alice"])}),o(()=>{n({order:"desc",limit:1})}),await i(()=>{expect(t.current[0].map(a=>a.name)).toEqual(["Carol"])})}),it("should support actions.next and actions.refresh",async()=>{const r=await s({backend:u,tableName:"State5",key:"id"});await r.batchSet([{id:"1",name:"Alice",age:30},{id:"2",name:"Bob",age:25}]);const{result:t}=c(()=>l(r,{},[]));await i(()=>{expect(typeof t.current[1].next).toBe("function"),expect(typeof t.current[1].reset).toBe("function"),expect(t.current[1].reset()).resolves.toBeUndefined(),expect(t.current[1].next()).resolves.toBeFalsy()})}),it("should handle thousands of records",async()=>{const r=await s({backend:u,tableName:"State6",key:"id"}),t=[],n=1e3;for(let e=1;e<=n;e++)t.push({id:e.toString(),name:`Person${e}`,age:20+e%50});await r.batchSet(t);const{result:a}=c(()=>l(r,{},[]));await i(()=>{expect(a.current[0].length).toBe(d)});for(let e=0;e<n/d;e++)o(()=>{a.current[1].next()}),await i(()=>{expect(a.current[0].length).toBe(Math.min(d*(e+2),n))});o(()=>{a.current[1].reset()}),await i(()=>{expect(a.current[0].length).toBe(d)})}),it("should change ordering",async()=>{const r=await s({backend:u,tableName:"State7",key:"id"}),t=[];for(let e=1;e<=100;e++)t.push({id:e.toString(),name:`Person${e}`,age:20+e%50});await r.batchSet(t);const{result:n,rerender:a}=c(({order:e})=>l(r,{sorBy:"age",order:e},[e]),{initialProps:{order:"asc"}});await i(()=>{expect(n.current[0][0].age).toBe(20)}),o(()=>{a({order:"desc"})}),await i(()=>{expect(n.current[0][0].age).toBe(69)})})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createScheduler as P}from"../scheduler";import{shallow as R}from"../utils/shallow";import{createTable as M,DEFAULT_STEP_SIZE as v}from"./table/table";const f=P();let O=0;function E(){return O++}async function L(g){const T=E();function l(e){return`state-${T}-search-${e}`}let h;async function o(){return h||(h=await M(g)),h}const c=new Map,i=new Map,m=new Map;async function b(e,n){const t=m.get(e),{items:a,options:s={}}=n,{stepSize:d=v}=s;if(!t)return!1;const r=[];for(let I=0;I<d;I++){const y=await t.next();if(y.done){m.delete(e);break}r.push(y.value.document),n.keys.add(String(y.value.rowId))}return R(n.items,r)?!1:(n.items=[...a,...r],r.length>0)}function w(e){const n=i.get(e);n&&n()}async function k(e){const n=await o(),t=c.get(e);if(!t)return;const{options:a}=t,s=n.search({...a,select:(d,{rowId:r})=>({document:d,rowId:r})});m.set(e,s),t.keys=new Set,t.items=[],await b(e,t)}async function S(e){await k(e),w(e)}function x(e){const{key:n,op:t}=e,a=new Set;for(const[s,{keys:d}]of c)switch(t){case"delete":case"update":{d.has(String(n))&&a.add(s);break}case"insert":{a.add(s);break}}return a}async function u(e){const n=new Set;for(const t of e){const a=x(t);for(const s of a)n.add(s)}for(const t of n){const a=l(t);f.schedule(a,{searchId:t})}}const p=new Set;function D(e,n){c.has(e)||(c.set(e,{items:[],options:n,keys:new Set}),n&&S(e));const t=c.get(e);return n&&(t.options=n),t}return{async set(e){const t=await(await o()).set(e);return await u([t]),t},async batchSet(e){const t=await(await o()).batchSet(e);return await u(t),t},async delete(e){const t=await(await o()).delete(e);return t&&await u([t]),t},async deleteBy(e){const t=await(await o()).deleteBy(e);return await u(t),t},async get(e,n){return(await o()).get(e,n)},async*search(e={}){const n=await o();for await(const t of n.search(e))yield t},async count(e){return await(await o()).count(e)},updateSearchOptions(e,n){const t=D(e,n);t.options=n;const a=l(e);f.schedule(a,{searchId:e})},subscribe(e,n){const t=l(e),a=f.add(t,{onScheduleDone(){S(e)}});return p.add(a),i.has(e)||i.set(e,n),()=>{i.delete(e),a(),c.delete(e)}},getSnapshot(e){return D(e).items},refresh:S,destroy(){for(const e of p)e();c.clear(),i.clear()},async next(e){const n=c.get(e);if(n){const t=await b(e,n);return t&&w(e),t}return!1}}}export{L as createSqliteState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=":memory:";export{e as IN_MEMORY_DB};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Database as m}from"bun:sqlite";import{MapDeque as p}from"./map-deque";function d(){const n=m.open(":memory:"),r=new p(100);function s(e){if(r.has(e))return r.get(e);const t=n.prepare(e);return r.set(e,t),t}const c={execute:async(e,t=[])=>{const a=s(e).run(...t);return{rowsAffected:a.changes,changes:a.changes}},transaction:async e=>n.transaction(()=>e(c))(),path:n.filename,select:async(e,t=[])=>s(e).all(...t)};return c}export{d as bunMemoryBackend};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class a extends Map{constructor(e,t){super(t);this.maxSize=e;if(this.maxSize<=0)throw new RangeError("maxSize must be greater than 0")}set(e,t){if(this.has(e))return super.set(e,t),this;if(this.size>=this.maxSize){const s=this.keys().next().value;s!==void 0&&this.delete(s)}return super.set(e,t),this}}export{a as MapDeque};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import{getWhereQuery as S}from"./where";const p=500,L=100;async function N(k){const{backend:s,tableName:o,indexes:D,key:T}=k,d=T!==void 0;d?await s.execute(`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS ${o} (
|
|
3
|
+
key TEXT PRIMARY KEY,
|
|
4
|
+
data TEXT NOT NULL
|
|
5
|
+
);
|
|
6
|
+
`):await s.execute(`
|
|
7
|
+
CREATE TABLE IF NOT EXISTS ${o} (
|
|
8
|
+
data TEXT NOT NULL
|
|
9
|
+
);
|
|
10
|
+
`);for(const n of D??[]){const a=String(n);await s.execute(`CREATE INDEX IF NOT EXISTS idx_${o}_${a} ON ${o} (json_extract(data, '$.${a}'));`)}function O(n){return d?n[T]:void 0}async function $(n){return(await n.select("SELECT changes() AS c"))[0]?.c??0}const f={backend:s,async set(n,a){const e=a??s,r=JSON.stringify(n);if(d){const t=O(n);if(t==null)throw new Error(`Document is missing the configured key "${String(T)}". Provide it or create the table without "key".`);if(await e.execute(`UPDATE ${o} SET data = ? WHERE key = ?`,[r,t]),await $(e)===1)return{key:t,op:"update"};try{return await e.execute(`INSERT INTO ${o} (key, data) VALUES (?, ?)`,[t,r]),{key:t,op:"insert"}}catch{return await e.execute(`UPDATE ${o} SET data = ? WHERE key = ?`,[r,t]),{key:t,op:"update"}}}await e.execute(`INSERT INTO ${o} (data) VALUES (?)`,[r]);const c=(await e.select("SELECT last_insert_rowid() AS id"))[0]?.id;if(typeof c!="number")throw new Error("Failed to retrieve last_insert_rowid()");return{key:c,op:"insert"}},async get(n,a=(e,r)=>e){const e=d?"key = ?":"rowid = ?",r=await s.select(`SELECT rowid, data FROM ${o} WHERE ${e}`,[n]);if(r.length===0)return;const[i]=r,{data:c,rowid:u}=i,t=JSON.parse(c);return a(t,{rowid:u})},async delete(n){const a=d?"key = ?":"rowid = ?";if(await s.execute(`DELETE FROM ${o} WHERE ${a}`,[n]),((await s.select("SELECT changes() AS c"))[0]?.c??0)>0)return{key:n,op:"delete"}},async*search(n={}){const{sorBy:a,order:e="asc",limit:r,offset:i=0,where:c,select:u=(y,m)=>y,stepSize:t=L}=n;let E=`SELECT rowid, data FROM ${o}`;c&&(E+=" "+S(c));let l=0,h=i;for(;;){let y=E;a?y+=` ORDER BY json_extract(data, '$.${String(a)}') COLLATE NOCASE ${e.toUpperCase()}`:y+=d?` ORDER BY key COLLATE NOCASE ${e.toUpperCase()}`:` ORDER BY rowid ${e.toUpperCase()}`;const m=r?Math.min(t,r-l):t;y+=` LIMIT ${m} OFFSET ${h}`;const w=await s.select(y);if(w.length===0)break;for(const{rowid:b,data:R}of w){if(r&&l>=r)return;const A=JSON.parse(R);yield u(A,{rowId:b}),l++}if(w.length<m||r&&l>=r)break;h+=w.length}},async count(n={}){const{where:a}=n;let e=`SELECT COUNT(*) as count FROM ${o}`;return a&&(e+=" "+S(a)),(await s.select(e))[0]?.count??0},async deleteBy(n){const a=S(n),e=d?"key":"rowid",r=[];return await s.transaction(async i=>{const c=await i.select(`SELECT ${e} AS k, rowid FROM ${o} ${a}`);if(c.length===0)return;const u=c.map(t=>t.k);for(let t=0;t<u.length;t+=p){const E=u.slice(t,t+p),l=E.map(()=>"?").join(",");await i.execute(`DELETE FROM ${o} WHERE ${e} IN (${l})`,E)}for(const t of u)r.push({key:t,op:"delete"})}),r},async batchSet(n){const a=[];return await s.transaction(async e=>{for(const r of n){const i=await f.set(r,e);a.push(i)}}),a}};return f}export{L as DEFAULT_STEP_SIZE,N as createTable};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function y(n){return typeof n=="string"?`'${n.split("'").join("''")}'`:typeof n=="number"?n.toString():typeof n=="boolean"?n?"1":"0":`'${String(n).split("'").join("''")}'`}function $(n,c,i){const e=i?`${i}.`:"";return n==="KEY"?`"${e}key"`:typeof c=="string"?`CAST(json_extract(${e}data, '$.${n}') AS TEXT)`:typeof c=="boolean"?`CAST(json_extract(${e}data, '$.${n}') AS INTEGER)`:typeof c=="number"?`CAST(json_extract(${e}data, '$.${n}') AS NUMERIC)`:`json_extract(${e}data, '$.${n}')`}const N=new Set(["is","isNot","gt","gte","lt","lte","in","notIn","like"]);function m(n){return n===void 0}function l(n,c){if(!n||typeof n!="object")return"";if(!m(n.AND)){const t=n.AND;if(Array.isArray(t)&&t.length>0){let o="",u=!1;for(const a of t){const f=l(a,c);f&&(u&&(o+=" AND "),o+=f,u=!0)}return u?`(${o})`:""}return""}if(!m(n.OR)){const t=n.OR;if(Array.isArray(t)&&t.length>0){let o="",u=!1;for(const a of t){const f=l(a,c);f&&(u&&(o+=" OR "),o+=f,u=!0)}return u?`(${o})`:""}return""}if(!m(n.NOT)){const t=n.NOT;if(t&&typeof t=="object"){const o=l(t,c);return o?`(NOT ${o})`:""}return""}let i="",e=!1;for(const t in n){if(t==="AND"||t==="OR"||t==="NOT")continue;const o=n[t];if(o==null)continue;let u;typeof o!="object"||Array.isArray(o)?u=Array.isArray(o)?{in:o}:{is:o}:u=o;for(const a of Object.keys(u)){if(!N.has(a))continue;const f=u[a];if(f==null)continue;const s=Array.isArray(f)?f:[f];if(s.length!==0){if(a==="is"||a==="isNot"||a==="in"||a==="notIn"){const[D]=s,r=$(t,D,c);let d="";if(s.length>1)for(const[A,g]of s.entries())A>0&&(d+=","),d+=y(g);switch(a){case"is":{i+=s.length>1?(e?" AND ":"")+`${r} IN (${d})`:(e?" AND ":"")+`${r} = ${y(s[0])}`;break}case"isNot":{i+=s.length>1?(e?" AND ":"")+`${r} NOT IN (${d})`:(e?" AND ":"")+`${r} <> ${y(s[0])}`;break}case"in":{i+=s.length>1?(e?" AND ":"")+`${r} IN (${d})`:(e?" AND ":"")+`${r} IN (${y(s[0])})`;break}case"notIn":{i+=s.length>1?(e?" AND ":"")+`${r} NOT IN (${d})`:(e?" AND ":"")+`${r} NOT IN (${y(s[0])})`;break}}e=!0;continue}for(const D of s){const r=$(t,D,c);switch(a){case"gt":{i+=(e?" AND ":"")+`${r} > ${y(D)}`;break}case"gte":{i+=(e?" AND ":"")+`${r} >= ${y(D)}`;break}case"lt":{i+=(e?" AND ":"")+`${r} < ${y(D)}`;break}case"lte":{i+=(e?" AND ":"")+`${r} <= ${y(D)}`;break}case"like":{i+=(e?" AND ":"")+`${r} LIKE ${y(D)}`;break}}e=!0}}}}return e?`(${i})`:""}function k(n){const c=l(n);return c?`WHERE ${c}`:""}export{l as getWhere,k as getWhereQuery};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useCallback as c,useDebugValue as a,useId as p,useLayoutEffect as y,useMemo as S}from"react";import{isError as f,isPromise as D}from"../utils/is";import{useSyncExternalStoreWithSelector as b}from"use-sync-external-store/shim/with-selector";function q(e,s={},u=[]){const{select:r}=s,t=p();y(()=>{e.updateSearchOptions(t,{...s,select:void 0})},u);const l=c(n=>r?n.map(r):n,[r]),d=c(n=>e.subscribe(t,n),[e,t]),i=c(()=>e.getSnapshot(t),[e,t]),o=b(d,i,i,l);if(a(o),D(o)||f(o))throw o;const m=S(()=>({next:()=>e.next(t),reset:()=>e.refresh(t)}),[t,e]);return[o,m]}export{q as useSqliteValue};
|
package/esm/use-value.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useDebugValue as
|
|
1
|
+
import{useDebugValue as o}from"react";import{EMPTY_SELECTOR as a}from"./types";import{isError as s,isPromise as n}from"./utils/is";import{useSyncExternalStoreWithSelector as S}from"use-sync-external-store/shim/with-selector";function T(t,i=a){const{emitter:r}=t,e=S(t.emitter.subscribe,r.getSnapshot,r.getInitialSnapshot,i);if(o(e),n(e)||s(e))throw e;return e}export{T as useValue};
|
package/esm/utils/common.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isAbortError as
|
|
1
|
+
import{isAbortError as i,isEqualBase as c,isPromise as b,isUndefined as l}from"./is";class u extends Error{static Error="AbortError"}function m(r,t){t&&t.abort();const e=new AbortController,{signal:n}=e;return{promise:new Promise((a,o)=>{n.addEventListener("abort",()=>{o(new u)}),r.then(a).catch(o)}),controller:e}}function T(r,t=c){if(!l(r.current)){if(!l(r.previous)&&t(r.current,r.previous))return!1;r.previous=r.current}return!0}function f(r,t){const{cache:e,emitter:{emit:n}}=r;if(!b(t))return t;e.abortController&&e.abortController.abort();const{promise:s,controller:a}=m(t,e.abortController);return e.abortController=a,s.then(o=>{e.current=o,n()}).catch(o=>{i(o)||(e.current=o,n())})}export{u as AbortError,T as canUpdate,m as cancelablePromise,f as handleAsyncUpdate};
|
package/package.json
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muya",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"author": "samuel.gjabel@gmail.com",
|
|
5
5
|
"repository": "https://github.com/samuelgjabel/muya",
|
|
6
6
|
"main": "cjs/index.js",
|
|
7
7
|
"module": "esm/index.js",
|
|
8
|
-
"peerDependencies": {
|
|
9
|
-
"use-sync-external-store": ">=1.2.0",
|
|
10
|
-
"react": ">=18.0.0"
|
|
11
|
-
},
|
|
12
8
|
"description": "👀 Another React state management library",
|
|
13
9
|
"homepage": "https://github.com/samuelgjabel/muya",
|
|
14
10
|
"keywords": [
|
|
@@ -23,13 +19,9 @@
|
|
|
23
19
|
"zustand"
|
|
24
20
|
],
|
|
25
21
|
"license": "MIT",
|
|
26
|
-
"
|
|
27
|
-
"react":
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
"use-sync-external-store": {
|
|
31
|
-
"optional": true
|
|
32
|
-
}
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": ">=18 <20",
|
|
24
|
+
"use-sync-external-store": ">=1.4.0 <1.6.0"
|
|
33
25
|
},
|
|
34
26
|
"react-native": "src/index.ts",
|
|
35
27
|
"sideEffects": false,
|
|
@@ -9,7 +9,7 @@ describe('scheduler', () => {
|
|
|
9
9
|
const value = 2
|
|
10
10
|
const callback = jest.fn()
|
|
11
11
|
scheduler.add(id, {
|
|
12
|
-
|
|
12
|
+
onScheduleDone: callback,
|
|
13
13
|
})
|
|
14
14
|
scheduler.schedule(id, value)
|
|
15
15
|
await waitFor(() => {
|
|
@@ -23,7 +23,7 @@ describe('scheduler', () => {
|
|
|
23
23
|
for (const id of ids) {
|
|
24
24
|
const callback = jest.fn()
|
|
25
25
|
scheduler.add(id, {
|
|
26
|
-
|
|
26
|
+
onScheduleDone: callback,
|
|
27
27
|
})
|
|
28
28
|
callbacks.push(callback)
|
|
29
29
|
}
|
package/src/create-state.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { select } from './select'
|
|
2
|
-
import type { GetState, SetValue, State } from './types'
|
|
2
|
+
import type { GetState, SetValue, State, Cache } from './types'
|
|
3
3
|
import { useValue } from './use-value'
|
|
4
4
|
import { createEmitter } from './utils/create-emitter'
|
|
5
5
|
import { isEqualBase, isPromise } from './utils/is'
|
|
@@ -23,7 +23,7 @@ type FullState<T> = GetStateOptions<T>['set'] extends undefined ? GetState<T> :
|
|
|
23
23
|
export function createState<T>(options: GetStateOptions<T>): FullState<T> {
|
|
24
24
|
const { get, destroy, set, getSnapshot } = options
|
|
25
25
|
const isSet = !!set
|
|
26
|
-
|
|
26
|
+
const cache: Cache<T> = {}
|
|
27
27
|
const state: FullState<T> = function (selector) {
|
|
28
28
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
29
29
|
return useValue(state, selector)
|
|
@@ -50,6 +50,7 @@ export function createState<T>(options: GetStateOptions<T>): FullState<T> {
|
|
|
50
50
|
} as never
|
|
51
51
|
state.get = get as never
|
|
52
52
|
state.set = set as State<T>['set']
|
|
53
|
+
state.cache = cache
|
|
53
54
|
|
|
54
55
|
return state
|
|
55
56
|
}
|
package/src/create.ts
CHANGED
|
@@ -1,40 +1,38 @@
|
|
|
1
1
|
import { canUpdate, handleAsyncUpdate } from './utils/common'
|
|
2
2
|
import { isEqualBase, isFunction, isPromise, isSetValueFunction, isUndefined } from './utils/is'
|
|
3
|
-
import type {
|
|
3
|
+
import type { DefaultValue, IsEqual, SetStateCb, SetValue, State } from './types'
|
|
4
4
|
import { createScheduler } from './scheduler'
|
|
5
5
|
import { subscribeToDevelopmentTools } from './debug/development-tools'
|
|
6
6
|
import { createState } from './create-state'
|
|
7
7
|
|
|
8
|
-
export const
|
|
8
|
+
export const STATE_SCHEDULER = createScheduler()
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Create state from a default value.
|
|
12
12
|
*/
|
|
13
13
|
export function create<T>(initialValue: DefaultValue<T>, isEqual: IsEqual<T> = isEqualBase): State<T> {
|
|
14
|
-
const cache: Cache<T> = {}
|
|
15
|
-
|
|
16
14
|
function getValue(): T {
|
|
17
15
|
try {
|
|
18
|
-
if (isUndefined(cache.current)) {
|
|
16
|
+
if (isUndefined(state.cache.current)) {
|
|
19
17
|
const value = isFunction(initialValue) ? initialValue() : initialValue
|
|
20
|
-
const resolvedValue = handleAsyncUpdate(
|
|
21
|
-
cache.current = resolvedValue
|
|
18
|
+
const resolvedValue = handleAsyncUpdate(state, value)
|
|
19
|
+
state.cache.current = resolvedValue
|
|
22
20
|
|
|
23
|
-
return cache.current
|
|
21
|
+
return state.cache.current
|
|
24
22
|
}
|
|
25
|
-
return cache.current
|
|
23
|
+
return state.cache.current
|
|
26
24
|
} catch (error) {
|
|
27
|
-
cache.current = error as T
|
|
25
|
+
state.cache.current = error as T
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
return cache.current
|
|
28
|
+
return state.cache.current
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
async function handleAsyncSetValue(previousPromise: Promise<T>, value: SetStateCb<T>) {
|
|
34
32
|
await previousPromise
|
|
35
|
-
const newValue = value(cache.current as Awaited<T>)
|
|
36
|
-
const resolvedValue = handleAsyncUpdate(
|
|
37
|
-
cache.current = resolvedValue
|
|
33
|
+
const newValue = value(state.cache.current as Awaited<T>)
|
|
34
|
+
const resolvedValue = handleAsyncUpdate(state, newValue)
|
|
35
|
+
state.cache.current = resolvedValue
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
function setValue(value: SetValue<T>) {
|
|
@@ -45,13 +43,13 @@ export function create<T>(initialValue: DefaultValue<T>, isEqual: IsEqual<T> = i
|
|
|
45
43
|
handleAsyncSetValue(previous as Promise<T>, value)
|
|
46
44
|
return
|
|
47
45
|
}
|
|
48
|
-
if (cache.abortController) {
|
|
49
|
-
cache.abortController.abort()
|
|
46
|
+
if (state.cache.abortController) {
|
|
47
|
+
state.cache.abortController.abort()
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
const newValue = isFunctionValue ? value(previous as Awaited<T>) : value
|
|
53
|
-
const resolvedValue = handleAsyncUpdate(
|
|
54
|
-
cache.current = resolvedValue
|
|
51
|
+
const resolvedValue = handleAsyncUpdate(state, newValue)
|
|
52
|
+
state.cache.current = resolvedValue
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
const state = createState<T>({
|
|
@@ -60,18 +58,18 @@ export function create<T>(initialValue: DefaultValue<T>, isEqual: IsEqual<T> = i
|
|
|
60
58
|
getValue()
|
|
61
59
|
clearScheduler()
|
|
62
60
|
state.emitter.clear()
|
|
63
|
-
cache.current = undefined
|
|
61
|
+
state.cache.current = undefined
|
|
64
62
|
},
|
|
65
63
|
set(value: SetValue<T>) {
|
|
66
|
-
|
|
64
|
+
STATE_SCHEDULER.schedule(state.id, value)
|
|
67
65
|
},
|
|
68
66
|
getSnapshot: getValue,
|
|
69
67
|
})
|
|
70
68
|
|
|
71
|
-
const clearScheduler =
|
|
72
|
-
|
|
73
|
-
cache.current = getValue()
|
|
74
|
-
if (!canUpdate(cache, isEqual)) {
|
|
69
|
+
const clearScheduler = STATE_SCHEDULER.add(state.id, {
|
|
70
|
+
onScheduleDone() {
|
|
71
|
+
state.cache.current = getValue()
|
|
72
|
+
if (!canUpdate(state.cache, isEqual)) {
|
|
75
73
|
return
|
|
76
74
|
}
|
|
77
75
|
state.emitter.emit()
|
|
@@ -3,7 +3,7 @@ import { isPromise, isState } from '../utils/is'
|
|
|
3
3
|
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
5
5
|
// @ts-expect-error
|
|
6
|
-
const reduxDevelopmentTools =
|
|
6
|
+
const reduxDevelopmentTools = globalThis?.__REDUX_DEVTOOLS_EXTENSION__?.connect({
|
|
7
7
|
name: 'CustomState', // This will name your instance in the DevTools
|
|
8
8
|
trace: true, // Enables trace if needed
|
|
9
9
|
})
|
package/src/scheduler.ts
CHANGED
|
@@ -2,18 +2,26 @@ export const THRESHOLD = 0.2
|
|
|
2
2
|
export const THRESHOLD_ITEMS = 10
|
|
3
3
|
export const RESCHEDULE_COUNT = 0
|
|
4
4
|
|
|
5
|
+
type ScheduleId = string | number | symbol
|
|
5
6
|
interface GlobalSchedulerItem<T> {
|
|
6
7
|
value: T
|
|
7
|
-
id:
|
|
8
|
+
id: ScheduleId
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export interface SchedulerOptions<T> {
|
|
11
12
|
readonly onResolveItem?: (item: T) => void
|
|
12
|
-
readonly
|
|
13
|
+
readonly onScheduleDone: () => void | Promise<void>
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* A simple scheduler to batch updates and avoid blocking the main thread
|
|
18
|
+
* It uses a combination of time-based and count-based strategies to determine when to flush the queue.
|
|
19
|
+
* - Time-based: If the time taken to process the current batch is less than a threshold (THRESHOLD), it continues processing.
|
|
20
|
+
* - Count-based: If the ScheduleId of items in the batch exceeds a certain limit (THRESHOLD_ITEMS), it defers processing to the next microtask.
|
|
21
|
+
* @returns An object with methods to add listeners and schedule tasks.
|
|
22
|
+
*/
|
|
15
23
|
export function createScheduler() {
|
|
16
|
-
const listeners = new Map<
|
|
24
|
+
const listeners = new Map<ScheduleId, SchedulerOptions<unknown>>()
|
|
17
25
|
const batches = new Set<GlobalSchedulerItem<unknown>>()
|
|
18
26
|
|
|
19
27
|
let frame = performance.now()
|
|
@@ -44,7 +52,7 @@ export function createScheduler() {
|
|
|
44
52
|
return
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
const effectedListeners = new Set<
|
|
55
|
+
const effectedListeners = new Set<ScheduleId>()
|
|
48
56
|
for (const value of batches) {
|
|
49
57
|
if (listeners.has(value.id)) {
|
|
50
58
|
effectedListeners.add(value.id)
|
|
@@ -62,18 +70,18 @@ export function createScheduler() {
|
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
for (const id of effectedListeners) {
|
|
65
|
-
listeners.get(id)?.
|
|
73
|
+
listeners.get(id)?.onScheduleDone()
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
return {
|
|
70
|
-
add<T>(id:
|
|
78
|
+
add<T>(id: ScheduleId, option: SchedulerOptions<T>) {
|
|
71
79
|
listeners.set(id, option as SchedulerOptions<unknown>)
|
|
72
80
|
return () => {
|
|
73
81
|
listeners.delete(id)
|
|
74
82
|
}
|
|
75
83
|
},
|
|
76
|
-
schedule<T>(id:
|
|
84
|
+
schedule<T>(id: ScheduleId, value: T) {
|
|
77
85
|
batches.add({ value, id })
|
|
78
86
|
schedule()
|
|
79
87
|
},
|
package/src/select.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { STATE_SCHEDULER } from './create'
|
|
2
2
|
import { createState } from './create-state'
|
|
3
3
|
import { subscribeToDevelopmentTools } from './debug/development-tools'
|
|
4
|
-
import type {
|
|
4
|
+
import type { GetState, IsEqual } from './types'
|
|
5
5
|
import { AbortError, canUpdate, handleAsyncUpdate } from './utils/common'
|
|
6
6
|
import { isPromise, isUndefined } from './utils/is'
|
|
7
7
|
|
|
@@ -21,8 +21,6 @@ export function select<T = unknown, S extends Array<unknown> = []>(
|
|
|
21
21
|
selector: (...values: AwaitedArray<S>) => T,
|
|
22
22
|
isEqual?: IsEqual<T>,
|
|
23
23
|
): GetState<T> {
|
|
24
|
-
const cache: Cache<T> = {}
|
|
25
|
-
|
|
26
24
|
function computedValue(): T {
|
|
27
25
|
let hasPromise = false
|
|
28
26
|
const values = states.map((state) => {
|
|
@@ -50,18 +48,18 @@ export function select<T = unknown, S extends Array<unknown> = []>(
|
|
|
50
48
|
return result
|
|
51
49
|
}
|
|
52
50
|
function getSnapshot(): T {
|
|
53
|
-
if (isUndefined(cache.current)) {
|
|
51
|
+
if (isUndefined(state.cache.current)) {
|
|
54
52
|
const newValue = computedValue()
|
|
55
|
-
cache.current = handleAsyncUpdate(
|
|
53
|
+
state.cache.current = handleAsyncUpdate(state, newValue)
|
|
56
54
|
}
|
|
57
|
-
return cache.current
|
|
55
|
+
return state.cache.current
|
|
58
56
|
}
|
|
59
57
|
function getValue(): T {
|
|
60
|
-
if (isUndefined(cache.current)) {
|
|
58
|
+
if (isUndefined(state.cache.current)) {
|
|
61
59
|
const newValue = computedValue()
|
|
62
|
-
cache.current = handleAsyncUpdate(
|
|
60
|
+
state.cache.current = handleAsyncUpdate(state, newValue)
|
|
63
61
|
}
|
|
64
|
-
const { current } = cache
|
|
62
|
+
const { current } = state.cache
|
|
65
63
|
if (isPromise(current)) {
|
|
66
64
|
return new Promise((resolve) => {
|
|
67
65
|
current.then((value: unknown) => {
|
|
@@ -74,13 +72,13 @@ export function select<T = unknown, S extends Array<unknown> = []>(
|
|
|
74
72
|
})
|
|
75
73
|
}) as T
|
|
76
74
|
}
|
|
77
|
-
return cache.current
|
|
75
|
+
return state.cache.current
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
const cleanups: Array<() => void> = []
|
|
81
79
|
for (const dependencyState of states) {
|
|
82
80
|
const clean = dependencyState.emitter.subscribe(() => {
|
|
83
|
-
|
|
81
|
+
STATE_SCHEDULER.schedule(state.id, null)
|
|
84
82
|
})
|
|
85
83
|
cleanups.push(clean)
|
|
86
84
|
}
|
|
@@ -92,17 +90,17 @@ export function select<T = unknown, S extends Array<unknown> = []>(
|
|
|
92
90
|
}
|
|
93
91
|
clearScheduler()
|
|
94
92
|
state.emitter.clear()
|
|
95
|
-
cache.current = undefined
|
|
93
|
+
state.cache.current = undefined
|
|
96
94
|
},
|
|
97
95
|
get: getValue,
|
|
98
96
|
getSnapshot,
|
|
99
97
|
})
|
|
100
98
|
|
|
101
|
-
const clearScheduler =
|
|
102
|
-
|
|
99
|
+
const clearScheduler = STATE_SCHEDULER.add(state.id, {
|
|
100
|
+
onScheduleDone() {
|
|
103
101
|
const newValue = computedValue()
|
|
104
|
-
cache.current = handleAsyncUpdate(
|
|
105
|
-
if (!canUpdate(cache, isEqual)) {
|
|
102
|
+
state.cache.current = handleAsyncUpdate(state, newValue)
|
|
103
|
+
if (!canUpdate(state.cache, isEqual)) {
|
|
106
104
|
return
|
|
107
105
|
}
|
|
108
106
|
state.emitter.emit()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createSqliteState } from '../create-sqlite-state'
|
|
2
|
+
import { bunMemoryBackend } from '../table/bun-backend'
|
|
3
|
+
|
|
4
|
+
const backend = bunMemoryBackend()
|
|
5
|
+
interface Person {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
age: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('create-sqlite-state', () => {
|
|
12
|
+
it('should batchSet and update multiple documents', async () => {
|
|
13
|
+
const sql = await createSqliteState<Person>({ backend, tableName: 'State2', key: 'id' })
|
|
14
|
+
await sql.batchSet([
|
|
15
|
+
{ id: '1', name: 'Alice', age: 30 },
|
|
16
|
+
{ id: '2', name: 'Bob', age: 25 },
|
|
17
|
+
])
|
|
18
|
+
const all = []
|
|
19
|
+
for await (const p of sql.search()) all.push(p)
|
|
20
|
+
expect(all).toHaveLength(2)
|
|
21
|
+
// update both
|
|
22
|
+
await sql.batchSet([
|
|
23
|
+
{ id: '1', name: 'Alice2', age: 31 },
|
|
24
|
+
{ id: '2', name: 'Bob2', age: 26 },
|
|
25
|
+
])
|
|
26
|
+
const updated = []
|
|
27
|
+
for await (const p of sql.search()) updated.push(p)
|
|
28
|
+
expect(updated).toEqual([
|
|
29
|
+
{ id: '1', name: 'Alice2', age: 31 },
|
|
30
|
+
{ id: '2', name: 'Bob2', age: 26 },
|
|
31
|
+
])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should deleteBy condition', async () => {
|
|
35
|
+
const sql = await createSqliteState<Person>({ backend, tableName: 'State3', key: 'id' })
|
|
36
|
+
await sql.batchSet([
|
|
37
|
+
{ id: '1', name: 'Alice', age: 30 },
|
|
38
|
+
{ id: '2', name: 'Bob', age: 25 },
|
|
39
|
+
{ id: '3', name: 'Carol', age: 40 },
|
|
40
|
+
])
|
|
41
|
+
const deleted = await sql.deleteBy({ age: { gt: 30 } })
|
|
42
|
+
expect(deleted.length).toBe(1)
|
|
43
|
+
const all = []
|
|
44
|
+
for await (const p of sql.search()) all.push(p)
|
|
45
|
+
expect(all.map((p) => p.id)).toEqual(['1', '2'])
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should get by key and with selector', async () => {
|
|
49
|
+
const sql = await createSqliteState<Person>({ backend, tableName: 'State4', key: 'id' })
|
|
50
|
+
await sql.set({ id: '1', name: 'Alice', age: 30 })
|
|
51
|
+
const doc = await sql.get('1')
|
|
52
|
+
expect(doc).toEqual({ id: '1', name: 'Alice', age: 30 })
|
|
53
|
+
const name = await sql.get('1', (d) => d.name)
|
|
54
|
+
expect(name).toBe('Alice')
|
|
55
|
+
const missing = await sql.get('999')
|
|
56
|
+
expect(missing).toBeUndefined()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should count documents with and without where', async () => {
|
|
60
|
+
const sql = await createSqliteState<Person>({ backend, tableName: 'State5', key: 'id' })
|
|
61
|
+
await sql.batchSet([
|
|
62
|
+
{ id: '1', name: 'Alice', age: 30 },
|
|
63
|
+
{ id: '2', name: 'Bob', age: 25 },
|
|
64
|
+
{ id: '3', name: 'Carol', age: 40 },
|
|
65
|
+
])
|
|
66
|
+
expect(await sql.count()).toBe(3)
|
|
67
|
+
expect(await sql.count({ where: { age: { gt: 30 } } })).toBe(1)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should support search with options', async () => {
|
|
71
|
+
const sql = await createSqliteState<Person>({ backend, tableName: 'State6', key: 'id' })
|
|
72
|
+
await sql.batchSet([
|
|
73
|
+
{ id: '1', name: 'Alice', age: 30 },
|
|
74
|
+
{ id: '2', name: 'Bob', age: 25 },
|
|
75
|
+
{ id: '3', name: 'Carol', age: 40 },
|
|
76
|
+
])
|
|
77
|
+
const results = []
|
|
78
|
+
for await (const p of sql.search({ where: { age: { lt: 35 } } })) results.push(p)
|
|
79
|
+
expect(results.map((p) => p.id)).toEqual(['1', '2'])
|
|
80
|
+
})
|
|
81
|
+
})
|