@livestore/livestore 0.0.13 → 0.0.14

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 (205) hide show
  1. package/README.md +18 -21
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/QueryCache.d.ts +1 -1
  4. package/dist/QueryCache.d.ts.map +1 -1
  5. package/dist/QueryCache.js.map +1 -1
  6. package/dist/__tests__/react/fixture.d.ts +5 -4
  7. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  8. package/dist/__tests__/react/fixture.js +13 -14
  9. package/dist/__tests__/react/fixture.js.map +1 -1
  10. package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
  11. package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
  12. package/dist/__tests__/react/useComponentState.test.js +68 -0
  13. package/dist/__tests__/react/useComponentState.test.js.map +1 -0
  14. package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
  15. package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
  16. package/dist/__tests__/react/useLQuery.test.js +38 -0
  17. package/dist/__tests__/react/useLQuery.test.js.map +1 -0
  18. package/dist/__tests__/react/useLiveStoreComponent.test.js +4 -9
  19. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -1
  20. package/dist/__tests__/react/useQuery.test.d.ts +2 -0
  21. package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
  22. package/dist/__tests__/react/useQuery.test.js +33 -0
  23. package/dist/__tests__/react/useQuery.test.js.map +1 -0
  24. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
  25. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
  26. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
  27. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
  28. package/dist/__tests__/reactive.test.js +168 -95
  29. package/dist/__tests__/reactive.test.js.map +1 -1
  30. package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
  31. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
  32. package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
  33. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
  34. package/dist/effect/LiveStore.d.ts +3 -9
  35. package/dist/effect/LiveStore.d.ts.map +1 -1
  36. package/dist/effect/LiveStore.js +11 -7
  37. package/dist/effect/LiveStore.js.map +1 -1
  38. package/dist/inMemoryDatabase.d.ts +17 -21
  39. package/dist/inMemoryDatabase.d.ts.map +1 -1
  40. package/dist/inMemoryDatabase.js +2 -9
  41. package/dist/inMemoryDatabase.js.map +1 -1
  42. package/dist/index.d.ts +9 -7
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +7 -3
  45. package/dist/index.js.map +1 -1
  46. package/dist/migrations.d.ts +7 -0
  47. package/dist/migrations.d.ts.map +1 -1
  48. package/dist/migrations.js +18 -13
  49. package/dist/migrations.js.map +1 -1
  50. package/dist/react/LiveStoreProvider.d.ts +1 -3
  51. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  52. package/dist/react/LiveStoreProvider.js +13 -10
  53. package/dist/react/LiveStoreProvider.js.map +1 -1
  54. package/dist/react/index.d.ts +4 -4
  55. package/dist/react/index.d.ts.map +1 -1
  56. package/dist/react/index.js +3 -3
  57. package/dist/react/index.js.map +1 -1
  58. package/dist/react/useComponentState.d.ts +50 -0
  59. package/dist/react/useComponentState.d.ts.map +1 -0
  60. package/dist/react/useComponentState.js +248 -0
  61. package/dist/react/useComponentState.js.map +1 -0
  62. package/dist/react/useGlobalQuery.d.ts +2 -2
  63. package/dist/react/useGlobalQuery.d.ts.map +1 -1
  64. package/dist/react/useGlobalQuery.js +5 -2
  65. package/dist/react/useGlobalQuery.js.map +1 -1
  66. package/dist/react/useGraphQL.d.ts +5 -3
  67. package/dist/react/useGraphQL.d.ts.map +1 -1
  68. package/dist/react/useGraphQL.js +27 -7
  69. package/dist/react/useGraphQL.js.map +1 -1
  70. package/dist/react/useLiveStoreComponent.d.ts +14 -14
  71. package/dist/react/useLiveStoreComponent.d.ts.map +1 -1
  72. package/dist/react/useLiveStoreComponent.js +151 -91
  73. package/dist/react/useLiveStoreComponent.js.map +1 -1
  74. package/dist/react/useQuery.d.ts +3 -0
  75. package/dist/react/useQuery.d.ts.map +1 -0
  76. package/dist/react/useQuery.js +42 -0
  77. package/dist/react/useQuery.js.map +1 -0
  78. package/dist/react/useTemporaryQuery.d.ts +8 -0
  79. package/dist/react/useTemporaryQuery.d.ts.map +1 -0
  80. package/dist/react/useTemporaryQuery.js +17 -0
  81. package/dist/react/useTemporaryQuery.js.map +1 -0
  82. package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
  83. package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
  84. package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
  85. package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
  86. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
  87. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
  88. package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
  89. package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
  90. package/dist/reactive.d.ts +42 -48
  91. package/dist/reactive.d.ts.map +1 -1
  92. package/dist/reactive.js +293 -186
  93. package/dist/reactive.js.map +1 -1
  94. package/dist/reactiveQueries/base-class.d.ts +28 -20
  95. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  96. package/dist/reactiveQueries/base-class.js +25 -17
  97. package/dist/reactiveQueries/base-class.js.map +1 -1
  98. package/dist/reactiveQueries/graph.d.ts +10 -0
  99. package/dist/reactiveQueries/graph.d.ts.map +1 -0
  100. package/dist/reactiveQueries/graph.js +6 -0
  101. package/dist/reactiveQueries/graph.js.map +1 -0
  102. package/dist/reactiveQueries/graphql.d.ts +35 -18
  103. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  104. package/dist/reactiveQueries/graphql.js +91 -10
  105. package/dist/reactiveQueries/graphql.js.map +1 -1
  106. package/dist/reactiveQueries/js.d.ts +17 -13
  107. package/dist/reactiveQueries/js.d.ts.map +1 -1
  108. package/dist/reactiveQueries/js.js +31 -8
  109. package/dist/reactiveQueries/js.js.map +1 -1
  110. package/dist/reactiveQueries/sql.d.ts +22 -18
  111. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  112. package/dist/reactiveQueries/sql.js +81 -16
  113. package/dist/reactiveQueries/sql.js.map +1 -1
  114. package/dist/schema.d.ts +0 -2
  115. package/dist/schema.d.ts.map +1 -1
  116. package/dist/schema.js +3 -6
  117. package/dist/schema.js.map +1 -1
  118. package/dist/storage/in-memory/index.d.ts +2 -2
  119. package/dist/storage/in-memory/index.d.ts.map +1 -1
  120. package/dist/storage/in-memory/index.js.map +1 -1
  121. package/dist/storage/index.d.ts +2 -2
  122. package/dist/storage/index.d.ts.map +1 -1
  123. package/dist/storage/tauri/index.d.ts +2 -2
  124. package/dist/storage/tauri/index.d.ts.map +1 -1
  125. package/dist/storage/tauri/index.js.map +1 -1
  126. package/dist/storage/web-worker/index.d.ts +4 -4
  127. package/dist/storage/web-worker/index.d.ts.map +1 -1
  128. package/dist/storage/web-worker/index.js +3 -5
  129. package/dist/storage/web-worker/index.js.map +1 -1
  130. package/dist/storage/web-worker/worker.js +2 -2
  131. package/dist/storage/web-worker/worker.js.map +1 -1
  132. package/dist/store.d.ts +19 -52
  133. package/dist/store.d.ts.map +1 -1
  134. package/dist/store.js +323 -266
  135. package/dist/store.js.map +1 -1
  136. package/dist/util.d.ts +3 -1
  137. package/dist/util.d.ts.map +1 -1
  138. package/dist/util.js +2 -0
  139. package/dist/util.js.map +1 -1
  140. package/package.json +2 -1
  141. package/src/QueryCache.ts +1 -1
  142. package/src/__tests__/react/fixture.tsx +21 -16
  143. package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
  144. package/src/__tests__/react/useQuery.test.tsx +48 -0
  145. package/src/__tests__/react/utils/extractStackInfoFromStackTrace.test.ts +40 -0
  146. package/src/__tests__/reactive.test.ts +194 -142
  147. package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
  148. package/src/effect/LiveStore.ts +14 -18
  149. package/src/inMemoryDatabase.ts +22 -30
  150. package/src/index.ts +8 -6
  151. package/src/migrations.ts +39 -21
  152. package/src/react/LiveStoreProvider.tsx +13 -16
  153. package/src/react/index.ts +4 -8
  154. package/src/react/{useLiveStoreComponent.ts → useComponentState.ts} +98 -230
  155. package/src/react/useQuery.ts +58 -0
  156. package/src/react/useTemporaryQuery.ts +21 -0
  157. package/src/react/utils/extractStackInfoFromStackTrace.ts +47 -0
  158. package/src/reactive.ts +386 -267
  159. package/src/reactiveQueries/base-class.ts +61 -39
  160. package/src/reactiveQueries/graph.ts +15 -0
  161. package/src/reactiveQueries/graphql.ts +147 -31
  162. package/src/reactiveQueries/js.ts +54 -21
  163. package/src/reactiveQueries/sql.ts +128 -37
  164. package/src/schema.ts +2 -5
  165. package/src/storage/in-memory/index.ts +2 -2
  166. package/src/storage/index.ts +2 -2
  167. package/src/storage/tauri/index.ts +2 -2
  168. package/src/storage/web-worker/index.ts +6 -8
  169. package/src/storage/web-worker/worker.ts +2 -2
  170. package/src/store.ts +394 -418
  171. package/src/util.ts +8 -2
  172. package/dist/backends/base.d.ts +0 -13
  173. package/dist/backends/base.d.ts.map +0 -1
  174. package/dist/backends/base.js +0 -53
  175. package/dist/backends/base.js.map +0 -1
  176. package/dist/backends/in-memory/index.d.ts +0 -22
  177. package/dist/backends/in-memory/index.d.ts.map +0 -1
  178. package/dist/backends/in-memory/index.js +0 -45
  179. package/dist/backends/in-memory/index.js.map +0 -1
  180. package/dist/backends/index.d.ts +0 -41
  181. package/dist/backends/index.d.ts.map +0 -1
  182. package/dist/backends/index.js +0 -16
  183. package/dist/backends/index.js.map +0 -1
  184. package/dist/backends/tauri/index.d.ts +0 -21
  185. package/dist/backends/tauri/index.d.ts.map +0 -1
  186. package/dist/backends/tauri/index.js +0 -48
  187. package/dist/backends/tauri/index.js.map +0 -1
  188. package/dist/backends/utils/idb.d.ts +0 -10
  189. package/dist/backends/utils/idb.d.ts.map +0 -1
  190. package/dist/backends/utils/idb.js +0 -58
  191. package/dist/backends/utils/idb.js.map +0 -1
  192. package/dist/backends/web-worker/index.d.ts +0 -26
  193. package/dist/backends/web-worker/index.d.ts.map +0 -1
  194. package/dist/backends/web-worker/index.js +0 -63
  195. package/dist/backends/web-worker/index.js.map +0 -1
  196. package/dist/backends/web-worker/worker.d.ts +0 -17
  197. package/dist/backends/web-worker/worker.d.ts.map +0 -1
  198. package/dist/backends/web-worker/worker.js +0 -139
  199. package/dist/backends/web-worker/worker.js.map +0 -1
  200. package/dist/storage/base.d.ts +0 -10
  201. package/dist/storage/base.d.ts.map +0 -1
  202. package/dist/storage/base.js +0 -14
  203. package/dist/storage/base.js.map +0 -1
  204. package/src/react/useGlobalQuery.ts +0 -37
  205. package/src/react/useGraphQL.ts +0 -112
