@livestore/livestore 0.0.12 → 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 (226) hide show
  1. package/README.md +25 -28
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/QueryCache.d.ts +20 -0
  4. package/dist/QueryCache.d.ts.map +1 -0
  5. package/dist/QueryCache.js +71 -0
  6. package/dist/QueryCache.js.map +1 -0
  7. package/dist/__tests__/react/fixture.d.ts +26 -0
  8. package/dist/__tests__/react/fixture.d.ts.map +1 -0
  9. package/dist/__tests__/react/fixture.js +60 -0
  10. package/dist/__tests__/react/fixture.js.map +1 -0
  11. package/dist/__tests__/react/useComponentState.test.d.ts +2 -0
  12. package/dist/__tests__/react/useComponentState.test.d.ts.map +1 -0
  13. package/dist/__tests__/react/useComponentState.test.js +68 -0
  14. package/dist/__tests__/react/useComponentState.test.js.map +1 -0
  15. package/dist/__tests__/react/useLQuery.test.d.ts +2 -0
  16. package/dist/__tests__/react/useLQuery.test.d.ts.map +1 -0
  17. package/dist/__tests__/react/useLQuery.test.js +38 -0
  18. package/dist/__tests__/react/useLQuery.test.js.map +1 -0
  19. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts +2 -0
  20. package/dist/__tests__/react/useLiveStoreComponent.test.d.ts.map +1 -0
  21. package/dist/__tests__/react/useLiveStoreComponent.test.js +73 -0
  22. package/dist/__tests__/react/useLiveStoreComponent.test.js.map +1 -0
  23. package/dist/__tests__/react/useQuery.test.d.ts +2 -0
  24. package/dist/__tests__/react/useQuery.test.d.ts.map +1 -0
  25. package/dist/__tests__/react/useQuery.test.js +33 -0
  26. package/dist/__tests__/react/useQuery.test.js.map +1 -0
  27. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts +2 -0
  28. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.d.ts.map +1 -0
  29. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js +38 -0
  30. package/dist/__tests__/react/utils/extractStackInfoFromStackTrace.test.js.map +1 -0
  31. package/dist/__tests__/reactive.test.d.ts +2 -0
  32. package/dist/__tests__/reactive.test.d.ts.map +1 -0
  33. package/dist/__tests__/reactive.test.js +271 -0
  34. package/dist/__tests__/reactive.test.js.map +1 -0
  35. package/dist/__tests__/reactiveQueries/sql.test.d.ts +2 -0
  36. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +1 -0
  37. package/dist/__tests__/reactiveQueries/sql.test.js +337 -0
  38. package/dist/__tests__/reactiveQueries/sql.test.js.map +1 -0
  39. package/dist/bounded-collections.d.ts +34 -0
  40. package/dist/bounded-collections.d.ts.map +1 -0
  41. package/dist/bounded-collections.js +103 -0
  42. package/dist/bounded-collections.js.map +1 -0
  43. package/dist/componentKey.d.ts +20 -0
  44. package/dist/componentKey.d.ts.map +1 -0
  45. package/dist/componentKey.js +3 -0
  46. package/dist/componentKey.js.map +1 -0
  47. package/dist/effect/LiveStore.d.ts +36 -0
  48. package/dist/effect/LiveStore.d.ts.map +1 -0
  49. package/dist/effect/LiveStore.js +41 -0
  50. package/dist/effect/LiveStore.js.map +1 -0
  51. package/dist/effect/index.d.ts +2 -0
  52. package/dist/effect/index.d.ts.map +1 -0
  53. package/dist/effect/index.js +2 -0
  54. package/dist/effect/index.js.map +1 -0
  55. package/dist/events.d.ts +7 -0
  56. package/dist/events.d.ts.map +1 -0
  57. package/dist/events.js +2 -0
  58. package/dist/events.js.map +1 -0
  59. package/dist/inMemoryDatabase.d.ts +56 -0
  60. package/dist/inMemoryDatabase.d.ts.map +1 -0
  61. package/dist/inMemoryDatabase.js +223 -0
  62. package/dist/inMemoryDatabase.js.map +1 -0
  63. package/dist/index.d.ts +22 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +13 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/migrations.d.ts +16 -0
  68. package/dist/migrations.d.ts.map +1 -0
  69. package/dist/migrations.js +67 -0
  70. package/dist/migrations.js.map +1 -0
  71. package/dist/otel.d.ts +4 -0
  72. package/dist/otel.d.ts.map +1 -0
  73. package/dist/otel.js +6 -0
  74. package/dist/otel.js.map +1 -0
  75. package/dist/react/LiveStoreContext.d.ts +11 -0
  76. package/dist/react/LiveStoreContext.d.ts.map +1 -0
  77. package/dist/react/LiveStoreContext.js +10 -0
  78. package/dist/react/LiveStoreContext.js.map +1 -0
  79. package/dist/react/LiveStoreProvider.d.ts +20 -0
  80. package/dist/react/LiveStoreProvider.d.ts.map +1 -0
  81. package/dist/react/LiveStoreProvider.js +52 -0
  82. package/dist/react/LiveStoreProvider.js.map +1 -0
  83. package/dist/react/index.d.ts +8 -0
  84. package/dist/react/index.d.ts.map +1 -0
  85. package/dist/react/index.js +6 -0
  86. package/dist/react/index.js.map +1 -0
  87. package/dist/react/useComponentState.d.ts +50 -0
  88. package/dist/react/useComponentState.d.ts.map +1 -0
  89. package/dist/react/useComponentState.js +248 -0
  90. package/dist/react/useComponentState.js.map +1 -0
  91. package/dist/react/useGlobalQuery.d.ts +3 -0
  92. package/dist/react/useGlobalQuery.d.ts.map +1 -0
  93. package/dist/react/useGlobalQuery.js +26 -0
  94. package/dist/react/useGlobalQuery.js.map +1 -0
  95. package/dist/react/useGraphQL.d.ts +13 -0
  96. package/dist/react/useGraphQL.d.ts.map +1 -0
  97. package/dist/react/useGraphQL.js +87 -0
  98. package/dist/react/useGraphQL.js.map +1 -0
  99. package/dist/react/useLiveStoreComponent.d.ts +75 -0
  100. package/dist/react/useLiveStoreComponent.d.ts.map +1 -0
  101. package/dist/react/useLiveStoreComponent.js +361 -0
  102. package/dist/react/useLiveStoreComponent.js.map +1 -0
  103. package/dist/react/useQuery.d.ts +3 -0
  104. package/dist/react/useQuery.d.ts.map +1 -0
  105. package/dist/react/useQuery.js +42 -0
  106. package/dist/react/useQuery.js.map +1 -0
  107. package/dist/react/useTemporaryQuery.d.ts +8 -0
  108. package/dist/react/useTemporaryQuery.d.ts.map +1 -0
  109. package/dist/react/useTemporaryQuery.js +17 -0
  110. package/dist/react/useTemporaryQuery.js.map +1 -0
  111. package/dist/react/utils/extractNamesFromStackTrace.d.ts +3 -0
  112. package/dist/react/utils/extractNamesFromStackTrace.d.ts.map +1 -0
  113. package/dist/react/utils/extractNamesFromStackTrace.js +40 -0
  114. package/dist/react/utils/extractNamesFromStackTrace.js.map +1 -0
  115. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts +7 -0
  116. package/dist/react/utils/extractStackInfoFromStackTrace.d.ts.map +1 -0
  117. package/dist/react/utils/extractStackInfoFromStackTrace.js +40 -0
  118. package/dist/react/utils/extractStackInfoFromStackTrace.js.map +1 -0
  119. package/dist/react/utils/useStateRefWithReactiveInput.d.ts +13 -0
  120. package/dist/react/utils/useStateRefWithReactiveInput.d.ts.map +1 -0
  121. package/dist/react/utils/useStateRefWithReactiveInput.js +38 -0
  122. package/dist/react/utils/useStateRefWithReactiveInput.js.map +1 -0
  123. package/dist/reactive.d.ts +134 -0
  124. package/dist/reactive.d.ts.map +1 -0
  125. package/dist/reactive.js +409 -0
  126. package/dist/reactive.js.map +1 -0
  127. package/dist/reactiveQueries/base-class.d.ts +32 -0
  128. package/dist/reactiveQueries/base-class.d.ts.map +1 -0
  129. package/dist/reactiveQueries/base-class.js +30 -0
  130. package/dist/reactiveQueries/base-class.js.map +1 -0
  131. package/dist/reactiveQueries/graph.d.ts +10 -0
  132. package/dist/reactiveQueries/graph.d.ts.map +1 -0
  133. package/dist/reactiveQueries/graph.js +6 -0
  134. package/dist/reactiveQueries/graph.js.map +1 -0
  135. package/dist/reactiveQueries/graphql.d.ts +42 -0
  136. package/dist/reactiveQueries/graphql.d.ts.map +1 -0
  137. package/dist/reactiveQueries/graphql.js +99 -0
  138. package/dist/reactiveQueries/graphql.js.map +1 -0
  139. package/dist/reactiveQueries/js.d.ts +23 -0
  140. package/dist/reactiveQueries/js.d.ts.map +1 -0
  141. package/dist/reactiveQueries/js.js +36 -0
  142. package/dist/reactiveQueries/js.js.map +1 -0
  143. package/dist/reactiveQueries/sql.d.ts +35 -0
  144. package/dist/reactiveQueries/sql.d.ts.map +1 -0
  145. package/dist/reactiveQueries/sql.js +97 -0
  146. package/dist/reactiveQueries/sql.js.map +1 -0
  147. package/dist/schema.d.ts +81 -0
  148. package/dist/schema.d.ts.map +1 -0
  149. package/dist/schema.js +46 -0
  150. package/dist/schema.js.map +1 -0
  151. package/dist/storage/in-memory/index.d.ts +15 -0
  152. package/dist/storage/in-memory/index.d.ts.map +1 -0
  153. package/dist/storage/in-memory/index.js +14 -0
  154. package/dist/storage/in-memory/index.js.map +1 -0
  155. package/dist/storage/index.d.ts +14 -0
  156. package/dist/storage/index.d.ts.map +1 -0
  157. package/dist/storage/index.js +9 -0
  158. package/dist/storage/index.js.map +1 -0
  159. package/dist/storage/tauri/index.d.ts +19 -0
  160. package/dist/storage/tauri/index.d.ts.map +1 -0
  161. package/dist/storage/tauri/index.js +38 -0
  162. package/dist/storage/tauri/index.js.map +1 -0
  163. package/dist/storage/utils/idb.d.ts +10 -0
  164. package/dist/storage/utils/idb.d.ts.map +1 -0
  165. package/dist/storage/utils/idb.js +58 -0
  166. package/dist/storage/utils/idb.js.map +1 -0
  167. package/dist/storage/web-worker/index.d.ts +27 -0
  168. package/dist/storage/web-worker/index.d.ts.map +1 -0
  169. package/dist/storage/web-worker/index.js +74 -0
  170. package/dist/storage/web-worker/index.js.map +1 -0
  171. package/dist/storage/web-worker/worker.d.ts +13 -0
  172. package/dist/storage/web-worker/worker.d.ts.map +1 -0
  173. package/dist/storage/web-worker/worker.js +110 -0
  174. package/dist/storage/web-worker/worker.js.map +1 -0
  175. package/dist/store.d.ts +159 -0
  176. package/dist/store.d.ts.map +1 -0
  177. package/dist/store.js +626 -0
  178. package/dist/store.js.map +1 -0
  179. package/dist/util.d.ts +28 -0
  180. package/dist/util.d.ts.map +1 -0
  181. package/dist/util.js +55 -0
  182. package/dist/util.js.map +1 -0
  183. package/package.json +47 -19
  184. package/src/QueryCache.ts +1 -1
  185. package/src/__tests__/react/fixture.tsx +35 -39
  186. package/src/__tests__/react/{useLiveStoreComponent.test.tsx → useComponentState.test.tsx} +9 -20
  187. package/src/__tests__/react/useQuery.test.tsx +48 -0
  188. package/src/__tests__/react/utils/extractStackInfoFromStackTrace.test.ts +40 -0
  189. package/src/__tests__/reactive.test.ts +194 -142
  190. package/src/__tests__/reactiveQueries/sql.test.ts +372 -0
  191. package/src/effect/LiveStore.ts +22 -31
  192. package/src/events.ts +1 -1
  193. package/src/inMemoryDatabase.ts +117 -142
  194. package/src/index.ts +18 -22
  195. package/src/migrations.ts +119 -0
  196. package/src/otel.ts +0 -11
  197. package/src/react/LiveStoreProvider.tsx +24 -23
  198. package/src/react/index.ts +12 -7
  199. package/src/react/useComponentState.ts +409 -0
  200. package/src/react/useQuery.ts +58 -0
  201. package/src/react/useTemporaryQuery.ts +21 -0
  202. package/src/react/utils/extractStackInfoFromStackTrace.ts +47 -0
  203. package/src/reactive.ts +386 -267
  204. package/src/reactiveQueries/base-class.ts +61 -39
  205. package/src/reactiveQueries/graph.ts +15 -0
  206. package/src/reactiveQueries/graphql.ts +147 -31
  207. package/src/reactiveQueries/js.ts +54 -21
  208. package/src/reactiveQueries/sql.ts +128 -37
  209. package/src/schema.ts +69 -145
  210. package/src/storage/in-memory/index.ts +21 -0
  211. package/src/storage/index.ts +27 -0
  212. package/src/{backends/tauri.ts → storage/tauri/index.ts} +14 -28
  213. package/src/storage/web-worker/index.ts +116 -0
  214. package/src/{backends/web-worker.ts → storage/web-worker/worker.ts} +17 -52
  215. package/src/store.ts +466 -457
  216. package/src/util.ts +13 -3
  217. package/tsconfig.json +1 -3
  218. package/src/backends/base.ts +0 -67
  219. package/src/backends/index.ts +0 -98
  220. package/src/backends/noop.ts +0 -32
  221. package/src/backends/web-in-memory.ts +0 -65
  222. package/src/backends/web.ts +0 -97
  223. package/src/react/useGlobalQuery.ts +0 -40
  224. package/src/react/useGraphQL.ts +0 -112
  225. package/src/react/useLiveStoreComponent.ts +0 -483
  226. /package/src/{backends → storage}/utils/idb.ts +0 -0
