muya 2.3.1 → 2.3.3

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.
@@ -1 +1 @@
1
- import{STATE_SCHEDULER as y}from"../create";import{getId as P}from"../utils/id";import{shallow as v}from"../utils/shallow";import{selectSql as O}from"./select-sql";import{createTable as R,DEFAULT_STEP_SIZE as M}from"./table/table";function K(g){const k=P();function m(e){return`state-${k}-search-${e}`}let h;async function o(){if(!h){const{backend:e,...n}=g,t=e instanceof Promise?await e:e;h=await R({backend:t,...n})}return h}const c=new Map,i=new Map,S=new Map;async function p(e,n){const t=S.get(e),{options:a={}}=n,{stepSize:s=M}=a;if(!t)return!1;const r=[];for(let u=0;u<s;u++){const l=await t.next();if(l.done){S.delete(e);break}n.keys.has(String(l.value.rowId))||(r.push(l.value.document),n.keys.add(String(l.value.rowId)))}return r.length===0||v(n.items,r)?!1:(n.items=[...n.items,...r],!0)}function b(e){const n=i.get(e);n&&n()}async function x(e){const n=await o(),t=c.get(e);if(!t)return;const{options:a}=t,s=n.search({...a,select:(r,{rowId:u})=>({document:r,rowId:u})});S.set(e,s),t.keys=new Set,t.items=[],await p(e,t)}async function f(e){await x(e),b(e)}function T(e){const{key:n,op:t}=e,a=new Set;for(const[s,{keys:r}]of c)switch(t){case"delete":case"update":{r.has(String(n))&&a.add(s);break}case"insert":{a.add(s);break}}return a}async function d(e){const n=new Set;for(const t of e){const a=T(t);for(const s of a)n.add(s)}for(const t of n){const a=m(t);y.schedule(a,{searchId:t})}}const w=new Set;function D(e,n){c.has(e)||(c.set(e,{items:[],options:n,keys:new Set}),n&&f(e));const t=c.get(e);return n&&(t.options=n),t}const I={async set(e){const t=await(await o()).set(e);return await d([t]),t},async batchSet(e){const t=await(await o()).batchSet(e);return await d(t),t},async delete(e){const t=await(await o()).delete(e);return t&&await d([t]),t},async deleteBy(e){const t=await(await o()).deleteBy(e);return await d(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=m(e);y.schedule(a,{searchId:e})},subscribe(e,n){const t=m(e),a=y.add(t,{onScheduleDone(){f(e)}});return w.add(a),i.has(e)||i.set(e,n),()=>{i.delete(e),a(),c.delete(e)}},getSnapshot(e){return D(e).items},refresh:f,destroy(){for(const e of w)e();c.clear(),i.clear()},async next(e){const n=c.get(e);if(n){const t=await p(e,n);return t&&b(e),t}return!1},select(e){return O(I,e)}};return I}export{K as createSqliteState};
1
+ import{STATE_SCHEDULER as f}from"../create";import{getId as P}from"../utils/id";import{shallow as v}from"../utils/shallow";import{selectSql as O}from"./select-sql";import{createTable as R,DEFAULT_STEP_SIZE as M}from"./table/table";function K(g){const k=P();function m(e){return`state-${k}-search-${e}`}let h;async function o(){if(!h){const{backend:e,...n}=g,t=e instanceof Promise?await e:e;h=await R({backend:t,...n})}return h}const c=new Map,i=new Map,S=new Map;async function p(e,n){const t=S.get(e),{options:a={}}=n,{stepSize:s=M}=a;if(!t)return!1;const r=[];for(let l=0;l<s;l++){const u=await t.next();if(u.done){S.delete(e);break}n.keys.has(String(u.value.rowId))||(r.push(u.value.document),n.keys.add(String(u.value.rowId)))}return r.length===0||v(n.items,r)?!1:(n.items=[...n.items,...r],!0)}function b(e){const n=i.get(e);n&&n()}async function x(e){const n=await o(),t=c.get(e);if(!t)return;const{options:a}=t,s=n.search({...a,select:(r,{rowId:l})=>({document:r,rowId:l})});S.set(e,s),t.keys=new Set,t.items=[],await p(e,t)}async function y(e){await x(e),b(e)}function T(e){const{key:n,op:t}=e,a=new Set;for(const[s,{keys:r}]of c)switch(t){case"delete":case"update":{r.has(String(n))&&a.add(s);break}case"insert":{a.add(s);break}}return a}async function d(e){const n=new Set;for(const t of e){const a=T(t);for(const s of a)n.add(s)}for(const t of n){const a=m(t);f.schedule(a,{searchId:t})}}const w=new Set;function D(e,n){c.has(e)||(c.set(e,{items:[],options:n,keys:new Set}),n&&y(e));const t=c.get(e);return n&&(t.options=n),t}const I={clear(e){c.delete(e)},async set(e){const t=await(await o()).set(e);return await d([t]),t},async batchSet(e){const t=await(await o()).batchSet(e);return await d(t),t},async delete(e){const t=await(await o()).delete(e);return t&&await d([t]),t},async deleteBy(e){const t=await(await o()).deleteBy(e);return await d(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=m(e);f.schedule(a,{searchId:e})},subscribe(e,n){const t=m(e),a=f.add(t,{onScheduleDone(){y(e)}});return w.add(a),i.has(e)||i.set(e,n),()=>{i.delete(e),a()}},getSnapshot(e){return D(e).items},refresh:y,destroy(){for(const e of w)e();c.clear(),i.clear()},async next(e){const n=c.get(e);if(n){const t=await p(e,n);return t&&b(e),t}return!1},select(e){return O(I,e)}};return I}export{K as createSqliteState};
@@ -1 +1 @@
1
- import{useCallback as r,useDebugValue as S,useId as p,useLayoutEffect as a,useMemo as D}from"react";import{isError as f,isPromise as y}from"../utils/is";import{useSyncExternalStoreWithSelector as x}from"use-sync-external-store/shim/with-selector";function L(e,s={},u=[]){const{select:c}=s,t=p();a(()=>{e.updateSearchOptions(t,{...s,select:void 0})},u);const l=r(o=>c?o.map(c):o,[c]),d=r(o=>e.subscribe(t,o),[e,t]),i=r(()=>e.getSnapshot(t),[e,t]),n=x(d,i,i,l);if(S(n),y(n)||f(n))throw n;const m=D(()=>({next:()=>e.next(t),reset:()=>e.refresh(t)}),[t,e]);return[n,m]}export{L as useSqliteValue};
1
+ import{useCallback as r,useDebugValue as S,useEffect as p,useId as a,useLayoutEffect as f,useMemo as D}from"react";import{isError as y,isPromise as x}from"../utils/is";import{useSyncExternalStoreWithSelector as b}from"use-sync-external-store/shim/with-selector";function O(e,s={},u=[]){const{select:c}=s,t=a();f(()=>{e.updateSearchOptions(t,{...s,select:void 0})},u),p(()=>()=>{e.clear(t)},[]);const l=r(o=>c?o.map(c):o,[c]),d=r(o=>e.subscribe(t,o),[e,t]),i=r(()=>e.getSnapshot(t),[e,t]),n=b(d,i,i,l);if(S(n),x(n)||y(n))throw n;const m=D(()=>({next:()=>e.next(t),reset:()=>e.refresh(t)}),[t,e]);return[n,m]}export{O as useSqliteValue};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muya",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "author": "samuel.gjabel@gmail.com",
5
5
  "repository": "https://github.com/samuelgjabel/muya",
6
6
  "main": "cjs/index.js",
@@ -4,7 +4,7 @@ import { createSqliteState } from '../create-sqlite'
4
4
  import { useSqliteValue } from '../use-sqlite'
5
5
  import { waitFor } from '@testing-library/react'
6
6
  import { bunMemoryBackend } from '../table/bun-backend'
7
- import { Suspense, useState } from 'react'
7
+ import { StrictMode, Suspense, useState } from 'react'
8
8
  import { DEFAULT_STEP_SIZE } from '../table/table'
9
9
 
10
10
  const backend = bunMemoryBackend()
@@ -15,13 +15,17 @@ interface Person {
15
15
  }
16
16
 
17
17
  function Wrapper({ children }: Readonly<{ children: React.ReactNode }>) {
18
- return <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
18
+ return (
19
+ <StrictMode>
20
+ <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
21
+ </StrictMode>
22
+ )
19
23
  }
20
24
  describe('use-sqlite-state', () => {
21
25
  it('should get basic value states', async () => {
22
26
  const sql = createSqliteState<Person>({ backend, tableName: 'State1', key: 'id' })
23
27
  let reRenders = 0
24
- const { result } = renderHook(
28
+ const { result, rerender } = renderHook(
25
29
  () => {
26
30
  reRenders++
27
31
  const aha = useSqliteValue(sql, {}, [])
@@ -66,6 +70,14 @@ describe('use-sqlite-state', () => {
66
70
  expect(result.current[0].length).toBe(2)
67
71
  expect(reRenders).toBe(6)
68
72
  })
73
+
74
+ act(() => {
75
+ rerender()
76
+ })
77
+ await waitFor(() => {
78
+ expect(reRenders).toBe(7)
79
+ expect(result.current[0].length).toBe(2)
80
+ })
69
81
  })
70
82
 
71
83
  it('should use where clause changed via state', async () => {
@@ -79,11 +91,11 @@ describe('use-sqlite-state', () => {
79
91
  const { result } = renderHook(() => {
80
92
  reRenders++
81
93
  const [minAge, setMinAge] = useState(20)
82
- return [useSqliteValue(sql, { where: { age: { gt: minAge } }, sorBy: 'age' }, [minAge]), setMinAge] as const
94
+ return [useSqliteValue(sql, { where: { age: { gt: minAge } }, sortBy: 'age' }, [minAge]), setMinAge] as const
83
95
  })
84
96
 
85
97
  await waitFor(() => {
86
- expect(result.current[0][0].map((p) => p.name)).toEqual(['Alice', 'Bob', 'Carol'])
98
+ expect(result.current[0][0].map((p) => p.name)).toEqual(['Bob', 'Alice', 'Carol'])
87
99
  expect(reRenders).toBe(2)
88
100
  })
89
101
 
@@ -132,17 +144,17 @@ describe('use-sqlite-state', () => {
132
144
  { id: '3', name: 'Carol', age: 40 },
133
145
  ])
134
146
  const { result, rerender } = renderHook(
135
- ({ order, limit }) => useSqliteValue(sql, { sorBy: 'age', order, limit }, [order, limit]),
147
+ ({ order, limit }) => useSqliteValue(sql, { sortBy: 'age', order, limit }, [order, limit]),
136
148
  { initialProps: { order: 'asc' as 'asc' | 'desc', limit: 2 } },
137
149
  )
138
150
  await waitFor(() => {
139
- expect(result.current[0].map((p) => p.name)).toEqual(['Alice', 'Bob'])
151
+ expect(result.current[0].map((p) => p.name)).toEqual(['Bob', 'Alice'])
140
152
  })
141
153
  act(() => {
142
154
  rerender({ order: 'desc', limit: 2 })
143
155
  })
144
156
  await waitFor(() => {
145
- expect(result.current[0].map((p) => p.name)).toEqual(['Alice', 'Bob'])
157
+ expect(result.current[0].map((p) => p.name)).toEqual(['Carol', 'Alice'])
146
158
  })
147
159
  act(() => {
148
160
  rerender({ order: 'desc', limit: 1 })
@@ -243,11 +255,11 @@ describe('use-sqlite-state', () => {
243
255
  people.push({ id: index.toString(), name: `Person${index}`, age: 20 + (index % 50) })
244
256
  }
245
257
  await sql.batchSet(people)
246
- const { result, rerender } = renderHook(({ order }) => useSqliteValue(sql, { sorBy: 'age', order }, [order]), {
258
+ const { result, rerender } = renderHook(({ order }) => useSqliteValue(sql, { sortBy: 'age', order }, [order]), {
247
259
  initialProps: { order: 'asc' as 'asc' | 'desc' },
248
260
  })
249
261
  await waitFor(() => {
250
- expect(result.current[0][0].age).toBe(21)
262
+ expect(result.current[0][0].age).toBe(20)
251
263
  })
252
264
  act(() => {
253
265
  rerender({ order: 'desc' })
@@ -268,14 +280,14 @@ describe('use-sqlite-state', () => {
268
280
  useSqliteValue(
269
281
  sql,
270
282
  {
271
- sorBy: 'age',
283
+ sortBy: 'age',
272
284
  select: (d) => d.name,
273
285
  },
274
286
  [],
275
287
  ),
276
288
  )
277
289
  await waitFor(() => {
278
- expect(result.current[0]).toEqual(['Alice', 'Bob', 'Carol'])
290
+ expect(result.current[0]).toEqual(['Bob', 'Alice', 'Carol'])
279
291
  })
280
292
  })
281
293
  it('should add 50 documents and then load with another hook', async () => {
@@ -286,7 +298,7 @@ describe('use-sqlite-state', () => {
286
298
  return useSqliteValue(
287
299
  sql,
288
300
  {
289
- sorBy: 'age',
301
+ sortBy: 'age',
290
302
  order: 'desc',
291
303
  },
292
304
  [],
@@ -31,6 +31,7 @@ export interface SyncTable<Document extends DocType> {
31
31
  readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>
32
32
  readonly destroy: () => void
33
33
  readonly next: (searchId: SearchId) => Promise<boolean>
34
+ readonly clear: (searchId: SearchId) => void
34
35
 
35
36
  readonly select: <Params extends unknown[]>(
36
37
  compute: (...args: Params) => SearchOptions<Document>,
@@ -221,6 +222,9 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
221
222
  }
222
223
 
223
224
  const state: SyncTable<Document> = {
225
+ clear(searchId: SearchId) {
226
+ cachedData.delete(searchId)
227
+ },
224
228
  async set(document) {
225
229
  const table = await getTable()
226
230
  const changes = await table.set(document)
@@ -271,20 +275,19 @@ export function createSqliteState<Document extends DocType>(options: CreateSqlit
271
275
 
272
276
  subscribe(searchId, listener) {
273
277
  const scheduleId = getScheduleId(searchId)
274
- const clear = STATE_SCHEDULER.add(scheduleId, {
278
+ const clearScheduler = STATE_SCHEDULER.add(scheduleId, {
275
279
  onScheduleDone() {
276
280
  refresh(searchId)
277
281
  },
278
282
  })
279
- clearSchedulers.add(clear)
283
+ clearSchedulers.add(clearScheduler)
280
284
 
281
285
  if (!listeners.has(searchId)) {
282
286
  listeners.set(searchId, listener)
283
287
  }
284
288
  return () => {
285
289
  listeners.delete(searchId)
286
- clear()
287
- cachedData.delete(searchId)
290
+ clearScheduler()
288
291
  }
289
292
  },
290
293
  getSnapshot(searchId) {
@@ -1,13 +1,13 @@
1
1
  import { createState } from '../create-state'
2
2
  import type { GetState } from '../types'
3
3
  import type { SyncTable } from './create-sqlite'
4
- import type { DocType } from './table/table.types'
4
+ import type { DocType, DotPath } from './table/table.types'
5
5
  import type { Where } from './table/where'
6
6
 
7
7
  export type CreateState<Document, Params extends unknown[]> = (...params: Params) => GetState<Document[]>
8
8
 
9
9
  export interface SqlSeachOptions<Document extends DocType> {
10
- readonly sorBy?: keyof Document
10
+ readonly sortBy?: DotPath<Document>
11
11
  readonly order?: 'asc' | 'desc'
12
12
  readonly limit?: number
13
13
  readonly offset?: number
@@ -1,4 +1,5 @@
1
1
  // table.types.ts
2
+ import type { SqlSeachOptions } from '../select-sql'
2
3
  import type { Backend } from './backend'
3
4
  import type { Where } from './where'
4
5
 
@@ -27,13 +28,7 @@ export interface DbOptions<Document extends DocType> {
27
28
  readonly disablePragmaOptimization?: boolean
28
29
  }
29
30
 
30
- export interface SearchOptions<Document extends DocType, Selected = Document> {
31
- readonly sortBy?: DotPath<Document>
32
- readonly order?: 'asc' | 'desc'
33
- readonly limit?: number
34
- readonly offset?: number
35
- readonly where?: Where<Document>
36
- readonly stepSize?: number
31
+ export interface SearchOptions<Document extends DocType, Selected = Document> extends SqlSeachOptions<Document> {
37
32
  readonly select?: (document: Document, meta: { rowId: number }) => Selected
38
33
  }
39
34
 
@@ -1,4 +1,4 @@
1
- import { useCallback, useDebugValue, useId, useLayoutEffect, useMemo, type DependencyList } from 'react'
1
+ import { useCallback, useDebugValue, useEffect, useId, useLayoutEffect, useMemo, type DependencyList } from 'react'
2
2
  import type { SyncTable } from './create-sqlite'
3
3
  import type { DocType } from './table/table.types'
4
4
  import { isError, isPromise } from '../utils/is'
@@ -39,6 +39,13 @@ export function useSqliteValue<Document extends DocType, Selected = Document>(
39
39
  // eslint-disable-next-line react-hooks/exhaustive-deps
40
40
  }, deps)
41
41
 
42
+ useEffect(() => {
43
+ return () => {
44
+ state.clear(id)
45
+ }
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ }, [])
48
+
42
49
  const selector = useCallback(
43
50
  (documents: Document[]) => {
44
51
  // eslint-disable-next-line unicorn/no-array-callback-reference
@@ -22,6 +22,7 @@ export interface SyncTable<Document extends DocType> {
22
22
  readonly deleteBy: (where: Where<Document>) => Promise<MutationResult[]>;
23
23
  readonly destroy: () => void;
24
24
  readonly next: (searchId: SearchId) => Promise<boolean>;
25
+ readonly clear: (searchId: SearchId) => void;
25
26
  readonly select: <Params extends unknown[]>(compute: (...args: Params) => SearchOptions<Document>) => CreateState<Document, Params>;
26
27
  }
27
28
  /**
@@ -1,10 +1,10 @@
1
1
  import type { GetState } from '../types';
2
2
  import type { SyncTable } from './create-sqlite';
3
- import type { DocType } from './table/table.types';
3
+ import type { DocType, DotPath } from './table/table.types';
4
4
  import type { Where } from './table/where';
5
5
  export type CreateState<Document, Params extends unknown[]> = (...params: Params) => GetState<Document[]>;
6
6
  export interface SqlSeachOptions<Document extends DocType> {
7
- readonly sorBy?: keyof Document;
7
+ readonly sortBy?: DotPath<Document>;
8
8
  readonly order?: 'asc' | 'desc';
9
9
  readonly limit?: number;
10
10
  readonly offset?: number;
@@ -1,3 +1,4 @@
1
+ import type { SqlSeachOptions } from '../select-sql';
1
2
  import type { Backend } from './backend';
2
3
  import type { Where } from './where';
3
4
  export type DocType = {
@@ -16,13 +17,7 @@ export interface DbOptions<Document extends DocType> {
16
17
  readonly key?: DotPath<Document>;
17
18
  readonly disablePragmaOptimization?: boolean;
18
19
  }
19
- export interface SearchOptions<Document extends DocType, Selected = Document> {
20
- readonly sortBy?: DotPath<Document>;
21
- readonly order?: 'asc' | 'desc';
22
- readonly limit?: number;
23
- readonly offset?: number;
24
- readonly where?: Where<Document>;
25
- readonly stepSize?: number;
20
+ export interface SearchOptions<Document extends DocType, Selected = Document> extends SqlSeachOptions<Document> {
26
21
  readonly select?: (document: Document, meta: {
27
22
  rowId: number;
28
23
  }) => Selected;