@@ -0,0 +1,372 @@
1
+ import * as otel from '@opentelemetry/api'
2
+ import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
3
+ import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
4
+ import { describe, expect, it } from 'vitest'
5
+
6
+ import { queryJS, querySQL, sql } from '../../index.js'
7
+ import { makeTodoMvc } from '../react/fixture.js'
8
+
9
+ describe('otel', () => {
10
+ let cachedProvider: BasicTracerProvider | undefined
11
+
12
+ const makeQuery = async () => {
13
+ const exporter = new InMemorySpanExporter()
14
+
15
+ const provider = cachedProvider ?? new BasicTracerProvider()
16
+ cachedProvider = provider
17
+ provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
18
+ provider.register()
19
+
20
+ const tracer = otel.trace.getTracer('test')
21
+
22
+ const span = tracer.startSpan('test')
23
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
24
+
25
+ const { store } = await makeTodoMvc({ otelTracer: tracer, otelContext })
26
+
27
+ return { store, tracer, exporter, span, provider }
28
+ }
29
+
30
+ it('otel', async () => {
31
+ const { store, exporter, span } = await makeQuery()
32
+
33
+ const query = querySQL(`select * from todos`, { queriedTables: ['todos'] })
34
+ expect(query.run()).toMatchInlineSnapshot('[]')
35
+
36
+ store.applyEvent('RawSql', {
37
+ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
38
+ bindValues: {},
39
+ writeTables: ['todos'],
40
+ })
41
+
42
+ expect(query.run()).toMatchInlineSnapshot(`
43
+ [
44
+ {
45
+ "completed": 0,
46
+ "id": "t1",
47
+ "text": "buy milk",
48
+ },
49
+ ]
50
+ `)
51
+
52
+ store.destroy()
53
+ query.destroy()
54
+ span.end()
55
+
56
+ expect(getSimplifiedRootSpan(exporter)).toMatchInlineSnapshot(`
57
+ {
58
+ "_name": "test",
59
+ "children": [
60
+ {
61
+ "_name": "livestore.in-memory-db:execute",
62
+ "attributes": {
63
+ "sql.query": "
64
+ PRAGMA page_size=32768;
65
+ PRAGMA cache_size=10000;
66
+ PRAGMA journal_mode='MEMORY'; -- we don't flush to disk before committing a write
67
+ PRAGMA synchronous='OFF';
68
+ PRAGMA temp_store='MEMORY';
69
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
70
+ ",
71
+ },
72
+ },
73
+ {
74
+ "_name": "sql-in-memory-select",
75
+ "attributes": {
76
+ "sql.cached": false,
77
+ "sql.query": "SELECT * FROM __livestore_schema",
78
+ "sql.rowsCount": 0,
79
+ },
80
+ },
81
+ {
82
+ "_name": "livestore.in-memory-db:execute",
83
+ "attributes": {
84
+ "sql.query": "INSERT OR IGNORE INTO app (id, newTodoText, filter) VALUES ('static', '', 'all');",
85
+ },
86
+ },
87
+ {
88
+ "_name": "LiveStore:applyEvents",
89
+ "children": [
90
+ {
91
+ "_name": "LiveStore:applyEvent",
92
+ "children": [
93
+ {
94
+ "_name": "LiveStore:applyEventWithoutRefresh",
95
+ "attributes": {
96
+ "livestore.actionType": "RawSql",
97
+ "livestore.args": "{
98
+ \\"sql\\": \\"INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);\\",
99
+ \\"bindValues\\": {},
100
+ \\"writeTables\\": [
101
+ \\"todos\\"
102
+ ]
103
+ }",
104
+ },
105
+ "children": [
106
+ {
107
+ "_name": "livestore.in-memory-db:execute",
108
+ "attributes": {
109
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
110
+ },
111
+ },
112
+ ],
113
+ },
114
+ ],
115
+ },
116
+ ],
117
+ },
118
+ {
119
+ "_name": "LiveStore:queries",
120
+ "children": [
121
+ {
122
+ "_name": "sql:select * from todos",
123
+ "attributes": {
124
+ "sql.query": "select * from todos",
125
+ "sql.rowsCount": 0,
126
+ },
127
+ "children": [
128
+ {
129
+ "_name": "sql-in-memory-select",
130
+ "attributes": {
131
+ "sql.cached": false,
132
+ "sql.query": "select * from todos",
133
+ "sql.rowsCount": 0,
134
+ },
135
+ },
136
+ ],
137
+ },
138
+ {
139
+ "_name": "sql:select * from todos",
140
+ "attributes": {
141
+ "sql.query": "select * from todos",
142
+ "sql.rowsCount": 1,
143
+ },
144
+ "children": [
145
+ {
146
+ "_name": "sql-in-memory-select",
147
+ "attributes": {
148
+ "sql.cached": false,
149
+ "sql.query": "select * from todos",
150
+ "sql.rowsCount": 1,
151
+ },
152
+ },
153
+ ],
154
+ },
155
+ ],
156
+ },
157
+ ],
158
+ }
159
+ `)
160
+ })
161
+
162
+ it('with thunks', async () => {
163
+ const { store, exporter, span } = await makeQuery()
164
+
165
+ const defaultTodo = { id: '', text: '', completed: 0 }
166
+
167
+ const filter = queryJS(() => `where completed = 0`, { label: 'where-filter' })
168
+ const query = querySQL((get) => `select * from todos ${get(filter)}`, {
169
+ queriedTables: ['todos'],
170
+ label: 'all todos',
171
+ }).getFirstRow({
172
+ defaultValue: defaultTodo,
173
+ })
174
+
175
+ expect(query.run()).toMatchInlineSnapshot(`
176
+ {
177
+ "completed": 0,
178
+ "id": "",
179
+ "text": "",
180
+ }
181
+ `)
182
+
183
+ store.applyEvent('RawSql', {
184
+ sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
185
+ bindValues: {},
186
+ writeTables: ['todos'],
187
+ })
188
+
189
+ expect(query.run()).toMatchInlineSnapshot(`
190
+ {
191
+ "completed": 0,
192
+ "id": "t1",
193
+ "text": "buy milk",
194
+ }
195
+ `)
196
+
197
+ store.destroy()
198
+ query.destroy()
199
+ span.end()
200
+
201
+ expect(getSimplifiedRootSpan(exporter)).toMatchInlineSnapshot(`
202
+ {
203
+ "_name": "test",
204
+ "children": [
205
+ {
206
+ "_name": "livestore.in-memory-db:execute",
207
+ "attributes": {
208
+ "sql.query": "
209
+ PRAGMA page_size=32768;
210
+ PRAGMA cache_size=10000;
211
+ PRAGMA journal_mode='MEMORY'; -- we don't flush to disk before committing a write
212
+ PRAGMA synchronous='OFF';
213
+ PRAGMA temp_store='MEMORY';
214
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
215
+ ",
216
+ },
217
+ },
218
+ {
219
+ "_name": "sql-in-memory-select",
220
+ "attributes": {
221
+ "sql.cached": false,
222
+ "sql.query": "SELECT * FROM __livestore_schema",
223
+ "sql.rowsCount": 0,
224
+ },
225
+ },
226
+ {
227
+ "_name": "livestore.in-memory-db:execute",
228
+ "attributes": {
229
+ "sql.query": "INSERT OR IGNORE INTO app (id, newTodoText, filter) VALUES ('static', '', 'all');",
230
+ },
231
+ },
232
+ {
233
+ "_name": "LiveStore:applyEvents",
234
+ "children": [
235
+ {
236
+ "_name": "LiveStore:applyEvent",
237
+ "children": [
238
+ {
239
+ "_name": "LiveStore:applyEventWithoutRefresh",
240
+ "attributes": {
241
+ "livestore.actionType": "RawSql",
242
+ "livestore.args": "{
243
+ \\"sql\\": \\"INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);\\",
244
+ \\"bindValues\\": {},
245
+ \\"writeTables\\": [
246
+ \\"todos\\"
247
+ ]
248
+ }",
249
+ },
250
+ "children": [
251
+ {
252
+ "_name": "livestore.in-memory-db:execute",
253
+ "attributes": {
254
+ "sql.query": "INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);",
255
+ },
256
+ },
257
+ ],
258
+ },
259
+ ],
260
+ },
261
+ ],
262
+ },
263
+ {
264
+ "_name": "LiveStore:queries",
265
+ "children": [
266
+ {
267
+ "_name": "js:all todos:first",
268
+ "children": [
269
+ {
270
+ "_name": "sql:select * from todos where completed = 0",
271
+ "attributes": {
272
+ "sql.query": "select * from todos where completed = 0",
273
+ "sql.rowsCount": 0,
274
+ },
275
+ "children": [
276
+ {
277
+ "_name": "js:where-filter",
278
+ },
279
+ {
280
+ "_name": "sql-in-memory-select",
281
+ "attributes": {
282
+ "sql.cached": false,
283
+ "sql.query": "select * from todos where completed = 0",
284
+ "sql.rowsCount": 0,
285
+ },
286
+ },
287
+ ],
288
+ },
289
+ ],
290
+ },
291
+ {
292
+ "_name": "js:all todos:first",
293
+ "children": [
294
+ {
295
+ "_name": "sql:select * from todos where completed = 0",
296
+ "attributes": {
297
+ "sql.query": "select * from todos where completed = 0",
298
+ "sql.rowsCount": 1,
299
+ },
300
+ "children": [
301
+ {
302
+ "_name": "sql-in-memory-select",
303
+ "attributes": {
304
+ "sql.cached": false,
305
+ "sql.query": "select * from todos where completed = 0",
306
+ "sql.rowsCount": 1,
307
+ },
308
+ },
309
+ ],
310
+ },
311
+ ],
312
+ },
313
+ ],
314
+ },
315
+ ],
316
+ }
317
+ `)
318
+ })
319
+ })
320
+
321
+ const compareHrTime = (a: [number, number], b: [number, number]) => {
322
+ if (a[0] !== b[0]) return a[0] - b[0]
323
+ return a[1] - b[1]
324
+ }
325
+
326
+ const omitEmpty = (obj: any) => {
327
+ const result: any = {}
328
+ for (const key in obj) {
329
+ if (
330
+ obj[key] !== undefined &&
331
+ !(Array.isArray(obj[key]) && obj[key].length === 0) &&
332
+ Object.keys(obj[key]).length > 0
333
+ ) {
334
+ result[key] = obj[key]
335
+ }
336
+ }
337
+ return result
338
+ }
339
+
340
+ const getSimplifiedRootSpan = (exporter: InMemorySpanExporter) => {
341
+ const spans = exporter.getFinishedSpans()
342
+ const spansMap = new Map<string, NestedSpan>(spans.map((span) => [span.spanContext().spanId, { span, children: [] }]))
343
+
344
+ spansMap.forEach((nestedSpan) => {
345
+ const parentSpan = nestedSpan.span.parentSpanId ? spansMap.get(nestedSpan.span.parentSpanId) : undefined
346
+ if (parentSpan) {
347
+ parentSpan.children.push(nestedSpan)
348
+ }
349
+ })
350
+
351
+ type NestedSpan = { span: ReadableSpan; children: NestedSpan[] }
352
+ const rootSpan = spansMap.get(spans.find((_) => _.name === 'test')!.spanContext().spanId)!
353
+
354
+ type SimplifiedNestedSpan = { _name: string; attributes: any; children: SimplifiedNestedSpan[] }
355
+
356
+ const simplifySpan = (span: NestedSpan): SimplifiedNestedSpan =>
357
+ omitEmpty({
358
+ _name: span.span.name,
359
+ attributes: span.span.attributes,
360
+ children: span.children
361
+ .filter((_) => _.span.name !== 'createStore')
362
+ .sort((a, b) => compareHrTime(a.span.startTime, b.span.startTime))
363
+ .map(simplifySpan),
364
+ })
365
+
366
+ // console.dir(
367
+ // spans.map((_) => [_.spanContext().spanId, _.name, _.attributes, _.parentSpanId]),
368
+ // { depth: 10 },
369
+ // )
370
+
371
+ return simplifySpan(rootSpan)
372
+ }
@@ -2,7 +2,7 @@ import type { Scope } from '@livestore/utils/effect'
2
2
  import { Context, Deferred, Duration, Effect, Layer, OtelTracer, pipe, Runtime } from '@livestore/utils/effect'
