muya 2.5.4 → 2.5.5

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.
Files changed (134) hide show
  1. package/{src/__tests__ → __tests__}/bench.test.tsx +4 -4
  2. package/{src/__tests__ → __tests__}/compare.test.tsx +8 -6
  3. package/{src/__tests__ → __tests__}/create.test.tsx +2 -2
  4. package/{src/utils/__tests__ → __tests__}/is.test.ts +3 -3
  5. package/{src/__tests__ → __tests__}/scheduler.test.tsx +1 -1
  6. package/{src/__tests__ → __tests__}/select.test.tsx +5 -5
  7. package/{src/utils/__tests__ → __tests__}/shallow.test.ts +1 -1
  8. package/{src/__tests__ → __tests__}/use-value-loadable.test.tsx +3 -3
  9. package/{src/__tests__ → __tests__}/use-value.test.tsx +8 -8
  10. package/build.ts +67 -0
  11. package/dist/cjs/index.js +1 -0
  12. package/dist/esm/create.js +1 -0
  13. package/dist/esm/debug/development-tools.js +1 -0
  14. package/dist/esm/index.js +1 -0
  15. package/dist/esm/select.js +1 -0
  16. package/{types → dist/types}/create-state.d.ts +1 -0
  17. package/dist/types/create-state.d.ts.map +1 -0
  18. package/{types → dist/types}/create.d.ts +1 -0
  19. package/dist/types/create.d.ts.map +1 -0
  20. package/dist/types/debug/development-tools.d.ts +13 -0
  21. package/dist/types/debug/development-tools.d.ts.map +1 -0
  22. package/{types → dist/types}/index.d.ts +3 -1
  23. package/dist/types/index.d.ts.map +1 -0
  24. package/{types → dist/types}/scheduler.d.ts +1 -0
  25. package/dist/types/scheduler.d.ts.map +1 -0
  26. package/{types → dist/types}/select.d.ts +1 -0
  27. package/dist/types/select.d.ts.map +1 -0
  28. package/{types → dist/types}/types.d.ts +1 -0
  29. package/dist/types/types.d.ts.map +1 -0
  30. package/{types → dist/types}/use-value-loadable.d.ts +1 -0
  31. package/dist/types/use-value-loadable.d.ts.map +1 -0
  32. package/{types → dist/types}/use-value.d.ts +2 -1
  33. package/dist/types/use-value.d.ts.map +1 -0
  34. package/{types → dist/types}/utils/common.d.ts +1 -0
  35. package/dist/types/utils/common.d.ts.map +1 -0
  36. package/{types → dist/types}/utils/create-emitter.d.ts +1 -0
  37. package/dist/types/utils/create-emitter.d.ts.map +1 -0
  38. package/{types → dist/types}/utils/id.d.ts +1 -0
  39. package/dist/types/utils/id.d.ts.map +1 -0
  40. package/{types → dist/types}/utils/is.d.ts +1 -0
  41. package/dist/types/utils/is.d.ts.map +1 -0
  42. package/{types → dist/types}/utils/shallow.d.ts +1 -0
  43. package/dist/types/utils/shallow.d.ts.map +1 -0
  44. package/package.json +23 -8
  45. package/src/create-state.d.ts.map +1 -0
  46. package/src/create.d.ts.map +1 -0
  47. package/src/create.ts +7 -2
  48. package/src/debug/development-tools.d.ts.map +1 -0
  49. package/src/debug/development-tools.ts +5 -40
  50. package/src/index.d.ts.map +1 -0
  51. package/src/index.ts +2 -1
  52. package/src/scheduler.d.ts.map +1 -0
  53. package/src/select.d.ts.map +1 -0
  54. package/src/select.ts +7 -2
  55. package/src/types.d.ts.map +1 -0
  56. package/src/use-value-loadable.d.ts.map +1 -0
  57. package/src/use-value.d.ts.map +1 -0
  58. package/src/use-value.ts +1 -1
  59. package/src/utils/common.d.ts.map +1 -0
  60. package/src/utils/create-emitter.d.ts.map +1 -0
  61. package/src/utils/id.d.ts.map +1 -0
  62. package/src/utils/is.d.ts.map +1 -0
  63. package/src/utils/shallow.d.ts.map +1 -0
  64. package/tsconfig.build.json +12 -0
  65. package/cjs/index.js +0 -1
  66. package/esm/__tests__/test-utils.js +0 -1
  67. package/esm/create.js +0 -1
  68. package/esm/debug/development-tools.js +0 -1
  69. package/esm/index.js +0 -1
  70. package/esm/select.js +0 -1
  71. package/esm/sqlite/__tests__/create-sqlite.test.js +0 -1
  72. package/esm/sqlite/__tests__/map-deque.test.js +0 -1
  73. package/esm/sqlite/__tests__/table.test.js +0 -1
  74. package/esm/sqlite/__tests__/tokenizer.test.js +0 -1
  75. package/esm/sqlite/__tests__/where.test.js +0 -1
  76. package/esm/sqlite/create-sqlite.js +0 -1
  77. package/esm/sqlite/index.js +0 -1
  78. package/esm/sqlite/table/backend.js +0 -1
  79. package/esm/sqlite/table/bun-backend.js +0 -1
  80. package/esm/sqlite/table/index.js +0 -1
  81. package/esm/sqlite/table/map-deque.js +0 -1
  82. package/esm/sqlite/table/table.js +0 -43
  83. package/esm/sqlite/table/table.types.js +0 -0
  84. package/esm/sqlite/table/tokenizer.js +0 -1
  85. package/esm/sqlite/table/where.js +0 -1
  86. package/esm/sqlite/use-sqlite-count.js +0 -1
  87. package/esm/sqlite/use-sqlite.js +0 -1
  88. package/esm/utils/__tests__/is.test.js +0 -1
  89. package/esm/utils/__tests__/shallow.test.js +0 -1
  90. package/src/sqlite/__tests__/create-sqlite.test.ts +0 -264
  91. package/src/sqlite/__tests__/map-deque.test.ts +0 -61
  92. package/src/sqlite/__tests__/table.test.ts +0 -351
  93. package/src/sqlite/__tests__/tokenizer.test.ts +0 -43
  94. package/src/sqlite/__tests__/use-slite-count.test.tsx +0 -96
  95. package/src/sqlite/__tests__/use-sqlite.more.test.tsx +0 -637
  96. package/src/sqlite/__tests__/use-sqlite.test.tsx +0 -1008
  97. package/src/sqlite/__tests__/where.test.ts +0 -234
  98. package/src/sqlite/create-sqlite.ts +0 -164
  99. package/src/sqlite/index.ts +0 -4
  100. package/src/sqlite/table/backend.ts +0 -21
  101. package/src/sqlite/table/bun-backend.ts +0 -47
  102. package/src/sqlite/table/index.ts +0 -6
  103. package/src/sqlite/table/map-deque.ts +0 -29
  104. package/src/sqlite/table/table.ts +0 -353
  105. package/src/sqlite/table/table.types.ts +0 -129
  106. package/src/sqlite/table/tokenizer.ts +0 -35
  107. package/src/sqlite/table/where.ts +0 -207
  108. package/src/sqlite/use-sqlite-count.ts +0 -69
  109. package/src/sqlite/use-sqlite.ts +0 -250
  110. package/types/__tests__/test-utils.d.ts +0 -25
  111. package/types/debug/development-tools.d.ts +0 -8
  112. package/types/sqlite/create-sqlite.d.ts +0 -31
  113. package/types/sqlite/index.d.ts +0 -4
  114. package/types/sqlite/table/backend.d.ts +0 -20
  115. package/types/sqlite/table/bun-backend.d.ts +0 -6
  116. package/types/sqlite/table/index.d.ts +0 -6
  117. package/types/sqlite/table/map-deque.d.ts +0 -5
  118. package/types/sqlite/table/table.d.ts +0 -21
  119. package/types/sqlite/table/table.types.d.ts +0 -91
  120. package/types/sqlite/table/tokenizer.d.ts +0 -11
  121. package/types/sqlite/table/where.d.ts +0 -37
  122. package/types/sqlite/use-sqlite-count.d.ts +0 -17
  123. package/types/sqlite/use-sqlite.d.ts +0 -39
  124. /package/{src/__tests__ → __tests__}/test-utils.ts +0 -0
  125. /package/{esm → dist/esm}/create-state.js +0 -0
  126. /package/{esm → dist/esm}/scheduler.js +0 -0
  127. /package/{esm → dist/esm}/types.js +0 -0
  128. /package/{esm → dist/esm}/use-value-loadable.js +0 -0
  129. /package/{esm → dist/esm}/use-value.js +0 -0
  130. /package/{esm → dist/esm}/utils/common.js +0 -0
  131. /package/{esm → dist/esm}/utils/create-emitter.js +0 -0
  132. /package/{esm → dist/esm}/utils/id.js +0 -0
  133. /package/{esm → dist/esm}/utils/is.js +0 -0
  134. /package/{esm → dist/esm}/utils/shallow.js +0 -0
