@livestore/livestore 0.0.24 → 0.0.27

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 (243) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/QueryCache.d.ts +3 -3
  3. package/dist/QueryCache.d.ts.map +1 -1
  4. package/dist/QueryCache.js +50 -60
  5. package/dist/QueryCache.js.map +1 -1
  6. package/dist/__tests__/react/fixture.d.ts +22 -7
  7. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  8. package/dist/__tests__/react/fixture.js +14 -15
  9. package/dist/__tests__/react/fixture.js.map +1 -1
  10. package/dist/__tests__/react/useQuery.test.js +37 -12
  11. package/dist/__tests__/react/useQuery.test.js.map +1 -1
  12. package/dist/__tests__/react/useRow.test.d.ts +2 -0
  13. package/dist/__tests__/react/useRow.test.d.ts.map +1 -0
  14. package/dist/__tests__/react/useRow.test.js +131 -0
  15. package/dist/__tests__/react/useRow.test.js.map +1 -0
  16. package/dist/__tests__/react/utils/stack-info.test.js +32 -0
  17. package/dist/__tests__/react/utils/stack-info.test.js.map +1 -1
  18. package/dist/__tests__/reactive.test.js +51 -0
  19. package/dist/__tests__/reactive.test.js.map +1 -1
  20. package/dist/__tests__/reactiveQueries/sql.test.js +6 -13
  21. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -1
  22. package/dist/effect/LiveStore.d.ts +3 -3
  23. package/dist/effect/LiveStore.d.ts.map +1 -1
  24. package/dist/effect/LiveStore.js +2 -2
  25. package/dist/effect/LiveStore.js.map +1 -1
  26. package/dist/global-state.d.ts +19 -0
  27. package/dist/global-state.d.ts.map +1 -0
  28. package/dist/global-state.js +20 -0
  29. package/dist/global-state.js.map +1 -0
  30. package/dist/inMemoryDatabase.d.ts +6 -6
  31. package/dist/inMemoryDatabase.d.ts.map +1 -1
  32. package/dist/inMemoryDatabase.js +16 -10
  33. package/dist/inMemoryDatabase.js.map +1 -1
  34. package/dist/index.d.ts +9 -13
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +7 -8
  37. package/dist/index.js.map +1 -1
  38. package/dist/migrations.d.ts +4 -4
  39. package/dist/migrations.d.ts.map +1 -1
  40. package/dist/migrations.js +34 -28
  41. package/dist/migrations.js.map +1 -1
  42. package/dist/react/LiveStoreContext.js.map +1 -1
  43. package/dist/react/LiveStoreProvider.d.ts +2 -2
  44. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  45. package/dist/react/LiveStoreProvider.js.map +1 -1
  46. package/dist/react/index.d.ts +1 -2
  47. package/dist/react/index.d.ts.map +1 -1
  48. package/dist/react/index.js +1 -1
  49. package/dist/react/index.js.map +1 -1
  50. package/dist/react/useQuery.d.ts +3 -0
  51. package/dist/react/useQuery.d.ts.map +1 -1
  52. package/dist/react/useQuery.js +7 -6
  53. package/dist/react/useQuery.js.map +1 -1
  54. package/dist/react/useRow.d.ts +33 -0
  55. package/dist/react/useRow.d.ts.map +1 -0
  56. package/dist/react/useRow.js +136 -0
  57. package/dist/react/useRow.js.map +1 -0
  58. package/dist/react/useTemporaryQuery.d.ts +2 -0
  59. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  60. package/dist/react/useTemporaryQuery.js +28 -11
  61. package/dist/react/useTemporaryQuery.js.map +1 -1
  62. package/dist/react/utils/stack-info.d.ts.map +1 -1
  63. package/dist/react/utils/stack-info.js +3 -3
  64. package/dist/react/utils/stack-info.js.map +1 -1
  65. package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -1
  66. package/dist/reactive.d.ts +38 -29
  67. package/dist/reactive.d.ts.map +1 -1
  68. package/dist/reactive.js +73 -45
  69. package/dist/reactive.js.map +1 -1
  70. package/dist/reactiveQueries/base-class.d.ts +10 -6
  71. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  72. package/dist/reactiveQueries/base-class.js +11 -12
  73. package/dist/reactiveQueries/base-class.js.map +1 -1
  74. package/dist/reactiveQueries/graphql.d.ts +2 -2
  75. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  76. package/dist/reactiveQueries/graphql.js +56 -50
  77. package/dist/reactiveQueries/graphql.js.map +1 -1
  78. package/dist/reactiveQueries/js.d.ts +7 -3
  79. package/dist/reactiveQueries/js.d.ts.map +1 -1
  80. package/dist/reactiveQueries/js.js +25 -15
  81. package/dist/reactiveQueries/js.js.map +1 -1
  82. package/dist/reactiveQueries/sql.d.ts +5 -5
  83. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  84. package/dist/reactiveQueries/sql.js +39 -34
  85. package/dist/reactiveQueries/sql.js.map +1 -1
  86. package/dist/row-query.d.ts +21 -0
  87. package/dist/row-query.d.ts.map +1 -0
  88. package/dist/row-query.js +77 -0
  89. package/dist/row-query.js.map +1 -0
  90. package/dist/schema/action.d.ts +30 -0
  91. package/dist/schema/action.d.ts.map +1 -0
  92. package/dist/schema/action.js +3 -0
  93. package/dist/schema/action.js.map +1 -0
  94. package/dist/schema/index.d.ts +28 -0
  95. package/dist/schema/index.d.ts.map +1 -0
  96. package/dist/schema/index.js +26 -0
  97. package/dist/schema/index.js.map +1 -0
  98. package/dist/schema/system-tables.d.ts +24 -0
  99. package/dist/schema/system-tables.d.ts.map +1 -0
  100. package/dist/schema/system-tables.js +11 -0
  101. package/dist/schema/system-tables.js.map +1 -0
  102. package/dist/schema/table-def.d.ts +161 -0
  103. package/dist/schema/table-def.d.ts.map +1 -0
  104. package/dist/schema/table-def.js +53 -0
  105. package/dist/schema/table-def.js.map +1 -0
  106. package/dist/storage/in-memory/index.d.ts +1 -1
  107. package/dist/storage/in-memory/index.d.ts.map +1 -1
  108. package/dist/storage/in-memory/index.js +6 -7
  109. package/dist/storage/in-memory/index.js.map +1 -1
  110. package/dist/storage/index.d.ts +1 -1
  111. package/dist/storage/index.d.ts.map +1 -1
  112. package/dist/storage/tauri/index.d.ts +1 -1
  113. package/dist/storage/tauri/index.d.ts.map +1 -1
  114. package/dist/storage/tauri/index.js +25 -23
  115. package/dist/storage/tauri/index.js.map +1 -1
  116. package/dist/storage/utils/idb.js +3 -1
  117. package/dist/storage/utils/idb.js.map +1 -1
  118. package/dist/storage/web-worker/index.d.ts +1 -1
  119. package/dist/storage/web-worker/index.d.ts.map +1 -1
  120. package/dist/storage/web-worker/index.js +38 -34
  121. package/dist/storage/web-worker/index.js.map +1 -1
  122. package/dist/storage/web-worker/worker.d.ts +1 -1
  123. package/dist/storage/web-worker/worker.d.ts.map +1 -1
  124. package/dist/storage/web-worker/worker.js +1 -1
  125. package/dist/storage/web-worker/worker.js.map +1 -1
  126. package/dist/store.d.ts +11 -21
  127. package/dist/store.d.ts.map +1 -1
  128. package/dist/store.js +284 -272
  129. package/dist/store.js.map +1 -1
  130. package/dist/utils/bounded-collections.d.ts.map +1 -0
  131. package/dist/utils/bounded-collections.js +90 -0
  132. package/dist/utils/bounded-collections.js.map +1 -0
  133. package/dist/utils/otel.d.ts.map +1 -0
  134. package/dist/{otel.js → utils/otel.js} +1 -1
  135. package/dist/utils/otel.js.map +1 -0
  136. package/dist/utils/util.d.ts.map +1 -0
  137. package/dist/utils/util.js.map +1 -0
  138. package/package.json +21 -18
  139. package/src/QueryCache.ts +4 -4
  140. package/src/__tests__/react/fixture.tsx +17 -17
  141. package/src/__tests__/react/useQuery.test.tsx +56 -14
  142. package/src/__tests__/react/useRow.test.tsx +205 -0
  143. package/src/__tests__/react/utils/stack-info.test.ts +34 -0
  144. package/src/__tests__/reactive.test.ts +71 -0
  145. package/src/__tests__/reactiveQueries/sql.test.ts +6 -13
  146. package/src/effect/LiveStore.ts +7 -7
  147. package/src/global-state.ts +26 -0
  148. package/src/inMemoryDatabase.ts +14 -12
  149. package/src/index.ts +22 -29
  150. package/src/migrations.ts +41 -35
  151. package/src/react/LiveStoreProvider.tsx +2 -2
  152. package/src/react/index.ts +7 -9
  153. package/src/react/useQuery.ts +12 -6
  154. package/src/react/useRow.ts +221 -0
  155. package/src/react/useTemporaryQuery.ts +43 -11
  156. package/src/react/utils/stack-info.ts +4 -3
  157. package/src/reactive.ts +81 -65
  158. package/src/reactiveQueries/base-class.ts +14 -10
  159. package/src/reactiveQueries/graphql.ts +4 -3
  160. package/src/reactiveQueries/js.ts +9 -5
  161. package/src/reactiveQueries/sql.ts +9 -9
  162. package/src/row-query.ts +142 -0
  163. package/src/schema/action.ts +41 -0
  164. package/src/schema/index.ts +63 -0
  165. package/src/schema/system-tables.ts +21 -0
  166. package/src/schema/table-def.ts +199 -0
  167. package/src/storage/in-memory/index.ts +1 -1
  168. package/src/storage/index.ts +2 -1
  169. package/src/storage/tauri/index.ts +2 -2
  170. package/src/storage/web-worker/index.ts +1 -1
  171. package/src/storage/web-worker/worker.ts +2 -2
  172. package/src/store.ts +51 -51
  173. package/dist/__tests__/react/useComponentState.test.d.ts +0 -2
  174. package/dist/__tests__/react/useComponentState.test.d.ts.map +0 -1
  175. package/dist/__tests__/react/useComponentState.test.js +0 -68
  176. package/dist/__tests__/react/useComponentState.test.js.map +0 -1
  177. package/dist/__tests__/react/useLQuery.test.d.ts +0 -2
  178. package/dist/__tests__/react/useLQuery.test.d.ts.map +0 -1
  179. package/dist/__tests__/react/useLQuery.test.js +0 -38
  180. package/dist/__tests__/react/useLQuery.test.js.map +0 -1
  181. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts +0 -2
  182. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +0 -1
  183. package/dist/__tests__/react/useLiveStoreComponent.test.js +0 -73
  184. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +0 -1
  185. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +0 -2
  186. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +0 -1
  187. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +0 -38
  188. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +0 -1
  189. package/dist/bounded-collections.d.ts.map +0 -1
  190. package/dist/bounded-collections.js +0 -103
  191. package/dist/bounded-collections.js.map +0 -1
  192. package/dist/componentKey.d.ts +0 -20
  193. package/dist/componentKey.d.ts.map +0 -1
  194. package/dist/componentKey.js +0 -3
  195. package/dist/componentKey.js.map +0 -1
  196. package/dist/otel.d.ts.map +0 -1
  197. package/dist/otel.js.map +0 -1
  198. package/dist/react/useComponentState.d.ts +0 -50
  199. package/dist/react/useComponentState.d.ts.map +0 -1
  200. package/dist/react/useComponentState.js +0 -240
  201. package/dist/react/useComponentState.js.map +0 -1
  202. package/dist/react/useGlobalQuery.d.ts +0 -3
  203. package/dist/react/useGlobalQuery.d.ts.map +0 -1
  204. package/dist/react/useGlobalQuery.js +0 -26
  205. package/dist/react/useGlobalQuery.js.map +0 -1
  206. package/dist/react/useGraphQL.d.ts +0 -13
  207. package/dist/react/useGraphQL.d.ts.map +0 -1
  208. package/dist/react/useGraphQL.js +0 -87
  209. package/dist/react/useGraphQL.js.map +0 -1
  210. package/dist/react/useLiveStoreComponent.d.ts +0 -75
  211. package/dist/react/useLiveStoreComponent.d.ts.map +0 -1
  212. package/dist/react/useLiveStoreComponent.js +0 -361
  213. package/dist/react/useLiveStoreComponent.js.map +0 -1
  214. package/dist/react/utils/extractNamesFromStackTrace.d.ts +0 -3
  215. package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +0 -1
  216. package/dist/react/utils/extractNamesFromStackTrace.js +0 -40
  217. package/dist/react/utils/extractNamesFromStackTrace.js.map +0 -1
  218. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +0 -7
  219. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +0 -1
  220. package/dist/react/utils/extractStackInfoFromStackTrace.js +0 -40
  221. package/dist/react/utils/extractStackInfoFromStackTrace.js.map +0 -1
  222. package/dist/reactiveQueries/graph.d.ts +0 -10
  223. package/dist/reactiveQueries/graph.d.ts.map +0 -1
  224. package/dist/reactiveQueries/graph.js +0 -6
  225. package/dist/reactiveQueries/graph.js.map +0 -1
  226. package/dist/schema.d.ts +0 -81
  227. package/dist/schema.d.ts.map +0 -1
  228. package/dist/schema.js +0 -46
  229. package/dist/schema.js.map +0 -1
  230. package/dist/util.d.ts.map +0 -1
  231. package/dist/util.js.map +0 -1
  232. package/src/__tests__/react/useComponentState.test.tsx +0 -100
  233. package/src/componentKey.ts +0 -9
  234. package/src/react/useComponentState.ts +0 -404
  235. package/src/reactiveQueries/graph.ts +0 -15
  236. package/src/schema.ts +0 -143
  237. /package/dist/{bounded-collections.d.ts → utils/bounded-collections.d.ts} +0 -0
  238. /package/dist/{otel.d.ts → utils/otel.d.ts} +0 -0
  239. /package/dist/{util.d.ts → utils/util.d.ts} +0 -0
  240. /package/dist/{util.js → utils/util.js} +0 -0
  241. /package/src/{bounded-collections.ts → utils/bounded-collections.ts} +0 -0
  242. /package/src/{otel.ts → utils/otel.ts} +0 -0
  243. /package/src/{util.ts → utils/util.ts} +0 -0