3
3
  import * as otel from '@opentelemetry/api'
4
4
  import type { GraphQLSchema } from 'graphql'
5
- import { mapValues } from 'lodash-es'
5
+ import initSqlite3Wasm from 'sqlite-esm'
6
6
 
7
7
  import type { InMemoryDatabase } from '../inMemoryDatabase.js'
8
8
  import type { Schema } from '../schema.js'
@@ -10,18 +10,22 @@ import type { StorageInit } from '../storage/index.js'
10
10
  import type { BaseGraphQLContext, GraphQLOptions, LiveStoreQuery, Store } from '../store.js'
11
11
  import { createStore } from '../store.js'
12
12
 
13
+ // NOTE we're starting to initialize the sqlite wasm binary here (already before calling `createStore`),
14
+ // so that it's ready when we need it
15
+ const sqlite3Promise = initSqlite3Wasm({
16
+ print: (message) => console.log(`[livestore sqlite] ${message}`),
17
+ printErr: (message) => console.error(`[livestore sqlite] ${message}`),
18
+ })
19
+
13
20
  // TODO get rid of `LiveStoreContext` wrapper and only expose the `Store` directly
14
21
  export type LiveStoreContext = {
15
- store: Store<any>
16
- globalQueries: LiveStoreQueryTypes
22
+ store: Store
17
23
  }