@@ -1,207 +0,0 @@
1
- /* eslint-disable unicorn/no-array-callback-reference */
2
- /* eslint-disable unicorn/no-nested-ternary */
3
- /* eslint-disable no-nested-ternary */
4
- /* eslint-disable sonarjs/no-nested-conditional */
5
- /* eslint-disable sonarjs/cognitive-complexity */
6
-
7
- import type { MakeAllFieldAsRequired } from './table.types'
8
-
9
- // -------------------------------------------------------------
10
- // Condition operators for each field
11
- // -------------------------------------------------------------
12
- interface Condition<T> {
13
- readonly is?: T | T[]
14
- readonly isNot?: T | T[]
15
- readonly gt?: T
16
- readonly gte?: T
17
- readonly lt?: T
18
- readonly lte?: T
19
- readonly in?: T[]
20
- readonly notIn?: T[]
21
- readonly like?: T | T[]
22
- readonly fts?: string | string[] // 🔥 NEW
23
- }
24
-
25
- type WhereRaw<T extends Record<string, unknown>> =
26
- | {
27
- [K in keyof T]?: T[K] extends Record<string, unknown>
28
- ? WhereRaw<T[K]> // nested object
29
- : Condition<T[K]> | T[K] | T[K][]
30
- }
31
- | {
32
- readonly AND?: Array<WhereRaw<T>>
33
- readonly OR?: Array<WhereRaw<T>>
34
- readonly NOT?: WhereRaw<T>
35
- }
36
-
37
- // -------------------------------------------------------------
38
- // Where type: recursive object with operators or nested fields
39
- // -------------------------------------------------------------
40
- export type Where<T extends Record<string, unknown>> = WhereRaw<MakeAllFieldAsRequired<T>>
41
- /**
42
- * Inline a value for SQL query, with proper escaping for strings
43
- * @param value The value to inline
44
- * @returns The inlined value as a string
45
- */
46
- function inlineValue(value: unknown): string {
47
- if (typeof value === 'string') return `'${value.replaceAll("'", "''")}'`
48
- if (typeof value === 'number') return value.toString()
49
- if (typeof value === 'boolean') return value ? '1' : '0'
50
- return `'${String(value).replaceAll("'", "''")}'`
51
- }
52
-
53
- /**
54
- * Get SQL expression for a field, with proper casting based on value type
55
- * @param field The field name
56
- * @param value The field value
57
- * @param tableAlias Optional table alias to prefix the field name
58
- * @returns The SQL expression for the field
59
- */
60
- function getFieldExpr(field: string, value: unknown, tableAlias?: string): string {
61
- const prefix = tableAlias ? `${tableAlias}.` : ''
62
- if (field === 'KEY') return `"${prefix}key"`
63
- if (typeof value === 'string') return `CAST(json_extract(${prefix}data, '$.${field}') AS TEXT)`
64
- if (typeof value === 'number') return `CAST(json_extract(${prefix}data, '$.${field}') AS NUMERIC)`
65
- if (typeof value === 'boolean') return `CAST(json_extract(${prefix}data, '$.${field}') AS INTEGER)`
66
- return `json_extract(${prefix}data, '$.${field}')`
67
- }
68
- const OPS_SET: ReadonlySet<string> = new Set(['is', 'isNot', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn', 'like', 'fts'])
69
-
70
- /**
71
- * Flatten a nested Where object into a single-level object with dot-separated keys
72
- * @param object The nested Where object
73
- * @param prefix The prefix for the current recursion level (used internally)
74
- * @returns A flattened object with dot-separated keys
75
- */
76
- function flattenWhere(object: Record<string, unknown>, prefix = ''): Record<string, unknown> {
77
- const result: Record<string, unknown> = {}
78
-
79
- for (const [k, v] of Object.entries(object)) {
80
- if (k === 'AND' || k === 'OR' || k === 'NOT') {
81
- result[k] = v
82
- continue
83
- }
84
-
85
- const path = prefix ? `${prefix}.${k}` : k
86
-
87
- if (v && typeof v === 'object' && !Array.isArray(v) && !Object.keys(v).some((kk) => OPS_SET.has(kk))) {
88
- Object.assign(result, flattenWhere(v as Record<string, unknown>, path))
89
- } else {
90
- result[path] = v
91
- }
92
- }
93
-
94
- return result
95
- }
96
-
97
- /**
98
- * Write SQL WHERE clause from a Where object
99
- * @param where The Where object defining the conditions
100
- * @param tableAlias Optional table alias to prefix field names
101
- * @param tableName Optional table name (required for FTS conditions)
102
- * @returns The SQL WHERE clause string (without the "WHERE" keyword)
103
- */
104
- export function getWhere<T extends Record<string, unknown>>(where: Where<T>, tableAlias?: string, tableName?: string): string {
105
- if (!where || typeof where !== 'object') return ''
106
-
107
- if (where.AND) {
108
- const clauses = Array.isArray(where.AND) ? where.AND.map((w) => getWhere(w, tableAlias, tableName)).filter(Boolean) : []
109
- return clauses.length > 0 ? `(${clauses.join(' AND ')})` : ''
110
- }
111
-
112
- if (where.OR) {
113
- const clauses = Array.isArray(where.OR) ? where.OR.map((w) => getWhere(w, tableAlias, tableName)).filter(Boolean) : []
114
- return clauses.length > 0 ? `(${clauses.join(' OR ')})` : ''
115
- }
116
-
117
- if (where.NOT) {
118
- const clause = getWhere(where.NOT, tableAlias, tableName)
119
- return clause ? `(NOT ${clause})` : ''
120
- }
121
-
122
- const flat = flattenWhere(where as Record<string, unknown>)
123
- let fieldClauses = ''
124
- let anyField = false
125
-
126
- for (const [key, rawValue] of Object.entries(flat)) {
127
- if (rawValue == null) continue
128
-
129
- let cond: Condition<unknown>
130
- if (typeof rawValue !== 'object' || Array.isArray(rawValue)) {
131
- cond = Array.isArray(rawValue) ? { in: rawValue } : { is: rawValue }
132
- } else {
133
- cond = rawValue as Condition<unknown>
134
- }
135
-
136
- for (const opKey of Object.keys(cond)) {
137
- const opValue = cond[opKey as keyof Condition<unknown>]
138
- if (opValue == null) continue
139
-
140
- const values = Array.isArray(opValue) ? opValue : [opValue]
141
- if (values.length === 0) continue
142
-
143
- if (opKey === 'fts') {
144
- if (!tableName) throw new Error('FTS requires tableName for JOIN reference')
145
- const clause = values
146
- .map(
147
- (v) =>
148
- `EXISTS (SELECT 1 FROM ${tableName}_fts f WHERE f.rowid = ${tableAlias ?? tableName}.rowid AND ${tableName}_fts MATCH ${inlineValue(v)})`,
149
- )
150
- .join(' AND ')
151
- fieldClauses += (anyField ? ' AND ' : '') + clause
152
- anyField = true
153
- continue
154
- }
155
-
156
- if (opKey === 'is' || opKey === 'isNot' || opKey === 'in' || opKey === 'notIn') {
157
- const fieldExpr = getFieldExpr(key, values[0], tableAlias)
158
- const inList = values.map(inlineValue).join(',')
159
- const clause =
160
- opKey === 'is'
161
- ? values.length > 1
162
- ? `${fieldExpr} IN (${inList})`
163
- : `${fieldExpr} = ${inlineValue(values[0])}`
164
- : opKey === 'isNot'
165
- ? values.length > 1
166
- ? `${fieldExpr} NOT IN (${inList})`
167
- : `${fieldExpr} <> ${inlineValue(values[0])}`
168
- : opKey === 'in'
169
- ? `${fieldExpr} IN (${inList})`
170
- : `${fieldExpr} NOT IN (${inList})`
171
- fieldClauses += (anyField ? ' AND ' : '') + clause
172
- anyField = true
173
- continue
174
- }
175
-
176
- for (const v of values) {
177
- const fieldExpr = getFieldExpr(key, v, tableAlias)
178
- const clause =
179
- opKey === 'gt'
180
- ? `${fieldExpr} > ${inlineValue(v)}`
181
- : opKey === 'gte'
182
- ? `${fieldExpr} >= ${inlineValue(v)}`
183
- : opKey === 'lt'
184
- ? `${fieldExpr} < ${inlineValue(v)}`
185
- : opKey === 'lte'
186
- ? `${fieldExpr} <= ${inlineValue(v)}`
187
- : `${fieldExpr} LIKE ${inlineValue(v)}`
188
- fieldClauses += (anyField ? ' AND ' : '') + clause
189
- anyField = true
190
- }
191
- }
192
- }
193
-
194
- return anyField ? `(${fieldClauses})` : ''
195
- }
196
-
197
- /**
198
- * Get SQL WHERE clause from a Where object
199
- * @param where The Where object defining the conditions
200
- * @param tableName Optional table name (required for FTS conditions)
201
- * @returns The SQL WHERE clause string (without the "WHERE" keyword)
202
- */
203
- export function getWhereQuery<T extends Record<string, unknown>>(where?: Where<T>, tableName?: string): string {
204
- if (!where) return ''
205
- const clause = getWhere(where, undefined, tableName)
206
- return clause ? `WHERE ${clause}` : ''
207
- }
@@ -1,69 +0,0 @@
1
- import { useCallback, useEffect, useLayoutEffect, useReducer, useRef, type DependencyList } from 'react'
2
- import type { SyncTable } from './create-sqlite'
3
- import type { DocType } from './table/table.types'
4
- import type { Where } from './table/where'
5
-
6
- /**
7
- * A React hook to count the number of items in a SyncTable reactively.
8
- * It updates the count when items are inserted or deleted, but ignores updates.
9
- * Supports filtering the count using a `where` clause.
10
- * @param state The SyncTable instance to observe.
11
- * @param options Optional filtering options.
12
- * @param options.where A `where` clause to filter the count.
13
- * @param deps Dependency list to control when to re-run the effect.
14
- * @returns The current count of items in the table.
15
- */
16
- export function useSqliteCount<Document extends DocType>(
17
- state: SyncTable<Document>,
18
- options: { where?: Where<Document> } = {},
19
- deps: DependencyList = [],
20
- ): number {
21
- const countRef = useRef(0)
22
- const [, rerender] = useReducer((c: number) => c + 1, 0)
23
-
24
- const updateCount = useCallback(async () => {
25
- const newCount = await state.count(options)
26
- countRef.current = newCount
27
- rerender()
28
- // eslint-disable-next-line react-hooks/exhaustive-deps
29
- }, deps)
30
-
31
- useEffect(() => {
32
- updateCount()
33
- // eslint-disable-next-line react-hooks/exhaustive-deps
34
- }, deps)
35
-
36
- useLayoutEffect(() => {
37
- const unsubscribe = state.subscribe((item) => {
38
- const { mutations, removedAll } = item
39
- if (removedAll) {
40
- countRef.current = 0
41
- rerender()
42
- return
43
- }
44
- if (!mutations) {
45
- return
46
- }
47
-
48
- let shouldUpdate = false
49
- for (const mutation of mutations) {
50
- const { op } = mutation
51
- if (op === 'insert' || op === 'delete') {
52
- shouldUpdate = true
53
- break
54
- }
55
- }
56
-
57
- if (shouldUpdate) {
58
- updateCount()
59
- }
60
- })
61
-
62
- return () => {
63
- unsubscribe()
64
- }
65
- // eslint-disable-next-line react-hooks/exhaustive-deps
66
- }, [state])
67
-
68
- return countRef.current
69
- }
@@ -1,250 +0,0 @@
1
- /* eslint-disable sonarjs/cognitive-complexity */
2
- import { useCallback, useLayoutEffect, useReducer, useRef, useState, type DependencyList } from 'react'
3
- import type { SyncTable } from './create-sqlite'
4
- import type { DocType, Key, SqlSeachOptions } from './table/table.types'
5
- import { DEFAULT_PAGE_SIZE } from './table'
6
- import { shallow } from '../utils/shallow'
7
- const MAX_ITERATIONS = 10_000
8
-
9
- /**
10
- * Shallow compare two dependency arrays
11
- * @param previousDeps Previous deps array
12
- * @param nextDeps Next deps array
13
- * @returns True if arrays have same length and all items are strictly equal
14
- */
15
- function shallowEqualDeps(previousDeps: DependencyList, nextDeps: DependencyList): boolean {
16
- if (previousDeps.length !== nextDeps.length) return false
17
- for (const [index, previousDep] of previousDeps.entries()) {
18
- if (!Object.is(previousDep, nextDeps[index])) return false
19
- }
20
- return true
21
- }
22
-
23
- export interface SqLiteActions {
24
- /**
25
- * Load the next page of results and return if isDone to show more results.
26
- * @returns isDone: boolean
27
- */
28
- readonly nextPage: () => Promise<boolean>
29
- /**
30
- * Reset the pagination and load the first page of results.
31
- * @returns void
32
- */
33
- readonly reset: () => Promise<void>
34
- /**
35
- * Map of document keys to their index in the results array.
36
- */
37
- readonly keysIndex: Map<Key, number>
38
- /**
39
- * True when deps changed but fresh data hasn't loaded yet.
40
- * Use this to show stale/dimmed UI while new results are loading.
41
- */
42
- readonly isStale: boolean
43
- }
44
-
45
- export interface UseSearchOptions<Document extends DocType, Selected = Document> extends SqlSeachOptions<Document> {
46
- /**
47
- * Naive projection. Prefer specialized queries for heavy fan-out graphs.
48
- */
49
- readonly select?: (document: Document) => Selected
50
- }
51
-
52
- /**
53
- * A React hook to perform paginated searches on a SyncTable and reactively update the results.
54
- * It supports pagination, resetting the search, and selecting specific fields from the documents.
55
- * @param state The SyncTable instance to perform searches on.
56
- * @param options Options to customize the search behavior, including pagination size and selection function.
57
- * @param deps Dependency list to control when to re-run the search and reset the iterator.
58
- * @returns A tuple containing the current list of results and an object with actions to manage pagination and resetting.
59
- */
60
- export function useSqliteValue<Document extends DocType, Selected = Document>(
61
- state: SyncTable<Document>,
62
- options: UseSearchOptions<Document, Selected> = {},
63
- deps: DependencyList = [],
64
- ): [(undefined extends Selected ? Document[] : Selected[]) | null, SqLiteActions] {
65
- const { select, pageSize = DEFAULT_PAGE_SIZE } = options
66
-
67
- const itemsRef = useRef<null | (Document | Selected)[]>(null)
68
- const [, rerender] = useReducer((c: number) => c + 1, 0)
69
- const keysIndex = useRef(new Map<Key, number>())
70
- const iteratorRef = useRef<AsyncIterableIterator<{ doc: Document; meta: { key: Key } }> | null>(null)
71
-
72
- // Track "settled" deps - the deps value when data last finished loading
73
- // isStale is derived: true when current deps differ from settled deps
74
- const [settledDeps, setSettledDeps] = useState<DependencyList | null>(null)
75
- const isStale = settledDeps === null || !shallowEqualDeps(settledDeps, deps)
76
-
77
- const updateIterator = useCallback(() => {
78
- // eslint-disable-next-line sonarjs/no-unused-vars
79
- const { select: _ignore, ...resetOptions } = options
80
- iteratorRef.current = state.search({ select: (doc, meta) => ({ doc, meta }), ...resetOptions })
81
- // eslint-disable-next-line react-hooks/exhaustive-deps
82
- }, [state, ...deps])
83
-
84
- const resetDataAndUpdateIterator = useCallback(() => {
85
- itemsRef.current = []
86
- keysIndex.current.clear()
87
- updateIterator()
88
- }, [updateIterator])
89
-
90
- const fillNextPage = useCallback(async (shouldReset: boolean) => {
91
- if (itemsRef.current === null) {
92
- itemsRef.current = []
93
- }
94
- if (shouldReset === true) {
95
- resetDataAndUpdateIterator()
96
- }
97
-
98
- const { current: iterator } = iteratorRef
99
- if (!iterator) {
100
- return true
101
- }
102
- let isDone = false
103
-
104
- for (let index = 0; index < pageSize; index++) {
105
- const result = await iterator.next()
106
- if (result.done) {
107
- iteratorRef.current = null
108
- isDone = true
109
- break
110
- }
111
- if (keysIndex.current.has(result.value.meta.key)) {
112
- continue
113
- }
114
- itemsRef.current.push(select ? select(result.value.doc) : (result.value.doc as unknown as Selected))
115
- keysIndex.current.set(result.value.meta.key, itemsRef.current.length - 1)
116
- }
117
- itemsRef.current = [...itemsRef.current]
118
- return isDone
119
- // eslint-disable-next-line react-hooks/exhaustive-deps
120
- }, [])
121
-
122
- const nextPage = useCallback(async () => {
123
- const isDone = await fillNextPage(false)
124
- rerender()
125
- return isDone
126
- }, [fillNextPage])
127
-
128
- useLayoutEffect(() => {
129
- const unsubscribe = state.subscribe(async (item) => {
130
- const { mutations, removedAll } = item
131
- if (removedAll) {
132
- resetDataAndUpdateIterator()
133
- }
134
- if (!mutations) {
135
- return
136
- }
137
-
138
- const oldLength = itemsRef.current?.length ?? 0
139
- let newLength = oldLength
140
- let hasUpdate = false
141
- const removeIndexes = new Set<number>()
142
- for (const mutation of mutations) {
143
- const { key, op, document } = mutation
144
- switch (op) {
145
- case 'insert': {
146
- newLength += 1
147
- break
148
- }
149
- case 'delete': {
150
- if (itemsRef.current && itemsRef.current.length > 0 && keysIndex.current.has(key)) {
151
- const index = keysIndex.current.get(key)
152
- if (index === undefined) break
153
- removeIndexes.add(index)
154
- hasUpdate = true
155
- }
156
- break
157
- }
158
- case 'update': {
159
- if (keysIndex.current.has(key)) {
160
- const index = keysIndex.current.get(key)
161
- if (index !== undefined && itemsRef.current) {
162
- const newItem = select ? select(document as Document) : (document as unknown as Selected)
163
- const previousItem = itemsRef.current[index]
164
-
165
- // 🆕 Only update & rerender if shallow comparison fails
166
- if (!shallow(previousItem, newItem)) {
167
- itemsRef.current[index] = newItem
168
- itemsRef.current = [...itemsRef.current]
169
- hasUpdate = true
170
- }
171
- }
172
- } else {
173
- // Handle updates to non-visible items
174
- const updatedItem = await state.get(key, select)
175
- if (updatedItem) {
176
- itemsRef.current = [...(itemsRef.current ?? []), updatedItem]
177
- keysIndex.current.set(key, itemsRef.current.length - 1)
178
- hasUpdate = true
179
- }
180
- }
181
- break
182
- }
183
- }
184
- }
185
-
186
- if (removeIndexes.size > 0 && itemsRef.current && itemsRef.current.length > 0) {
187
- const newIndex = new Map<Key, number>()
188
- itemsRef.current = itemsRef.current?.filter((_, index) => {
189
- return !removeIndexes.has(index)
190
- })
191
- let newIdx = 0
192
- for (const [key, index] of keysIndex.current) {
193
- if (removeIndexes.has(index)) {
194
- continue
195
- }
196
- newIndex.set(key, newIdx)
197
- newIdx++
198
- }
199
- keysIndex.current = newIndex
200
- }
201
-
202
- const isLengthChanged = oldLength !== newLength
203
- const isChanged = isLengthChanged || hasUpdate
204
- if (!isChanged) return
205
- if (isLengthChanged) {
206
- await fillNextPage(true)
207
- let iterations = 0
208
- while ((itemsRef.current?.length ?? 0) < newLength && iterations < MAX_ITERATIONS) {
209
- await fillNextPage(false)
210
- iterations++
211
- }
212
- if (iterations === MAX_ITERATIONS) {
213
- // Optionally log a warning to help with debugging
214
- // eslint-disable-next-line no-console
215
- console.warn('Reached maximum iterations in fillNextPage loop. Possible duplicate or data issue.')
216
- }
217
- }
218
- rerender()
219
- })
220
- return () => {
221
- unsubscribe()
222
- }
223
- // eslint-disable-next-line react-hooks/exhaustive-deps
224
- }, [state])
225
-
226
- useLayoutEffect(() => {
227
- // Capture current deps for this effect invocation
228
- const currentDeps = deps
229
- resetDataAndUpdateIterator()
230
- nextPage().then(() => {
231
- // Mark these deps as settled when data finishes loading
232
- setSettledDeps(currentDeps)
233
- })
234
- // eslint-disable-next-line react-hooks/exhaustive-deps
235
- }, deps)
236
-
237
- const resetCb = useCallback(async () => {
238
- // Set settledDeps to null to make isStale=true during reset
239
- setSettledDeps(null)
240
- resetDataAndUpdateIterator()
241
- await nextPage()
242
- // After data loads, mark current deps as settled
243
- setSettledDeps(deps)
244
- }, [nextPage, resetDataAndUpdateIterator, deps])
245
-
246
- return [itemsRef.current, { nextPage, reset: resetCb, keysIndex: keysIndex.current, isStale }] as [
247
- (undefined extends Selected ? Document[] : Selected[]) | null,
248
- SqLiteActions,
249
- ]
250
- }
@@ -1,25 +0,0 @@
1
- import { Component } from 'react';
2
- /**
3
- * Create a promise that resolves after a specified time
4
- * @param time Time in ms to wait
5
- * @returns A promise that resolves after the specified time
6
- */
7
- export declare function longPromise(time?: number): Promise<number>;
8
- export declare class ErrorBoundary extends Component<{
9
- fallback: React.ReactNode;
10
- children: React.ReactNode;
11
- }, {
12
- hasError: boolean;
13
- error: Error | null;
14
- }> {
15
- constructor(props: {
16
- fallback: React.ReactNode;
17
- children: React.ReactNode;
18
- });
19
- static getDerivedStateFromError(error: Error): {
20
- hasError: boolean;
21
- error: Error;
22
- };
23
- componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
24
- render(): import("react").ReactNode;
25
- }
@@ -1,8 +0,0 @@
1
- import type { GetState, State } from '../types';
2
- export type StateType = 'state' | 'derived';
3
- /**
4
- * Subscribe a state to development tools if available
5
- * @param state The state to subscribe
6
- * @returns A function to unsubscribe from development tools
7
- */
8
- export declare function subscribeToDevelopmentTools<T>(state: State<T> | GetState<T>): (() => void) | undefined;
@@ -1,31 +0,0 @@
1
- import type { Backend } from './table';
2
- import type { DbOptions, DocType, DotPath, GetFieldType, GroupByOptions, GroupByResult, Key, MutationResult, SearchOptions } from './table/table.types';
3
- import type { Where } from './table/where';
4
- export interface CreateSqliteOptions<Document extends DocType> extends Omit<DbOptions<Document>, 'backend'> {
5
- readonly backend: Backend | Promise<Backend>;
6
- }
7
- export interface MutationItems<Doc> {
8
- mutations?: MutationResult<Doc>[];
9
- removedAll?: boolean;
10
- }
11
- export interface SyncTable<Document extends DocType> {
12
- readonly subscribe: (listener: (mutation: MutationItems<Document>) => void) => () => void;
13
- readonly set: (document: Document) => Promise<MutationResult<Document>>;
14
- readonly batchSet: (documents: Document[]) => Promise<MutationResult<Document>[]>;
15
- readonly batchDelete: (keys: Key[]) => Promise<MutationResult<Document>[]>;
16
- readonly get: <Selected = Document>(key: Key, selector?: (document: Document) => Selected) => Promise<Selected | undefined>;
17
- readonly delete: (key: Key) => Promise<MutationResult<Document> | undefined>;
18
- readonly search: <Selected = Document>(options?: SearchOptions<Document, Selected>) => AsyncIterableIterator<Selected>;
19
- readonly count: (options?: {
20
- where?: Where<Document>;
21
- }) => Promise<number>;
22
- readonly deleteBy: (where: Where<Document>) => Promise<MutationResult<Document>[]>;
23
- readonly clear: () => Promise<void>;
24
- readonly groupBy: <Field extends DotPath<Document>>(field: Field, options?: GroupByOptions<Document>) => Promise<Array<GroupByResult<GetFieldType<Document, Field>>>>;
25
- }
26
- /**
27
- * Create a SyncTable that wraps a Table and provides reactive capabilities
28
- * @param options Options to create the SyncTable, including the backend and table name
29
- * @returns A SyncTable instance with methods to interact with the underlying Table and manage reactive searches
30
- */
31
- export declare function createSqliteState<Document extends DocType>(options: CreateSqliteOptions<Document>): SyncTable<Document>;
@@ -1,4 +0,0 @@
1
- export * from './create-sqlite';
2
- export * from './table';
3
- export * from './use-sqlite-count';
4
- export * from './use-sqlite';
@@ -1,20 +0,0 @@
1
- export declare const IN_MEMORY_DB = ":memory:";
2
- export interface QueryResult {
3
- /** The number of rows affected by the query. */
4
- rowsAffected: number;
5
- /**
6
- * The last inserted `id`.
7
- *
8
- * This value is not set for Postgres databases. If the
9
- * last inserted id is required on Postgres, the `select` function
10
- * must be used, with a `RETURNING` clause
11
- * (`INSERT INTO todos (title) VALUES ($1) RETURNING id`).
12
- */
13
- lastInsertId?: number;
14
- }
15
- export interface Backend {
16
- execute: (query: string, bindValues?: unknown[]) => Promise<QueryResult>;
17
- select: <T>(query: string, bindValues?: unknown[]) => Promise<T>;
18
- transaction: (callback: (tx: Backend) => Promise<void>) => Promise<void>;
19
- path: string;
20
- }
@@ -1,6 +0,0 @@
1
- import type { Backend } from './backend';
2
- /**
3
- * Create an in-memory SQLite backend using Bun's SQLite implementation
4
- * @returns A Backend instance for in-memory SQLite operations
5
- */
6
- export declare function bunMemoryBackend(): Backend;
@@ -1,6 +0,0 @@
1
- export * from './backend';
2
- export * from './table.types';
3
- export * from './where';
4
- export * from './table';
5
- export * from './map-deque';
6
- export * from './tokenizer';
@@ -1,5 +0,0 @@
1
- export declare class MapDeque<K, V> extends Map<K, V> {
2
- private maxSize;
3
- constructor(maxSize: number, entries?: ReadonlyArray<readonly [K, V]> | null);
4
- set(key: K, value: V): this;
5
- }
@@ -1,21 +0,0 @@
1
- import type { Table, DbOptions, DocType } from './table.types';
2
- export declare const DEFAULT_PAGE_SIZE = 100;
3
- /**
4
- * Convert a dot-separated path to a JSON path
5
- * @param dot The dot-separated path string
6
- * @returns The JSON path string
7
- */
8
- export declare function toJsonPath(dot: string): string;
9
- /**
10
- * Get a nested value from an object using a dot-separated path
11
- * @param object The object to retrieve the value from
12
- * @param path The dot-separated path string
13
- * @returns The value at the specified path, or undefined if not found
14
- */
15
- export declare function getByPath<T extends object>(object: T, path: string): unknown;
16
- /**
17
- * Create and initialize a table in the database with the specified options
18
- * @param options The options for creating the table, including table name, indexes, backend, and key
19
- * @returns A promise that resolves to the created Table instance
20
- */
21
- export declare function createTable<Document extends DocType>(options: DbOptions<Document>): Promise<Table<Document>>;