@@ -159,6 +159,77 @@ describe('a trivial graph', () => {
159
159
  graph.setRef(a, 2)
160
160
  // expect(numberOfCallsToC).toBe(2) // TODO comp caching
161
161
  })
162
+
163
+ describe('skip refresh', () => {
164
+ it(`defers effect execution until manual run`, () => {
165
+ const { graph, a, c, d, numberOfRunsForC } = makeGraph()
166
+
167
+ // using here both to track number oe effect runs and to "update the effect behavior"
168
+ let numberOfEffectRuns = 0
169
+ const effect = graph.makeEffect((get) => {
170
+ expect(get(c)).toBe(numberOfEffectRuns === 0 ? 3 : 4)
171
+ numberOfEffectRuns++
172
+ })
173
+
174
+ effect.doEffect()
175
+
176
+ expect(numberOfEffectRuns).toBe(1)
177
+ expect(numberOfRunsForC.runs).toBe(1)
178
+
179
+ graph.setRef(a, 2, { skipRefresh: true })
180
+
181
+ expect(numberOfEffectRuns).toBe(1)
182
+ expect(numberOfRunsForC.runs).toBe(1)
183
+
184
+ // Even setting a unrelated ref should not trigger a refresh
185
+ graph.setRef(d, 0)
186
+
187
+ expect(numberOfEffectRuns).toBe(1)
188
+ expect(numberOfRunsForC.runs).toBe(1)
189
+
190
+ graph.runDeferredEffects()
191
+
192
+ expect(numberOfEffectRuns).toBe(2)
193
+ expect(numberOfRunsForC.runs).toBe(2)
194
+ })
195
+
196
+ it(`doesn't run deferred effects which have been destroyed already`, () => {
197
+ const { graph, a, c, numberOfRunsForC } = makeGraph()
198
+
199
+ let numberOfEffect1Runs = 0
200
+ const effect1 = graph.makeEffect((get) => {
201
+ expect(get(c)).toBe(numberOfEffect1Runs === 0 ? 3 : 4)
202
+ numberOfEffect1Runs++
203
+ })
204
+
205
+ let numberOfEffect2Runs = 0
206
+ const effect2 = graph.makeEffect((get) => {
207
+ expect(get(c)).toBe(numberOfEffect2Runs === 0 ? 3 : 4)
208
+ numberOfEffect2Runs++
209
+ })
210
+
211
+ effect1.doEffect()
212
+ effect2.doEffect()
213
+
214
+ expect(numberOfEffect1Runs).toBe(1)
215
+ expect(numberOfEffect2Runs).toBe(1)
216
+ expect(numberOfRunsForC.runs).toBe(1)
217
+
218
+ graph.setRef(a, 2, { skipRefresh: true })
219
+
220
+ expect(numberOfEffect1Runs).toBe(1)
221
+ expect(numberOfEffect2Runs).toBe(1)
222
+ expect(numberOfRunsForC.runs).toBe(1)
223
+
224
+ graph.destroy(effect1)
225
+
226
+ graph.runDeferredEffects()
227
+
228
+ expect(numberOfEffect1Runs).toBe(1)
229
+ expect(numberOfEffect2Runs).toBe(2)
230
+ expect(numberOfRunsForC.runs).toBe(2)
231
+ })
232
+ })
162
233
  })