18
24
 
19
- export type QueryDefinition = (store: Store<any>) => LiveStoreQuery
20
- export type GlobalQueryDefs = { [key: string]: QueryDefinition }
25
+ export type QueryDefinition = (store: Store) => LiveStoreQuery
21
26
 
22
27
  export type LiveStoreCreateStoreOptions<GraphQLContext extends BaseGraphQLContext> = {
23
28
  schema: Schema
24
- globalQueryDefs: GlobalQueryDefs
25
29
  loadStorage: () => StorageInit | Promise<StorageInit>
26
30
  graphQLOptions?: GraphQLOptions<GraphQLContext>
27
31
  otelTracer?: otel.Tracer
@@ -40,7 +44,6 @@ export const DeferredStoreContext = Context.Tag<DeferredStoreContext>(
40
44
 
41
45
  export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
42
46
  schema: Schema
43
- globalQueryDefs?: Effect.Effect<never, never, GlobalQueryDefs>
44
47
  loadStorage: () => StorageInit | Promise<StorageInit>
45
48
  graphQLOptions?: {
46
49
  schema: Effect.Effect<otel.Tracer, never, GraphQLSchema>
@@ -60,7 +63,6 @@ export const LiveStoreContextLayer = <GraphQLContext extends BaseGraphQLContext>
60
63
  export const LiveStoreContextDeferred = Layer.effect(DeferredStoreContext, Deferred.make<never, LiveStoreContext>())
61
64
 
62
65
  export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>({
63
- globalQueryDefs,
64
66
  schema,
65
67
  loadStorage,
66
68
  graphQLOptions: graphQLOptions_,
@@ -89,6 +91,8 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
89
91
  boot_(db).pipe(Effect.withSpan('boot'), Effect.tapCauseLogPretty, Runtime.runPromise(runtime))
90
92
  : undefined
91
93
 
94
+ const sqlite3 = yield* $(Effect.promise(() => sqlite3Promise))
95
+
92
96
  const store = yield* $(
93
97
  Effect.tryPromise(() =>
94
98
  createStore({
@@ -98,6 +102,7 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
98
102
  otelTracer,
99
103
  otelRootSpanContext,
100
104
  boot,
105
+ sqlite3,
101
106
  }),
102
107
  ),
103
108
  Effect.acquireRelease((store) => Effect.sync(() => store.destroy())),
@@ -105,16 +110,7 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
105
110
 
106
111
  window.__debugLiveStore = store
107
112
 
108
- // TODO remove global queries concept
109
- const globalQueries = yield* $(
110
- globalQueryDefs ?? Effect.succeed({} as GlobalQueryDefs),
111
- Effect.map((defs) => mapValues(defs, (queryDef) => queryDef(store))),
112
- Effect.withSpan('LiveStore:makeGlobalQueries', {
113
- parent: OtelTracer.makeExternalSpan(otel.trace.getSpanContext(store.otel.queriesSpanContext)!),
114
- }),
115
- )
116
-
117
- return { store, globalQueries }
113
+ return { store }
118
114
  }),
119
115
  Effect.tap((storeCtx) => Effect.flatMap(DeferredStoreContext, (def) => Deferred.succeed(def, storeCtx))),
120
116
  Effect.timeoutFail({
@@ -2,28 +2,16 @@
2
2
 
3
3
  import { shouldNeverHappen } from '@livestore/utils'
4
4
  import type * as otel from '@opentelemetry/api'
5
- import type * as SqliteWasm from 'sqlite-esm'
5
+ import type * as Sqlite from 'sqlite-esm'
6
6
 
7
7
  import BoundMap, { BoundArray } from './bounded-collections.js'
8
8
  // import { EVENTS_TABLE_NAME } from './events.js'
9
9
  import { sql } from './index.js'
10
10
  import { getDurationMsFromSpan, getStartTimeHighResFromSpan } from './otel.js'
11
11
  import QueryCache from './QueryCache.js'
12
- import type { Bindable, ParamsObject } from './util.js'
13
- import { prepareBindValues } from './util.js'
12
+ import type { Bindable, PreparedBindValues } from './util.js'
14
13
 
15
- export enum IndexType {
16
- Basic = 'Basic',
17
- FullText = 'FullText',
18
- }
19
-
20
- export interface Index {
21
- indexType: IndexType
22
- name: string
23
- columns: string[]
24
- }
25
-
26
- declare type DatabaseWithCAPI = SqliteWasm.Database & { capi: SqliteWasm.CAPI }
14
+ type DatabaseWithCAPI = Sqlite.Database & { capi: Sqlite.CAPI }
27
15
 
28
16
  export interface DebugInfo {
29
17
  slowQueries: BoundArray<SlowQueryInfo>
@@ -34,10 +22,10 @@ export interface DebugInfo {
34
22
 
35
23
  export type SlowQueryInfo = [
36
24
  queryStr: string,
37
- bindValues: Bindable | undefined,
25
+ bindValues: PreparedBindValues | undefined,
38
26
  durationMs: number,
39
27
  rowsCount: number | undefined,
40
- queriedTables: string[],
28
+ queriedTables: ReadonlyArray<string>,
41
29
  startTimePerfNow: DOMHighResTimeStamp,
42
30
  ]
43
31
 
@@ -50,7 +38,7 @@ export const emptyDebugInfo = (): DebugInfo => ({
50
38
 
51
39
  export class InMemoryDatabase {
52
40
  // TODO: how many unique active statements are expected?
53
- private cachedStmts = new BoundMap<string, SqliteWasm.PreparedStatement>(200)
41
+ private cachedStmts = new BoundMap<string, Sqlite.PreparedStatement>(200)
54
42
  private tablesUsedCache = new BoundMap<string, string[]>(200)
55
43
  private resultCache = new QueryCache()
56
44
  public debugInfo: DebugInfo = emptyDebugInfo()
@@ -59,15 +47,20 @@ export class InMemoryDatabase {
59
47
  private db: DatabaseWithCAPI,
60
48
  private otelTracer: otel.Tracer,
61
49
  private otelRootSpanContext: otel.Context,
62
- public SQL: SqliteWasm.Sqlite3Static,
50
+ public SQL: Sqlite.Sqlite3Static,
63
51
  ) {}
64
52
 
65
- static load(
66
- data: Uint8Array | undefined,
67
- otelTracer: otel.Tracer,
68
- otelRootSpanContext: otel.Context,
69
- sqlite3: SqliteWasm.Sqlite3Static,
70
- ): InMemoryDatabase {
53
+ static load({
54
+ data,
55
+ otelTracer,
56
+ otelRootSpanContext,
57
+ sqlite3,
58
+ }: {
59
+ data: Uint8Array | undefined
60
+ otelTracer: otel.Tracer
61
+ otelRootSpanContext: otel.Context
62
+ sqlite3: Sqlite.Sqlite3Static
63
+ }): InMemoryDatabase {
71
64
  // TODO move WASM init higher up in the init process (to do some other work while it's loading)
72
65
 
73
66
  const db = new sqlite3.oo1.DB({ filename: ':memory:', flags: 'c' }) as DatabaseWithCAPI
@@ -138,7 +131,7 @@ export class InMemoryDatabase {
138
131
 
139
132
  execute(
140
133
  query: string,
141
- bindValues?: ParamsObject,
134
+ bindValues?: PreparedBindValues,
142
135
  writeTables?: string[],
143
136
  options?: { hasNoEffects?: boolean; otelContext: otel.Context },
144
137
  ): { durationMs: number } {
@@ -155,9 +148,8 @@ export class InMemoryDatabase {
155
148
  this.cachedStmts.set(query, stmt)
156
149
  }
157
150
 
158
- // TODO check whether we can remove the extra `prepareBindValues` call here (e.g. enforce proper type in API)
159
151
  if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
160
- stmt.bind(prepareBindValues(bindValues, query))
152
+ stmt.bind(bindValues)
161
153
  }
162
154
 
163
155
  if (import.meta.env.DEV) {
@@ -210,8 +202,8 @@ export class InMemoryDatabase {
210
202
  select<T = any>(
211
203
  query: string,
212
204
  options?: {
213
- queriedTables?: string[]
214
- bindValues?: Bindable
205
+ queriedTables?: ReadonlyArray<string>
206
+ bindValues?: PreparedBindValues
215
207
  skipCache?: boolean
216
208
  otelContext?: otel.Context
217
209
  },
package/src/index.ts CHANGED
@@ -1,11 +1,10 @@
1
- export { Store, createStore, RESET_DB_LOCAL_STORAGE_KEY } from './store.js'
1
+ export { Store, createStore } from './store.js'
2
2
  export type { LiveStoreQuery, BaseGraphQLContext, QueryResult, QueryDebugInfo, RefreshReason } from './store.js'
3
3
 
4
4
  export type { QueryDefinition, LiveStoreCreateStoreOptions, LiveStoreContext } from './effect/LiveStore.js'
5
5
 
6
6
  export {
7
7
  defineComponentStateSchema,
8
- EVENT_CURSOR_TABLE,
9
8
  defineAction,
10
9
  defineActions,
11
10
  defineTables,
@@ -21,10 +20,13 @@ export type {
21
20
  RefreshReasonWithGenericReasons,
22
21
  SerializedAtom,
23
22
  SerializedEffect,
23
+ Atom,
24
24
  } from './reactive.js'
25
- export type { LiveStoreJSQuery } from './reactiveQueries/js.js'
26
- export type { LiveStoreSQLQuery } from './reactiveQueries/sql.js'
27
- export type { LiveStoreGraphQLQuery } from './reactiveQueries/graphql.js'
25
+ export { type LiveStoreJSQuery, queryJS } from './reactiveQueries/js.js'
26
+ export { type LiveStoreSQLQuery, querySQL } from './reactiveQueries/sql.js'
27
+ export { type LiveStoreGraphQLQuery, queryGraphQL } from './reactiveQueries/graphql.js'
28
+ export { type GetAtomResult } from './reactiveQueries/base-class.js'
29
+ export { dbGraph } from './reactiveQueries/graph.js'
28
30
 
29
31
  export { labelForKey } from './componentKey.js'
30
32
  export type { ComponentKey } from './componentKey.js'
@@ -37,5 +39,5 @@ export type TableDefinition = SqliteAst.Table
37
39
 
38
40
  export { SqliteDsl as DbSchema } from 'effect-db-schema'
39
41
 
40
- export { sql, type Bindable } from './util.js'
42
+ export { prepareBindValues, sql, type Bindable, type PreparedBindValues } from './util.js'
41
43
  export { isEqual } from 'lodash-es'
package/src/migrations.ts CHANGED
@@ -5,8 +5,11 @@ import { memoize, omit } from 'lodash-es'
5
5
  import type { InMemoryDatabase } from './index.js'
6
6
  import type { Schema, SchemaMetaRow } from './schema.js'
7
7
  import { componentStateTables, SCHEMA_META_TABLE, systemTables } from './schema.js'
8
+ import type { PreparedBindValues } from './util.js'
8
9
  import { sql } from './util.js'
9
10
 
11
+ const getMemoizedTimestamp = memoize(() => new Date().toISOString())
12
+
10
13
  // TODO more graceful DB migration (e.g. backup DB before destructive migrations)
11
14
  export const migrateDb = ({
12
15
  db,
@@ -31,7 +34,6 @@ export const migrateDb = ({
31
34
  schemaMetaRows.map(({ tableName, schemaHash }) => [tableName, schemaHash]),
32
35
  )
33
36
 
34
- const getMemoizedTimestamp = memoize(() => new Date().toISOString())
35
37
  const tableDefs = {
36
38
  // NOTE it's important the `SCHEMA_META_TABLE` comes first since we're writing to it below
37
39
  [SCHEMA_META_TABLE]: systemTables[SCHEMA_META_TABLE],
@@ -47,30 +49,46 @@ export const migrateDb = ({
47
49
  `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
48
50
  )
49
51
 
50
- const columnSpec = makeColumnSpec(tableDef)
51
-
52
- // TODO need to possibly handle cascading deletes due to foreign keys
53
- db.execute(sql`drop table if exists ${tableName}`, undefined, [], { otelContext })
54
- db.execute(sql`create table if not exists ${tableName} (${columnSpec});`, undefined, [], { otelContext })
55
-
56
- for (const index of tableDef.indexes) {
57
- db.execute(createIndexFromDefinition(tableName, index), undefined, [], { otelContext })
58
- }
59
-
60
- const updatedAt = getMemoizedTimestamp()
61
- db.execute(
62
- sql`
63
- INSERT INTO ${SCHEMA_META_TABLE} (tableName, schemaHash, updatedAt) VALUES ($tableName, $schemaHash, $updatedAt)
64
- ON CONFLICT (tableName) DO UPDATE SET schemaHash = $schemaHash, updatedAt = $updatedAt;
65
- `,
66
- { tableName, schemaHash, updatedAt },
67
- [],
68
- { otelContext },
69
- )
52
+ migrateTable({ db, tableDef, otelContext, schemaHash })
70
53
  }
71
54
  }
72
55
  }
73
56
 
57
+ export const migrateTable = ({
58
+ db,
59
+ tableDef,
60
+ otelContext,
61
+ schemaHash,
62
+ }: {
63
+ db: InMemoryDatabase
64
+ tableDef: SqliteAst.Table
65
+ otelContext: otel.Context
66
+ schemaHash: number
67
+ }) => {
68
+ console.log(`Migrating table '${tableDef.name}'...`)
69
+ const tableName = tableDef.name
70
+ const columnSpec = makeColumnSpec(tableDef)
71
+
72
+ // TODO need to possibly handle cascading deletes due to foreign keys
73
+ db.execute(sql`drop table if exists ${tableName}`, undefined, [], { otelContext })
74
+ db.execute(sql`create table if not exists ${tableName} (${columnSpec});`, undefined, [], { otelContext })
75
+
76
+ for (const index of tableDef.indexes) {
77
+ db.execute(createIndexFromDefinition(tableName, index), undefined, [], { otelContext })
78
+ }
79
+
80
+ const updatedAt = getMemoizedTimestamp()
81
+ db.execute(
82
+ sql`
83
+ INSERT INTO ${SCHEMA_META_TABLE} (tableName, schemaHash, updatedAt) VALUES ($tableName, $schemaHash, $updatedAt)
84
+ ON CONFLICT (tableName) DO UPDATE SET schemaHash = $schemaHash, updatedAt = $updatedAt;
85
+ `,
86
+ { $tableName: tableName, $schemaHash: schemaHash, $updatedAt: updatedAt } as unknown as PreparedBindValues,
87
+ [],
88
+ { otelContext },
89
+ )
90
+ }
91
+
74
92
  const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) => {
75
93
  const uniqueStr = index.unique ? 'UNIQUE' : ''
76
94
  return sql`create ${uniqueStr} index ${index.name} on ${tableName} (${index.columns.join(', ')})`