package/src/util.ts CHANGED
@@ -1,8 +1,14 @@
1
+ /// <reference lib="es2022" />
2
+
3
+ import type { Brand } from '@livestore/utils/effect'
4
+
1
5
  export type ParamsObject = Record<string, SqlValue>
2
- export type SqlValue = string | number | Uint8Array
6
+ export type SqlValue = string | number | Uint8Array | null
3
7
 
4
8
  export type Bindable = SqlValue[] | ParamsObject
5
9
 
10
+ export type PreparedBindValues = Brand.Branded<Bindable, 'PreparedBindValues'>
11
+
6
12
  /**
7
13
  * This is a tag function for tagged literals.
8
14
  * it lets us get syntax highlighting on SQL queries in VSCode, but
@@ -23,7 +29,9 @@ export const sql = (template: TemplateStringsArray, ...args: unknown[]): string
23
29
  /* because rusqlite doesn't allow unused named params
24
30
  /* TODO: Search for unused params via proper parsing, not string search
25
31
  **/
26
- export const prepareBindValues = (values: ParamsObject, statement: string): ParamsObject => {
32
+ export const prepareBindValues = (values: Bindable, statement: string): PreparedBindValues => {
33
+ if (Array.isArray(values)) return values as PreparedBindValues
34
+
27
35
  const result: ParamsObject = {}
28
36
  for (const [key, value] of Object.entries(values)) {
29
37
  if (statement.includes(key)) {
@@ -31,7 +39,7 @@ export const prepareBindValues = (values: ParamsObject, statement: string): Para
31
39
  }
32
40
  }
33
41
 
34
- return result
42
+ return result as PreparedBindValues
35
43
  }
36
44
 
37
45
  /**
@@ -57,3 +65,5 @@ export const objectToString = (error: any): string => {
57
65
  return 'Error while printing error: ' + e
58
66
  }
59
67
  }
68
+
69
+ export const isPromise = (value: any): value is Promise<unknown> => typeof value?.then === 'function'
package/tsconfig.json CHANGED
@@ -9,7 +9,5 @@
9
9
  "tsBuildInfoFile": "./dist/.tsbuildinfo"
10
10
  },
11
11
  "include": ["./src"],
12
- "references": [
13
- { "path": "../utils" },
14
- ]
12
+ "references": [{ "path": "../../effect-db-schema" }, { "path": "../utils" }]
15
13
  }
@@ -1,67 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- import { errorToString } from '@livestore/utils'
3
- import { identity } from '@livestore/utils/effect'
4
- import * as otel from '@opentelemetry/api'
5
-
6
- import type { LiveStoreEvent } from '../events.js'
7
- // import { EVENTS_TABLE_NAME } from '../events.js'
8
- import type { ActionDefinition } from '../schema.js'
9
- import type { ParamsObject } from '../util.js'
10
- import type { Backend, SelectResponse } from './index.js'
11
-
12
- export abstract class BaseBackend implements Backend {
13
- abstract otelTracer: otel.Tracer
14
-
15
- select = async <T = any>(query: string, bindValues?: ParamsObject): Promise<SelectResponse<T>> => {
16
- throw new Error('Method not implemented.')
17
- }
18
-
19
- execute = (query: string, bindValues?: ParamsObject, parentSpan?: otel.Span): void => {
20
- throw new Error('Method not implemented.')
21
- }
22
-
23
- getPersistedData = async (parentSpan?: otel.Span): Promise<Uint8Array> => {
24
- throw new Error('Method not implemented.')
25
- }
26
-
27
- // TODO move `applyEvent` logic to Store and only call `execute` here
28
- applyEvent = (event: LiveStoreEvent, eventDefinition: ActionDefinition, parentSpan?: otel.Span): void => {
29
- const ctx = parentSpan ? otel.trace.setSpan(otel.context.active(), parentSpan) : otel.context.active()
30
- this.otelTracer.startActiveSpan('LiveStore:backend:applyEvent', {}, ctx, (span) => {
31
- try {
32
- // Careful: this SQL statement is duplicated in the backend.
33
- // Remember to update it in src-tauri/src/store.rs:apply_event as well.
34
- // await this.execute(sql`insert into ${EVENTS_TABLE_NAME} (id, type, args) values ($id, $type, $args)`, {
35
- // id: event.id,
36
- // type: event.type,
37
- // args: JSON.stringify(event.args ?? {}),
38
- // })
39
-
40
- const statement =
41
- typeof eventDefinition.statement === 'function'
42
- ? eventDefinition.statement(event.args)
43
- : eventDefinition.statement
44
-
45
- const prepareBindValues = eventDefinition.prepareBindValues ?? identity
46
-
47
- const bindValues =
48
- typeof eventDefinition.statement === 'function' && statement.argsAlreadyBound
49
- ? {}
50
- : prepareBindValues(event.args)
51
-
52
- span.setAttributes({
53
- 'livestore.statement.sql': statement.sql,
54
- 'livestore.statement.writeTables': statement.writeTables,
55
- 'livestore.statement.bindVales': JSON.stringify(bindValues),
56
- })
57
-
58
- this.execute(statement.sql, bindValues, span)
59
- } catch (e: any) {
60
- span.setStatus({ code: otel.SpanStatusCode.ERROR, message: errorToString(e) })
61
- throw e
62
- } finally {
63
- span.end()
64
- }
65
- })
66
- }
67
- }
@@ -1,98 +0,0 @@
1
- // A backend represents a raw SQLite database.
2
- // Examples include:
3
- // - A native SQLite process running in a Tauri Rust process
4
- // - A SQL.js WASM version of SQLite running in a web worker
5
- //
6
- // We can send commands to execute various kinds of queries,
7
- // and respond to various events from the database.
8
-
9
- import type * as otel from '@opentelemetry/api'
10
-
11
- import type { LiveStoreEvent } from '../events.js'
12
- import type { ActionDefinition } from '../schema.js'
13
- import type { ParamsObject } from '../util.js'
14
- import { casesHandled } from '../util.js'
15
- import type { BackendOptionsTauri } from './tauri.js'
16
- import type { BackendOptionsWeb } from './web.js'
17
- import { WebWorkerBackend } from './web.js'
18
- import type { BackendOptionsWebInMemory } from './web-in-memory.js'
19
- import { WebInMemoryBackend } from './web-in-memory.js'
20
-
21
- /* A location of a persistent writable SQLite file */
22
- export type WritableDatabaseLocation =
23
- | {
24
- type: 'opfs'
25
- virtualFilename: string
26
- }
27
- | {
28
- type: 'indexeddb'
29
- virtualFilename: string
30
- }
31
- | {
32
- type: 'filesystem'
33
- directory: string
34
- filename: string
35
- }
36
- | {
37
- type: 'volatile-in-memory'
38
- }
39
-
40
- export interface Backend {
41
- // Select some data from the DB.
42
- // This should only do reads, not writes, but we don't strongly enforce that.
43
- select<T = any>(query: string, bindValues?: ParamsObject, parentSpan?: otel.Span): Promise<SelectResponse<T>>
44
-
45
- // Execute a query where you don't care about the result.
46
- // Used for writes and configuration changes.
47
- execute(query: string, bindValues?: ParamsObject, parentSpan?: otel.Span): void
48
-
49
- /** Apply an event to the backend */
50
- applyEvent(event: LiveStoreEvent, eventDefiniton: ActionDefinition, parentSpan?: otel.Span): void
51
-
52
- /** Return a snapshot of persisted data from the backend */
53
- getPersistedData(parentSpan?: otel.Span): Promise<Uint8Array>
54
- }
55
-
56
- export type BackendType = 'tauri' | 'web' | 'web-in-memory'
57
-
58
- export const isBackendType = (type: string): type is BackendType => {
59
- return type === 'tauri' || type === 'web' || type === 'web-in-memory'
60
- }
61
-
62
- export type SelectResponse<T = any> = {
63
- results: T[]
64
-
65
- // other perf stats metadata about how long the query took
66
- [key: string]: any
67
- }
68
-
69
- export enum IndexType {
70
- Basic = 'Basic',
71
- FullText = 'FullText',
72
- }
73
-
74
- export type BackendOptions = BackendOptionsTauri | BackendOptionsWeb | BackendOptionsWebInMemory
75
- export type BackendOtelProps = {
76
- otelTracer: otel.Tracer
77
- parentSpan: otel.Span
78
- }
79
-
80
- export const createBackend = async (options: BackendOptions, otelProps: BackendOtelProps): Promise<Backend> => {
81
- switch (options.type) {
82
- case 'tauri': {
83
- // NOTE Dynamic import is needed to avoid Tauri is a dependency of LiveStore (e.g. when used in the web)
84
- const { TauriBackend } = await import('./tauri.js')
85
- return await TauriBackend.load(options, otelProps)
86
- }
87
- case 'web': {
88
- return WebWorkerBackend.load(options, otelProps)
89
- }
90
- // NOTE currently only needed for testing
91
- case 'web-in-memory': {
92
- return WebInMemoryBackend.load(options, otelProps)
93
- }
94
- default: {
95
- casesHandled(options)
96
- }
97
- }
98
- }
@@ -1,32 +0,0 @@
1
- import { makeNoopTracer } from '@livestore/utils'
2
- import type * as otel from '@opentelemetry/api'
3
-
4
- import type { ParamsObject } from '../util.js'
5
- import { BaseBackend } from './base.js'
6
- import type { SelectResponse } from './index.js'
7
-
8
- export type BackendOptionsNoop = {
9
- type: 'noop'
10
- /** Specifies where to persist data for this backend */
11
- otelTracer?: otel.Tracer
12
- }
13
-
14
- export class NoopBackend extends BaseBackend {
15
- constructor(readonly otelTracer: otel.Tracer) {
16
- super()
17
- }
18
-
19
- static load = async (options: BackendOptionsNoop): Promise<NoopBackend> => {
20
- return new NoopBackend(options.otelTracer ?? makeNoopTracer())
21
- }
22
-
23
- execute = (_query: string, _bindValues?: ParamsObject): void => {}
24
-
25
- select = async <T>(_query: string, _bindValues?: ParamsObject): Promise<SelectResponse<T>> => {
26
- return { results: [] }
27
- }
28
-
29
- getPersistedData = async (): Promise<Uint8Array> => {
30
- return new Uint8Array()
31
- }
32
- }
@@ -1,65 +0,0 @@
1
- import type * as otel from '@opentelemetry/api'
2
- import type * as SqliteWasm from 'sqlite-esm'
3
- import sqlite3InitModule from 'sqlite-esm'
4
-
5
- import type { ParamsObject } from '../util.js'
6
- import { prepareBindValues } from '../util.js'
7
- import { BaseBackend } from './base.js'
8
- import type { BackendOtelProps, SelectResponse } from './index.js'
9
-
10
- export type BackendOptionsWebInMemory = {
11
- type: 'web-in-memory'
12
- }
13
-
14
- declare type DatabaseWithCAPI = SqliteWasm.Database & { capi: SqliteWasm.CAPI }
15
-
16
- // NOTE: This backend is currently only used for testing
17
- export class WebInMemoryBackend extends BaseBackend {
18
- constructor(
19
- readonly otelTracer: otel.Tracer,
20
- readonly db: DatabaseWithCAPI,
21
- ) {
22
- super()
23
- }
24
-
25
- static load = async (
26
- _options: BackendOptionsWebInMemory,
27
- { otelTracer }: BackendOtelProps,
28
- ): Promise<WebInMemoryBackend> => {
29
- const sqlite3 = await sqlite3InitModule({
30
- print: (message) => console.log(`[sql-client] ${message}`),
31
- printErr: (message) => console.error(`[sql-client] ${message}`),
32
- })
33
- const db = new sqlite3.oo1.DB({ filename: ':memory:', flags: 'c' }) as DatabaseWithCAPI
34
- db.capi = sqlite3.capi
35
-
36
- return new WebInMemoryBackend(otelTracer, db)
37
- }
38
-
39
- execute = (query: string, bindValues?: ParamsObject): void => {
40
- this.db.exec({
41
- sql: query,
42
- bind: prepareBindValues(bindValues ?? {}, query) as TODO,
43
- returnValue: 'resultRows',
44
- rowMode: 'object',
45
- })
46
- }
47
-
48
- select = async <T>(query: string, bindValues?: ParamsObject): Promise<SelectResponse<T>> => {
49
- const resultRows: T[] = []
50
-
51
- this.db.exec({
52
- sql: query,
53
- bind: prepareBindValues(bindValues ?? {}, query) as TODO,
54
- rowMode: 'object',
55
- resultRows,
56
- // callback: (row: any) => console.log('select result', db.filename, query, row),
57
- } as TODO)
58
-
59
- return { results: resultRows }
60
- }
61
-
62
- getPersistedData = async (): Promise<Uint8Array> => {
63
- return this.db.capi.sqlite3_js_db_export(this.db.pointer)
64
- }
65
- }
@@ -1,97 +0,0 @@
1
- import type * as otel from '@opentelemetry/api'
2
- import * as Comlink from 'comlink'
3
-
4
- import type { ParamsObject } from '../util.js'
5
- import { prepareBindValues } from '../util.js'
6
- import { BaseBackend } from './base.js'
7
- import type { BackendOtelProps, SelectResponse, WritableDatabaseLocation } from './index.js'
8
- import type { WrappedWorker } from './web-worker.js'
9
-
10
- export type BackendOptionsWeb = {
11
- type: 'web'
12
- /** Specifies where to persist data for this backend */
13
- persistentDatabaseLocation: WritableDatabaseLocation
14
- }
15
-
16
- export class WebWorkerBackend extends BaseBackend {
17
- worker: Comlink.Remote<WrappedWorker>
18
- persistentDatabaseLocation: WritableDatabaseLocation
19
- otelTracer: otel.Tracer
20
-
21
- executionBacklog: { query: string; bindValues?: ParamsObject }[] = []
22
- executionPromise: Promise<void> | undefined = undefined
23
-
24
- private constructor({
25
- worker,
26
- persistentDatabaseLocation,
27
- otelTracer,
28
- }: {
29
- worker: Comlink.Remote<WrappedWorker>
30
- persistentDatabaseLocation: WritableDatabaseLocation
31
- otelTracer: otel.Tracer
32
- }) {
33
- super()
34
- this.worker = worker
35
- this.persistentDatabaseLocation = persistentDatabaseLocation
36
- this.otelTracer = otelTracer
37
- }
38
-
39
- static load = async (
40
- { persistentDatabaseLocation }: BackendOptionsWeb,
41
- { otelTracer }: BackendOtelProps,
42
- ): Promise<WebWorkerBackend> => {
43
- // TODO: Importing the worker like this only works with Vite;
44
- // should this really be inside the LiveStore library?
45
- // Doesn't work with Firefox right now during dev https://bugzilla.mozilla.org/show_bug.cgi?id=1247687
46
- const worker = new Worker(new URL('./web-worker.js', import.meta.url), {
47
- type: 'module',
48
- })
49
- const wrappedWorker = Comlink.wrap<WrappedWorker>(worker)
50
-
51
- await wrappedWorker.initialize({ persistentDatabaseLocation })
52
-
53
- return new WebWorkerBackend({
54
- worker: wrappedWorker,
55
- persistentDatabaseLocation,
56
- otelTracer,
57
- })
58
- }
59
-
60
- execute = (query: string, bindValues_?: ParamsObject) => {
61
- const bindValues = prepareBindValues(bindValues_ ?? {}, query)
62
- this.executionBacklog.push({ query, bindValues })
63
-
64
- // Instead of sending the queries to the worker immediately, we wait a bit and batch them up (which reduces the number of messages sent to the worker)
65
- if (this.executionPromise === undefined) {
66
- this.executionPromise = new Promise((resolve) => {
67
- setTimeout(() => {
68
- void this.worker.executeBulk(this.executionBacklog)
69
- this.executionBacklog = []
70
- this.executionPromise = undefined
71
-
72
- resolve()
73
- }, 10)
74
- })
75
- }
76
- }
77
-
78
- select = async <T>(query: string, bindValues?: ParamsObject): Promise<SelectResponse<T>> => {
79
- // NOTE we need to wait for the executionBacklog to be worked off, before we run the select query (as it might depend on the previous execution queries)
80
- await this.executionPromise
81
-
82
- try {
83
- const response = (await this.worker.select(query, bindValues)) as SelectResponse<T>
84
- return response
85
- } catch (e) {
86
- console.error(`Error while executing query via "select": ${query}`)
87
- throw e
88
- }
89
- }
90
-
91
- getPersistedData = async (_parentSpan?: otel.Span): Promise<Uint8Array> => {
92
- // NOTE we need to wait for the executionBacklog to be worked off
93
- await this.executionPromise
94
-
95
- return this.worker.getPersistedData()
96
- }
97
- }
@@ -1,40 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
-
3
- import { labelForKey } from '../componentKey.js'
4
- import { TODO_REMOVE_trackLongRunningSpan } from '../otel.js'
5
- import type { LiveStoreQuery, QueryResult } from '../store.js'
6
-
7
- export const useGlobalQuery = <Q extends LiveStoreQuery>(query: Q): QueryResult<Q> => {
8
- // We know the query has a result by the time we use it; so we can synchronously populate a default state
9
- const [value, setValue] = useState<QueryResult<Q>>(query.results$.result)
10
-
11
- // Subscribe to future updates for this query
12
- useEffect(() => {
13
- return query.store.otel.tracer.startActiveSpan(
14
- `LiveStore:useGlobalQuery:${labelForKey(query.componentKey)}:${query.label}`,
15
- {},
16
- query.store.otel.queriesSpanContext,
17
- (span) => {
18
- TODO_REMOVE_trackLongRunningSpan(span)
19
-
20
- const cancel = query.store.subscribe(
21
- query,
22
- (v) => {
23
- // NOTE: we return a reference to the result object within LiveStore;
24
- // this implies that app code must not mutate the results, or else
25
- // there may be weird reactivity bugs.
26
- return setValue(v)
27
- },
28
- undefined,
29
- { label: query.label },
30
- )
31
- return () => {
32
- cancel()
33
- span.end()
34
- }
35
- },
36
- )
37
- }, [query])
38
-
39
- return value
40
- }
@@ -1,112 +0,0 @@
1
- import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'
2
- import * as otel from '@opentelemetry/api'
3
- import { isEqual } from 'lodash-es'
4
- import React from 'react'
5
-
6
- import { labelForKey } from '../componentKey.js'
7
- import { useStore } from './LiveStoreContext.js'
8
- import { type ComponentKeyConfig, useComponentKey } from './useLiveStoreComponent.js'
9
- import { useStateRefWithReactiveInput } from './utils/useStateRefWithReactiveInput.js'
10
-
11
- export type UseLiveStoreComponentProps<TResult extends Record<string, any>, TVariables extends Record<string, any>> = {
12
- query: DocumentNode<TResult, TVariables>
13
- variables: TVariables
14
- componentKey: ComponentKeyConfig
15
- reactDeps?: React.DependencyList
16
- }
17
-
18
- /**
19
- * This is needed because the `React.useMemo` call below, can sometimes be called multiple times 🤷,
20
- * so we need to "cache" the fact that we've already started a span for this component.
21
- * The map entry is being removed again in the `React.useEffect` call below.
22
- */
23
- const spanAlreadyStartedCache = new Map<string, { span: otel.Span; otelContext: otel.Context }>()
24
-
25
- // TODO 1) figure out a way to make `variables` optional if the query doesn't have any variables (probably requires positional args)
26
- // TODO 2) allow `.pipe` on the resulting query (possibly as a separate optional prop)
27
- export const useGraphQL = <TResult extends Record<string, any>, TVariables extends Record<string, any> = {}>({
28
- query,
29
- variables,
30
- componentKey: componentKeyConfig,
31
- reactDeps = [],
32
- }: UseLiveStoreComponentProps<TResult, TVariables>): Readonly<TResult> => {
33
- const componentKey = useComponentKey(componentKeyConfig, reactDeps)
34
- const { store } = useStore()
35
-
36
- const componentKeyLabel = React.useMemo(() => labelForKey(componentKey), [componentKey])
37
-
38
- // The following `React.useMemo` and `React.useEffect` calls are used to start and end a span for the lifetime of this component.
39
- const { span, otelContext } = React.useMemo(() => {
40
- const existingSpan = spanAlreadyStartedCache.get(componentKeyLabel)
41
- if (existingSpan !== undefined) return existingSpan
42
-
43
- const span = store.otel.tracer.startSpan(
44
- `LiveStore:useGraphQL:${componentKeyLabel}`,
45
- {},
46
- store.otel.queriesSpanContext,
47
- )
48
-
49
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
50
-
51
- spanAlreadyStartedCache.set(componentKeyLabel, { span, otelContext })
52
-
53
- return { span, otelContext }
54
- }, [componentKeyLabel, store.otel.queriesSpanContext, store.otel.tracer])
55
-
56
- React.useEffect(
57
- () => () => {
58
- spanAlreadyStartedCache.delete(componentKeyLabel)
59
- span.end()
60
- },
61
- [componentKeyLabel, span],
62
- )
63
-
64
- const makeLiveStoreQuery = React.useCallback(
65
- () => store.queryGraphQL(query, () => variables ?? ({} as TVariables), { componentKey, otelContext }),
66
- // NOTE: we don't include the queries function passed in by the user here;
67
- // the reason is that we don't want to force them to memoize that function.
68
- // Instead, we just assume that the function always has the same contents.
69
- // This makes sense for LiveStore because the component config should be static.
70
- // TODO: document this and consider whether it's the right API surface.
71
- // eslint-disable-next-line react-hooks/exhaustive-deps
72
- [componentKey, store],
73
- )
74
-
75
- // TODO get rid of the temporary query workaround
76
- const initialQueryResults = React.useMemo(
77
- () => store.inTempQueryContext(() => makeLiveStoreQuery().results$.result),
78
- [makeLiveStoreQuery, store],
79
- )
80
-
81
- const [queryResultsRef, setQueryResults_] = useStateRefWithReactiveInput<TResult>(initialQueryResults)
82
-
83
- React.useEffect(() => {
84
- const liveStoreQuery = makeLiveStoreQuery()
85
- const unsubscribe = store.subscribe(
86
- liveStoreQuery,
87
- (results) => {
88
- if (isEqual(results, queryResultsRef.current) === false) {
89
- setQueryResults_(results)
90
- }
91
- },
92
- undefined,
93
- { label: `useGraphQL:query:subscribe:${liveStoreQuery.label}` },
94
- )
95
-
96
- return () => {
97
- unsubscribe()
98
- }
99
- // NOTE `setQueryResults_` from the deps array as it seems to cause an infinite loop
100
- // This should probably be improved
101
- // eslint-disable-next-line react-hooks/exhaustive-deps
102
- }, [
103
- makeLiveStoreQuery,
104
- // setQueryResults_,
105
- store,
106
- ])
107
-
108
- // Very important: remove any queries / other resources associated w/ this component
109
- React.useEffect(() => () => store.unmountComponent(componentKey), [store, componentKey])
110
-
111
- return queryResultsRef.current
112
- }