163
234
  })
164
235
 
@@ -30,12 +30,11 @@ describe('otel', () => {
30
30
  it('otel', async () => {
31
31
  const { store, exporter, span } = await makeQuery()
32
32
 
33
- const query = querySQL(`select * from todos`, { queriedTables: ['todos'] })
33
+ const query = querySQL(`select * from todos`, { queriedTables: new Set(['todos']) })
34
34
  expect(query.run()).toMatchInlineSnapshot('[]')
35
35
 
36
- store.applyEvent('RawSql', {
36
+ store.applyEvent('livestore.RawSql', {
37
37
  sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
38
- bindValues: {},
39
38
  writeTables: ['todos'],
40
39
  })
41
40
 
@@ -93,10 +92,9 @@ describe('otel', () => {
93
92
  {
94
93
  "_name": "LiveStore:applyEventWithoutRefresh",
95
94
  "attributes": {
96
- "livestore.actionType": "RawSql",
95
+ "livestore.actionType": "livestore.RawSql",
97
96
  "livestore.args": "{
98
97
  \\"sql\\": \\"INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);\\",
99
- \\"bindValues\\": {},
100
98
  \\"writeTables\\": [
101
99
  \\"todos\\"
102
100
  ]
@@ -165,10 +163,7 @@ describe('otel', () => {
165
163
  const defaultTodo = { id: '', text: '', completed: 0 }
166
164
 
167
165
  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({
166
+ const query = querySQL((get) => `select * from todos ${get(filter)}`, { label: 'all todos' }).getFirstRow({
172
167
  defaultValue: defaultTodo,
173
168
  })
174
169
 
@@ -180,9 +175,8 @@ describe('otel', () => {
180
175
  }
181
176
  `)
182
177
 
183
- store.applyEvent('RawSql', {
178
+ store.applyEvent('livestore.RawSql', {
184
179
  sql: sql`INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);`,
185
- bindValues: {},
186
180
  writeTables: ['todos'],
187
181
  })
188
182
 
@@ -238,10 +232,9 @@ describe('otel', () => {
238
232
  {
239
233
  "_name": "LiveStore:applyEventWithoutRefresh",
240
234
  "attributes": {
241
- "livestore.actionType": "RawSql",
235
+ "livestore.actionType": "livestore.RawSql",
242
236
  "livestore.args": "{
243
237
  \\"sql\\": \\"INSERT INTO todos (id, text, completed) VALUES ('t1', 'buy milk', 0);\\",
244
- \\"bindValues\\": {},
245
238
  \\"writeTables\\": [
246
239
  \\"todos\\"
247
240
  ]
@@ -5,7 +5,7 @@ import type { GraphQLSchema } from 'graphql'
5
5
  import initSqlite3Wasm from 'sqlite-esm'
6
6
 
7
7
  import type { InMemoryDatabase } from '../inMemoryDatabase.js'
8
- import type { Schema } from '../schema.js'
8
+ import type { LiveStoreSchema } from '../schema/index.js'
9
9
  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'
@@ -25,7 +25,7 @@ export type LiveStoreContext = {
25
25
  export type QueryDefinition = (store: Store) => LiveStoreQuery
26
26
 
27
27
  export type LiveStoreCreateStoreOptions<GraphQLContext extends BaseGraphQLContext> = {
28
- schema: Schema
28
+ schema: LiveStoreSchema
29
29
  loadStorage: () => StorageInit | Promise<StorageInit>
30
30
  graphQLOptions?: GraphQLOptions<GraphQLContext>
31
31
  otelTracer?: otel.Tracer
@@ -43,7 +43,7 @@ export const DeferredStoreContext = Context.Tag<DeferredStoreContext>(
43
43
  // export const DeferredStoreContext = Effect.cached(Effect.flatMap(StoreContext, (_) => Effect.succeed(_)))
44
44
 
45
45
  export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
46
- schema: Schema
46
+ schema: LiveStoreSchema
47
47
  loadStorage: () => StorageInit | Promise<StorageInit>
48
48
  graphQLOptions?: {
49
49
  schema: Effect.Effect<otel.Tracer, never, GraphQLSchema>
@@ -55,9 +55,9 @@ export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
55
55
  export const LiveStoreContextLayer = <GraphQLContext extends BaseGraphQLContext>(
56
56
  props: LiveStoreContextProps<GraphQLContext>,
57
57
  ): Layer.Layer<otel.Tracer, never, LiveStoreContext> =>
58
- Layer.provide(
59
- LiveStoreContextDeferred,
60
- Layer.scoped(LiveStoreContext, makeLiveStoreContext(props)).pipe(Layer.withSpan('LiveStore')),
58
+ Layer.scoped(LiveStoreContext, makeLiveStoreContext(props)).pipe(
59
+ Layer.withSpan('LiveStore'),
60
+ Layer.provide(LiveStoreContextDeferred),
61
61
  )
62
62
 
63
63
  export const LiveStoreContextDeferred = Layer.effect(DeferredStoreContext, Deferred.make<never, LiveStoreContext>())
@@ -78,7 +78,7 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
78
78
 
79
79
  const otelRootSpanContext = otel.context.active()
80
80
 
81
- const otelTracer = yield* $(OtelTracer.OtelTracer)
81
+ const otelTracer = yield* $(OtelTracer.Tracer)
82
82
 
83
83
  const graphQLOptions = yield* $(
84
84
  graphQLOptions_
@@ -0,0 +1,26 @@
1
+ /**
2
+ *
3
+ * LiveStore currently relies on some global state in order to simplify the end-user API.
4
+ * This however also has the downside that LiveStore can't be used in multiple instances in the same app.
5
+ * It could possibly also lead to some other problems.
6
+ *
7
+ * We should find some better way to do this and ideally remove this global state.
8
+ *
9
+ * Another approach could be to use the global state by default but provide an additional way to let the user
10
+ * explicitly pass instances of state below into the LiveStore constructors.
11
+ *
12
+ */
13
+
14
+ import ReactDOM from 'react-dom'
15
+
16
+ import { ReactiveGraph } from './reactive.js'
17
+ import type { DbContext } from './reactiveQueries/base-class.js'
18
+ import type { TableDef } from './schema/table-def.js'
19
+ import type { QueryDebugInfo, RefreshReason } from './store.js'
20
+
21
+ export const dbGraph = new ReactiveGraph<RefreshReason, QueryDebugInfo, DbContext>({
22
+ // TODO also find a better way to only use this effects wrapper when used in a React app
23
+ effectsWrapper: (run) => ReactDOM.unstable_batchedUpdates(() => run()),
24
+ })
25
+
26
+ export const dynamicallyRegisteredTables: Map<string, TableDef> = new Map()
@@ -4,12 +4,12 @@ import { shouldNeverHappen } from '@livestore/utils'
4
4
  import type * as otel from '@opentelemetry/api'
5
5
  import type * as Sqlite from 'sqlite-esm'
6
6
 
7
- import BoundMap, { BoundArray } from './bounded-collections.js'
8
7
  // import { EVENTS_TABLE_NAME } from './events.js'
9
8
  import { sql } from './index.js'
10
- import { getDurationMsFromSpan, getStartTimeHighResFromSpan } from './otel.js'
11
9
  import QueryCache from './QueryCache.js'
12
- import type { Bindable, PreparedBindValues } from './util.js'
10
+ import BoundMap, { BoundArray } from './utils/bounded-collections.js'
11
+ import { getDurationMsFromSpan, getStartTimeHighResFromSpan } from './utils/otel.js'
12
+ import type { Bindable, PreparedBindValues } from './utils/util.js'
13
13
 
14
14
  type DatabaseWithCAPI = Sqlite.Database & { capi: Sqlite.CAPI }
15
15
 
@@ -25,7 +25,7 @@ export type SlowQueryInfo = [
25
25
  bindValues: PreparedBindValues | undefined,
26
26
  durationMs: number,
27
27
  rowsCount: number | undefined,
28
- queriedTables: ReadonlyArray<string>,
28
+ queriedTables: Set<string>,
29
29
  startTimePerfNow: DOMHighResTimeStamp,
30
30
  ]
31
31
 
@@ -39,7 +39,7 @@ export const emptyDebugInfo = (): DebugInfo => ({
39
39
  export class InMemoryDatabase {
40
40
  // TODO: how many unique active statements are expected?
41
41
  private cachedStmts = new BoundMap<string, Sqlite.PreparedStatement>(200)
42
- private tablesUsedCache = new BoundMap<string, string[]>(200)
42
+ private tablesUsedCache = new BoundMap<string, Set<string>>(200)
43
43
  private resultCache = new QueryCache()
44
44
  private tablesUsedStmt
45
45
  public debugInfo: DebugInfo = emptyDebugInfo()
@@ -119,25 +119,27 @@ export class InMemoryDatabase {
119
119
  return cached
120
120
  }
121
121
  const stmt = this.tablesUsedStmt
122
- const tablesUsed = []
122
+ const tablesUsed = new Set<string>()
123
123
  try {
124
124
  stmt.bind([query])
125
125
  while (stmt.step()) {
126
- tablesUsed.push(stmt.get(0))
126
+ tablesUsed.add(stmt.get(0))
127
127
  }
128
128
  } finally {
129
129
  stmt.reset()
130
130
  }
131
- this.tablesUsedCache.set(query, tablesUsed as string[])
132
- return tablesUsed as string[]
131
+ this.tablesUsedCache.set(query, tablesUsed)
132
+ return tablesUsed
133
133
  }
134
134
 
135
135
  execute(
136
136
  query: string,
137
137
  bindValues?: PreparedBindValues,
138
- writeTables?: string[],
138
+ writeTables?: ReadonlyArray<string>,
139
139
  options?: { hasNoEffects?: boolean; otelContext?: otel.Context },
140
140
  ): { durationMs: number } {
141
+ // console.debug('in-memory-db:execute', query, bindValues)
142
+
141
143
  return this.otelTracer.startActiveSpan(
142
144
  'livestore.in-memory-db:execute',
143
145
  // TODO truncate query string
@@ -192,7 +194,7 @@ export class InMemoryDatabase {
192
194
  bindValues,
193
195
  durationMs,
194
196
  undefined,
195
- [],
197
+ new Set(),
196
198
  getStartTimeHighResFromSpan(span),
197
199
  ])
198
200
  }
@@ -205,7 +207,7 @@ export class InMemoryDatabase {
205
207
  select<T = any>(
206
208
  query: string,
207
209
  options?: {
208
- queriedTables?: ReadonlyArray<string>
210
+ queriedTables?: Set<string>
209
211
  bindValues?: PreparedBindValues
210
212
  skipCache?: boolean
211
213
  otelContext?: otel.Context
package/src/index.ts CHANGED
@@ -3,41 +3,34 @@ export type { LiveStoreQuery, BaseGraphQLContext, QueryResult, QueryDebugInfo, R
3
3
 
4
4
  export type { QueryDefinition, LiveStoreCreateStoreOptions, LiveStoreContext } from './effect/LiveStore.js'
5
5
 
6
- export {
7
- defineComponentStateSchema,
8
- defineAction,
9
- defineActions,
10
- defineTables,
11
- defineMaterializedViews,
12
- makeSchema,
13
- } from './schema.js'
14
6
  export { InMemoryDatabase, type DebugInfo, emptyDebugInfo } from './inMemoryDatabase.js'
7
+
15
8
  export type { Storage, StorageType, StorageInit } from './storage/index.js'
16
- export type {
17
- GetAtom,
18
- AtomDebugInfo,
19
- RefreshDebugInfo,
20
- RefreshReasonWithGenericReasons,
21
- SerializedAtom,
22
- SerializedEffect,
23
- Atom,
24
- } from './reactive.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'
9
+
10
+ export type { GetAtom, AtomDebugInfo, RefreshDebugInfo, SerializedAtom, Atom } from './reactive.js'
11
+ export { LiveStoreJSQuery, queryJS } from './reactiveQueries/js.js'
12
+ export { LiveStoreSQLQuery, querySQL } from './reactiveQueries/sql.js'
13
+ export { LiveStoreGraphQLQuery, queryGraphQL } from './reactiveQueries/graphql.js'
28
14
  export { type GetAtomResult } from './reactiveQueries/base-class.js'
29
- export { dbGraph } from './reactiveQueries/graph.js'
30
15
 
31
- export { labelForKey } from './componentKey.js'
32
- export type { ComponentKey } from './componentKey.js'
33
- export type { Schema, GetActionArgs, GetApplyEventArgs, Index, ActionDefinition, ActionDefinitions } from './schema.js'
16
+ export { dbGraph } from './global-state.js'
34
17
 
35
- export { SqliteAst, SqliteDsl } from 'effect-db-schema'
18
+ export { type RowResult, type RowResultEncoded, type RowQueryArgs, rowQuery } from './row-query.js'
36
19
 
37
- import type { SqliteAst } from 'effect-db-schema'
38
- export type TableDefinition = SqliteAst.Table
20
+ export { defineAction, defineActions, makeSchema, DbSchema } from './schema/index.js'
39
21
 
40
- export { SqliteDsl as DbSchema } from 'effect-db-schema'
22
+ export type {
23
+ LiveStoreSchema,
24
+ InputSchema,
25
+ GetActionArgs,
26
+ GetApplyEventArgs,
27
+ ActionDefinition,
28
+ ActionDefinitions,
29
+ SQLWriteStatement,
30
+ SchemaMetaRow,
31
+ } from './schema/index.js'
32
+
33
+ export { SqliteAst, SqliteDsl } from 'effect-db-schema'
41
34
 
42
- export { prepareBindValues, sql, type Bindable, type PreparedBindValues } from './util.js'
35
+ export { prepareBindValues, sql, type Bindable, type PreparedBindValues } from './utils/util.js'
43
36
  export { isEqual } from 'lodash-es'
package/src/migrations.ts CHANGED
@@ -1,12 +1,14 @@
1
+ import { Schema as EffectSchema } from '@livestore/utils/effect'
1
2
  import type * as otel from '@opentelemetry/api'
2
3
  import { SqliteAst } from 'effect-db-schema'
3
- import { memoize, omit } from 'lodash-es'
4
+ import { memoize } from 'lodash-es'
4
5
 
6
+ import { dynamicallyRegisteredTables } from './global-state.js'
5
7
  import type { InMemoryDatabase } from './index.js'
6
- import type { Schema, SchemaMetaRow } from './schema.js'
7
- import { componentStateTables, SCHEMA_META_TABLE, systemTables } from './schema.js'
8
- import type { PreparedBindValues } from './util.js'
9
- import { sql } from './util.js'
8
+ import type { LiveStoreSchema, SchemaMetaRow } from './schema/index.js'
9
+ import { SCHEMA_META_TABLE, systemTables } from './schema/index.js'
10
+ import type { PreparedBindValues } from './utils/util.js'
11
+ import { sql } from './utils/util.js'
10
12
 
11
13
  const getMemoizedTimestamp = memoize(() => new Date().toISOString())
12
14
 
@@ -18,7 +20,7 @@ export const migrateDb = ({
18
20
  }: {
19
21
  db: InMemoryDatabase
20
22
  otelContext: otel.Context
21
- schema: Schema
23
+ schema: LiveStoreSchema
22
24
  }) => {
23
25
  db.execute(
24
26
  // TODO use schema migration definition from schema.ts instead
@@ -34,16 +36,18 @@ export const migrateDb = ({
34
36
  schemaMetaRows.map(({ tableName, schemaHash }) => [tableName, schemaHash]),
35
37
  )
36
38
 
37
- const tableDefs = {
39
+ const tableDefs = new Set([
38
40
  // NOTE it's important the `SCHEMA_META_TABLE` comes first since we're writing to it below
39
- [SCHEMA_META_TABLE]: systemTables[SCHEMA_META_TABLE],
40
- ...omit(schema.tables, [SCHEMA_META_TABLE]),
41
- ...componentStateTables,
42
- }
43
-
44
- for (const [tableName, tableDef] of Object.entries(tableDefs)) {
41
+ ...systemTables,
42
+ ...Array.from(schema.tables.values()).filter((_) => _.schema.name !== SCHEMA_META_TABLE),
43
+ ...dynamicallyRegisteredTables.values(),
44
+ ])
45
+
46
+ for (const tableDef of tableDefs) {
47
+ const tableAst = tableDef.schema.ast
48
+ const tableName = tableAst.name
45
49
  const dbSchemaHash = dbSchemaHashByTable[tableName]
46
- const schemaHash = SqliteAst.hash(tableDef)
50
+ const schemaHash = SqliteAst.hash(tableAst)
47
51
  if (schemaHash !== dbSchemaHash) {
48
52
  if (import.meta.env.VITE_LIVESTORE_SKIP_MIGRATIONS) {
49
53
  console.log(
@@ -54,7 +58,7 @@ export const migrateDb = ({
54
58
  `Schema hash mismatch for table '${tableName}' (DB: ${dbSchemaHash}, expected: ${schemaHash}), migrating table...`,
55
59
  )
56
60
 
57
- migrateTable({ db, tableDef, otelContext, schemaHash })
61
+ migrateTable({ db, tableAst, otelContext, schemaHash })
58
62
  }
59
63
  }
60
64
  }
@@ -62,24 +66,24 @@ export const migrateDb = ({
62
66
 
63
67
  export const migrateTable = ({
64
68
  db,
65
- tableDef,
69
+ tableAst,
66
70
  otelContext,
67
71
  schemaHash,
68
72
  }: {
69
73
  db: InMemoryDatabase
70
- tableDef: SqliteAst.Table
74
+ tableAst: SqliteAst.Table
71
75
  otelContext: otel.Context
72
76
  schemaHash: number
73
77
  }) => {
74
- console.log(`Migrating table '${tableDef.name}'...`)
75
- const tableName = tableDef.name
76
- const columnSpec = makeColumnSpec(tableDef)
78
+ console.log(`Migrating table '${tableAst.name}'...`)
79
+ const tableName = tableAst.name
80
+ const columnSpec = makeColumnSpec(tableAst)
77
81
 
78
82
  // TODO need to possibly handle cascading deletes due to foreign keys
79
83
  db.execute(sql`drop table if exists ${tableName}`, undefined, [], { otelContext })
80
84
  db.execute(sql`create table if not exists ${tableName} (${columnSpec});`, undefined, [], { otelContext })
81
85
 
82
- for (const index of tableDef.indexes) {
86
+ for (const index of tableAst.indexes) {
83
87
  db.execute(createIndexFromDefinition(tableName, index), undefined, [], { otelContext })
84
88
  }
85
89
 
@@ -100,9 +104,9 @@ const createIndexFromDefinition = (tableName: string, index: SqliteAst.Index) =>
100
104
  return sql`create ${uniqueStr} index ${index.name} on ${tableName} (${index.columns.join(', ')})`
101
105
  }
102
106
 
103
- const makeColumnSpec = (tableDef: SqliteAst.Table) => {
104
- const primaryKeys = tableDef.columns.filter((_) => _.primaryKey).map((_) => _.name)
105
- const columnDefStrs = tableDef.columns.map(toSqliteColumnSpec)
107
+ const makeColumnSpec = (tableAst: SqliteAst.Table) => {
108
+ const primaryKeys = tableAst.columns.filter((_) => _.primaryKey).map((_) => _.name)
109
+ const columnDefStrs = tableAst.columns.map(toSqliteColumnSpec)
106
110
  if (primaryKeys.length > 0) {
107
111
  columnDefStrs.push(`PRIMARY KEY (${primaryKeys.join(', ')})`)
108
112
  }
@@ -110,16 +114,18 @@ const makeColumnSpec = (tableDef: SqliteAst.Table) => {
110
114
  return columnDefStrs.join(', ')
111
115
  }
112
116
 
117
+ /** NOTE primary keys are applied on a table level not on a column level to account for multi-column primary keys */
113
118
  const toSqliteColumnSpec = (column: SqliteAst.Column) => {
114
- const columnType = column.type._tag
115
- // const primaryKey = column.primaryKey ? 'primary key' : ''
116
- const nullable = column.nullable === false ? 'not null' : ''
117
- const defaultValue =
118
- column.default === undefined
119
- ? ''
120
- : columnType === 'text'
121
- ? `default '${column.default}'`
122
- : `default ${column.default}`
123
-
124
- return `${column.name} ${columnType} ${nullable} ${defaultValue}`
119
+ const columnTypeStr = column.type._tag
120
+ const nullableStr = column.nullable === false ? 'not null' : ''
121
+ const defaultValueStr = (() => {
122
+ if (column.default === undefined) return ''
123
+
124
+ const encodeValue = EffectSchema.encodeSync(column.codec)
125
+ const encodedDefaultValue = encodeValue(column.default ?? null)
126
+
127
+ return columnTypeStr === 'text' ? `default '${encodedDefaultValue}'` : `default ${encodedDefaultValue}`
128
+ })()
129
+
130
+ return `${column.name} ${columnTypeStr} ${nullableStr} ${defaultValueStr}`
125
131
  }
@@ -6,7 +6,7 @@ import initSqlite3Wasm from 'sqlite-esm'
6
6
  // TODO refactor so the `react` module doesn't depend on `effect` module
7
7
  import type { LiveStoreContext as StoreContext_, LiveStoreCreateStoreOptions } from '../effect/LiveStore.js'
8
8
  import type { InMemoryDatabase } from '../inMemoryDatabase.js'
9
- import type { Schema } from '../schema.js'
9
+ import type { LiveStoreSchema } from '../schema/index.js'
10
10
  import type { StorageInit } from '../storage/index.js'
11
11
  import type { BaseGraphQLContext, GraphQLOptions } from '../store.js'
12
12
  import { createStore } from '../store.js'
@@ -20,7 +20,7 @@ const sqlite3Promise = initSqlite3Wasm({
20
20
  })
21
21
 
22
22
  interface LiveStoreProviderProps<GraphQLContext> {
23
- schema: Schema
23
+ schema: LiveStoreSchema
24
24
  loadStorage: () => StorageInit | Promise<StorageInit>
25
25
  boot?: (db: InMemoryDatabase, parentSpan: otel.Span) => unknown | Promise<unknown>
26
26
  graphQLOptions?: GraphQLOptions<GraphQLContext>
@@ -1,17 +1,15 @@
1
- export type {
2
- Setters,
3
- ComponentKeyConfig,
4
- QueryDefinitions,
5
- ComponentColumns,
6
- GetStateType,
7
- GetStateTypeEncoded,
8
- } from './useComponentState.js'
9
1
  export { LiveStoreContext, useStore } from './LiveStoreContext.js'
10
2
  export { LiveStoreProvider } from './LiveStoreProvider.js'
11
- export { useComponentState } from './useComponentState.js'
12
3
  export { useQuery } from './useQuery.js'
13
4
  export { useTemporaryQuery } from './useTemporaryQuery.js'
14
5
  export { useStackInfo } from './utils/stack-info.js'
6
+ export {
7
+ useRow,
8
+ type StateSetters,
9
+ type SetStateAction,
10
+ type Dispatch,
11
+ type UseRowResult as UseStateResult,
12
+ } from './useRow.js'
15
13
 
16
14
  // Needed to make TS happy
17
15
  export type { TypedDocumentNode } from '@graphql-typed-document-node/core'
@@ -6,6 +6,7 @@ import type { ILiveStoreQuery } from '../reactiveQueries/base-class.js'
6
6
  import { useStore } from './LiveStoreContext.js'
7
7
  import { extractStackInfoFromStackTrace, originalStackLimit } from './utils/stack-info.js'
8
8
  import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInput.js'
9
+
9
10
  /**
10
11
  * This is needed because the `React.useMemo` call below, can sometimes be called multiple times 🤷,
11
12
  * so we need to "cache" the fact that we've already started a span for this component.
@@ -13,7 +14,12 @@ import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInp
13
14
  */
14
15
  const spanAlreadyStartedCache = new Map<ILiveStoreQuery<any>, { span: otel.Span; otelContext: otel.Context }>()
15
16
 
16
- export const useQuery = <TResult>(query: ILiveStoreQuery<TResult>): TResult => {
17
+ export const useQuery = <TResult>(query: ILiveStoreQuery<TResult>): TResult => useQueryRef(query).current
18
+
19
+ export const useQueryRef = <TResult>(
20
+ query: ILiveStoreQuery<TResult>,
21
+ parentOtelContext?: otel.Context,
22
+ ): React.MutableRefObject<TResult> => {
17
23
  const { store } = useStore()
18
24
 
19
25
  const stackInfo = React.useMemo(() => {
@@ -32,7 +38,7 @@ export const useQuery = <TResult>(query: ILiveStoreQuery<TResult>): TResult => {
32
38
  const span = store.otel.tracer.startSpan(
33
39
  `LiveStore:useQuery:${query.label}`,
34
40
  { attributes: { label: query.label, stackInfo: JSON.stringify(stackInfo) } },
35
- store.otel.queriesSpanContext,
41
+ parentOtelContext ?? store.otel.queriesSpanContext,
36
42
  )
37
43
 
38
44
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
@@ -40,7 +46,7 @@ export const useQuery = <TResult>(query: ILiveStoreQuery<TResult>): TResult => {
40
46
  spanAlreadyStartedCache.set(query, { span, otelContext })
41
47
 
42
48
  return { span, otelContext }
43
- }, [query, stackInfo, store.otel.queriesSpanContext, store.otel.tracer])
49
+ }, [parentOtelContext, query, stackInfo, store.otel.queriesSpanContext, store.otel.tracer])
44
50
 
45
51
  const initialResult = React.useMemo(
46
52
  () =>
@@ -67,7 +73,7 @@ export const useQuery = <TResult>(query: ILiveStoreQuery<TResult>): TResult => {
67
73
  // Subscribe to future updates for this query
68
74
  React.useEffect(() => {
69
75
  query.activeSubscriptions.add(stackInfo)
70
- const unsub = store.subscribe(
76
+ const unsubFromStore = store.subscribe(
71
77
  query,
72
78
  (newValue) => {
73
79
  // NOTE: we return a reference to the result object within LiveStore;
@@ -82,9 +88,9 @@ export const useQuery = <TResult>(query: ILiveStoreQuery<TResult>): TResult => {
82
88
  )
83
89
  return () => {
84
90
  query.activeSubscriptions.delete(stackInfo)
85
- unsub()
91
+ unsubFromStore()
86
92
  }
87
93
  }, [stackInfo, query, setValue, store, valueRef, otelContext, span])
88
94
 
89
- return valueRef.current
95
+ return valueRef
90
96
  }