@livestore/livestore 0.0.39 → 0.0.40

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 (190) hide show
  1. package/README.md +15 -24
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +192 -17
  4. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/react/fixture.js +10 -29
  6. package/dist/__tests__/react/fixture.js.map +1 -1
  7. package/dist/{mutations.d.ts → cud.d.ts} +14 -19
  8. package/dist/cud.d.ts.map +1 -0
  9. package/dist/{mutations.js → cud.js} +15 -7
  10. package/dist/cud.js.map +1 -0
  11. package/dist/cud.test.d.ts +2 -0
  12. package/dist/cud.test.d.ts.map +1 -0
  13. package/dist/cud.test.js +47 -0
  14. package/dist/cud.test.js.map +1 -0
  15. package/dist/inMemoryDatabase.d.ts +1 -1
  16. package/dist/inMemoryDatabase.d.ts.map +1 -1
  17. package/dist/inMemoryDatabase.js +1 -4
  18. package/dist/inMemoryDatabase.js.map +1 -1
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/migrations.d.ts.map +1 -1
  24. package/dist/migrations.js +11 -7
  25. package/dist/migrations.js.map +1 -1
  26. package/dist/query-info.d.ts +2 -5
  27. package/dist/query-info.d.ts.map +1 -1
  28. package/dist/query-info.js +3 -2
  29. package/dist/query-info.js.map +1 -1
  30. package/dist/react/useAtom.d.ts.map +1 -1
  31. package/dist/react/useAtom.js +2 -2
  32. package/dist/react/useAtom.js.map +1 -1
  33. package/dist/react/useQuery.test.d.ts.map +1 -0
  34. package/dist/{__tests__/react → react}/useQuery.test.js +8 -11
  35. package/dist/react/useQuery.test.js.map +1 -0
  36. package/dist/react/useRow.js +4 -4
  37. package/dist/react/useRow.js.map +1 -1
  38. package/dist/react/useRow.test.d.ts.map +1 -0
  39. package/dist/{__tests__/react → react}/useRow.test.js +14 -38
  40. package/dist/react/useRow.test.js.map +1 -0
  41. package/dist/reactive.d.ts +2 -2
  42. package/dist/reactive.d.ts.map +1 -1
  43. package/dist/reactive.js +50 -15
  44. package/dist/reactive.js.map +1 -1
  45. package/dist/reactive.test.d.ts.map +1 -0
  46. package/dist/{__tests__/reactive.test.js → reactive.test.js} +1 -1
  47. package/dist/reactive.test.js.map +1 -0
  48. package/dist/reactiveQueries/base-class.d.ts +2 -0
  49. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  50. package/dist/reactiveQueries/base-class.js +1 -0
  51. package/dist/reactiveQueries/base-class.js.map +1 -1
  52. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  53. package/dist/reactiveQueries/graphql.js +1 -0
  54. package/dist/reactiveQueries/graphql.js.map +1 -1
  55. package/dist/reactiveQueries/js.d.ts.map +1 -1
  56. package/dist/reactiveQueries/js.js +1 -0
  57. package/dist/reactiveQueries/js.js.map +1 -1
  58. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  59. package/dist/reactiveQueries/sql.js +1 -0
  60. package/dist/reactiveQueries/sql.js.map +1 -1
  61. package/dist/reactiveQueries/sql.test.d.ts.map +1 -0
  62. package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.js +44 -34
  63. package/dist/reactiveQueries/sql.test.js.map +1 -0
  64. package/dist/row-query.js +7 -5
  65. package/dist/row-query.js.map +1 -1
  66. package/dist/schema/index.d.ts +20 -7
  67. package/dist/schema/index.d.ts.map +1 -1
  68. package/dist/schema/index.js +18 -3
  69. package/dist/schema/index.js.map +1 -1
  70. package/dist/schema/mutations.d.ts +81 -0
  71. package/dist/schema/mutations.d.ts.map +1 -0
  72. package/dist/schema/mutations.js +29 -0
  73. package/dist/schema/mutations.js.map +1 -0
  74. package/dist/schema/parse-utils.d.ts +3 -3
  75. package/dist/schema/parse-utils.d.ts.map +1 -1
  76. package/dist/schema/table-def.d.ts +1 -1
  77. package/dist/schema/table-def.d.ts.map +1 -1
  78. package/dist/schema/table-def.js +2 -2
  79. package/dist/schema/table-def.js.map +1 -1
  80. package/dist/storage/in-memory/index.d.ts +4 -0
  81. package/dist/storage/in-memory/index.d.ts.map +1 -1
  82. package/dist/storage/in-memory/index.js +3 -0
  83. package/dist/storage/in-memory/index.js.map +1 -1
  84. package/dist/storage/index.d.ts +4 -0
  85. package/dist/storage/index.d.ts.map +1 -1
  86. package/dist/storage/tauri/index.d.ts +4 -0
  87. package/dist/storage/tauri/index.d.ts.map +1 -1
  88. package/dist/storage/tauri/index.js +6 -0
  89. package/dist/storage/tauri/index.js.map +1 -1
  90. package/dist/storage/utils/idb.d.ts +1 -0
  91. package/dist/storage/utils/idb.d.ts.map +1 -1
  92. package/dist/storage/utils/idb.js +11 -0
  93. package/dist/storage/utils/idb.js.map +1 -1
  94. package/dist/storage/web-worker/common.d.ts +11 -0
  95. package/dist/storage/web-worker/common.d.ts.map +1 -0
  96. package/dist/storage/web-worker/common.js +2 -0
  97. package/dist/storage/web-worker/common.js.map +1 -0
  98. package/dist/storage/web-worker/index.d.ts +14 -7
  99. package/dist/storage/web-worker/index.d.ts.map +1 -1
  100. package/dist/storage/web-worker/index.js +70 -14
  101. package/dist/storage/web-worker/index.js.map +1 -1
  102. package/dist/storage/web-worker/make-worker.d.ts +20 -0
  103. package/dist/storage/web-worker/make-worker.d.ts.map +1 -0
  104. package/dist/storage/web-worker/make-worker.js +155 -0
  105. package/dist/storage/web-worker/make-worker.js.map +1 -0
  106. package/dist/storage/web-worker/vite-dev-polyfill.d.ts +2 -0
  107. package/dist/storage/web-worker/vite-dev-polyfill.d.ts.map +1 -0
  108. package/dist/storage/web-worker/vite-dev-polyfill.js +35 -0
  109. package/dist/storage/web-worker/vite-dev-polyfill.js.map +1 -0
  110. package/dist/store.d.ts +32 -42
  111. package/dist/store.d.ts.map +1 -1
  112. package/dist/store.js +82 -131
  113. package/dist/store.js.map +1 -1
  114. package/dist/utils/dev.d.ts +3 -0
  115. package/dist/utils/dev.d.ts.map +1 -0
  116. package/dist/utils/dev.js +16 -0
  117. package/dist/utils/dev.js.map +1 -0
  118. package/dist/utils/util.d.ts +2 -0
  119. package/dist/utils/util.d.ts.map +1 -1
  120. package/dist/utils/util.js +2 -0
  121. package/dist/utils/util.js.map +1 -1
  122. package/package.json +24 -12
  123. package/src/__tests__/react/fixture.tsx +12 -30
  124. package/src/cud.test.ts +52 -0
  125. package/src/{mutations.ts → cud.ts} +28 -22
  126. package/src/inMemoryDatabase.ts +2 -7
  127. package/src/index.ts +14 -8
  128. package/src/migrations.ts +10 -7
  129. package/src/query-info.ts +4 -7
  130. package/src/react/useAtom.ts +2 -2
  131. package/src/{__tests__/react → react}/useQuery.test.tsx +11 -11
  132. package/src/{__tests__/react → react}/useRow.test.tsx +21 -39
  133. package/src/react/useRow.ts +4 -4
  134. package/src/{__tests__/reactive.test.ts → reactive.test.ts} +1 -1
  135. package/src/reactive.ts +60 -19
  136. package/src/reactiveQueries/base-class.ts +4 -0
  137. package/src/reactiveQueries/graphql.ts +2 -0
  138. package/src/reactiveQueries/js.ts +2 -0
  139. package/src/{__tests__/reactiveQueries → reactiveQueries}/sql.test.ts +44 -34
  140. package/src/reactiveQueries/sql.ts +2 -0
  141. package/src/row-query.ts +11 -9
  142. package/src/schema/index.ts +47 -11
  143. package/src/schema/mutations.ts +129 -0
  144. package/src/schema/parse-utils.ts +1 -1
  145. package/src/schema/table-def.ts +7 -1
  146. package/src/storage/in-memory/index.ts +7 -0
  147. package/src/storage/index.ts +8 -0
  148. package/src/storage/tauri/index.ts +10 -0
  149. package/src/storage/utils/idb.ts +14 -0
  150. package/src/storage/web-worker/common.ts +6 -0
  151. package/src/storage/web-worker/index.ts +86 -17
  152. package/src/storage/web-worker/make-worker.ts +214 -0
  153. package/src/storage/web-worker/vite-dev-polyfill.ts +33 -0
  154. package/src/store.ts +142 -212
  155. package/src/utils/dev.ts +23 -0
  156. package/src/utils/util.ts +4 -0
  157. package/dist/__tests__/mutations.test.d.ts +0 -2
  158. package/dist/__tests__/mutations.test.d.ts.map +0 -1
  159. package/dist/__tests__/mutations.test.js +0 -40
  160. package/dist/__tests__/mutations.test.js.map +0 -1
  161. package/dist/__tests__/react/useQuery.test.d.ts.map +0 -1
  162. package/dist/__tests__/react/useQuery.test.js.map +0 -1
  163. package/dist/__tests__/react/useRow.test.d.ts.map +0 -1
  164. package/dist/__tests__/react/useRow.test.js.map +0 -1
  165. package/dist/__tests__/reactive.test.d.ts.map +0 -1
  166. package/dist/__tests__/reactive.test.js.map +0 -1
  167. package/dist/__tests__/reactiveQueries/sql.test.d.ts.map +0 -1
  168. package/dist/__tests__/reactiveQueries/sql.test.js.map +0 -1
  169. package/dist/events.d.ts +0 -7
  170. package/dist/events.d.ts.map +0 -1
  171. package/dist/events.js +0 -2
  172. package/dist/events.js.map +0 -1
  173. package/dist/mutations.d.ts.map +0 -1
  174. package/dist/mutations.js.map +0 -1
  175. package/dist/schema/action.d.ts +0 -30
  176. package/dist/schema/action.d.ts.map +0 -1
  177. package/dist/schema/action.js +0 -3
  178. package/dist/schema/action.js.map +0 -1
  179. package/dist/storage/web-worker/worker.d.ts +0 -13
  180. package/dist/storage/web-worker/worker.d.ts.map +0 -1
  181. package/dist/storage/web-worker/worker.js +0 -110
  182. package/dist/storage/web-worker/worker.js.map +0 -1
  183. package/src/__tests__/mutations.test.ts +0 -43
  184. package/src/events.ts +0 -8
  185. package/src/schema/action.ts +0 -41
  186. package/src/storage/web-worker/worker.ts +0 -141
  187. /package/dist/{__tests__/react → react}/useQuery.test.d.ts +0 -0
  188. /package/dist/{__tests__/react → react}/useRow.test.d.ts +0 -0
  189. /package/dist/{__tests__/reactive.test.d.ts → reactive.test.d.ts} +0 -0
  190. /package/dist/{__tests__/reactiveQueries → reactiveQueries}/sql.test.d.ts +0 -0
@@ -1,11 +1,13 @@
1
- import { casesHandled } from '@livestore/utils'
1
+ import { casesHandled, notYetImplemented } from '@livestore/utils'
2
2
  import type * as otel from '@opentelemetry/api'
3
3
  import * as Comlink from 'comlink'
4
4
 
5
+ import type { MutationEvent } from '../../index.js'
5
6
  import type { PreparedBindValues } from '../../utils/util.js'
6
7
  import type { Storage, StorageOtelProps } from '../index.js'
7
8
  import { IDB } from '../utils/idb.js'
8
- import type { WrappedWorker } from './worker.js'
9
+ import type { ExecutionBacklogItem } from './common.js'
10
+ import type { WrappedWorker } from './make-worker.js'
9
11
 
10
12
  export type StorageType = 'opfs' | 'indexeddb'
11
13
 
@@ -13,28 +15,33 @@ export type StorageOptionsWeb = {
13
15
  /** Specifies where to persist data for this storage */
14
16
  type: StorageType
15
17
  fileName: string
18
+ worker: Worker | (new (options?: { name: string }) => Worker)
16
19
  }
17
20
 
18
21
  export class WebWorkerStorage implements Storage {
19
- worker: Comlink.Remote<WrappedWorker>
22
+ worker: Worker
23
+ wrappedWorker: Comlink.Remote<WrappedWorker>
20
24
  options: StorageOptionsWeb
21
25
  otelTracer: otel.Tracer
22
26
 
23
- executionBacklog: { query: string; bindValues?: PreparedBindValues }[] = []
27
+ executionBacklog: ExecutionBacklogItem[] = []
24
28
  executionPromise: Promise<void> | undefined
25
29
 
26
30
  private constructor({
27
31
  worker,
32
+ wrappedWorker,
28
33
  options,
29
34
  otelTracer,
30
35
  executionPromise,
31
36
  }: {
32
- worker: Comlink.Remote<WrappedWorker>
37
+ worker: Worker
38
+ wrappedWorker: Comlink.Remote<WrappedWorker>
33
39
  options: StorageOptionsWeb
34
40
  otelTracer: otel.Tracer
35
41
  executionPromise: Promise<void>
36
42
  }) {
37
43
  this.worker = worker
44
+ this.wrappedWorker = wrappedWorker
38
45
  this.options = options
39
46
  this.otelTracer = otelTracer
40
47
  this.executionPromise = executionPromise
@@ -43,26 +50,31 @@ export class WebWorkerStorage implements Storage {
43
50
  }
44
51
 
45
52
  static load = (options: StorageOptionsWeb) => {
46
- // TODO: Importing the worker like this only works with Vite;
47
- // should this really be inside the LiveStore library?
48
- // Doesn't work with Firefox right now during dev https://bugzilla.mozilla.org/show_bug.cgi?id=1247687
49
- const worker = new Worker(new URL('./worker.js', import.meta.url), {
50
- type: 'module',
51
- })
53
+ const worker = options.worker instanceof Worker ? options.worker : new options.worker({ name: 'livestore-worker' })
54
+ // TODO replace Comlink with Effect worker
52
55
  const wrappedWorker = Comlink.wrap<WrappedWorker>(worker)
53
56
 
54
57
  return ({ otelTracer }: StorageOtelProps) =>
55
58
  new WebWorkerStorage({
56
- worker: wrappedWorker,
59
+ worker,
60
+ wrappedWorker,
57
61
  options,
58
62
  otelTracer,
59
- executionPromise: wrappedWorker.initialize(options),
63
+ executionPromise: wrappedWorker.initialize({ fileName: options.fileName, type: options.type }),
60
64
  })
61
65
  }
62
66
 
63
- execute = (query: string, bindValues?: PreparedBindValues) => {
64
- this.executionBacklog.push({ query, bindValues })
67
+ execute = (query: string, bindValues?: PreparedBindValues, _parentSpan?: otel.Span | undefined) => {
68
+ this.executionBacklog.push({ _tag: 'execute', query, bindValues })
69
+ this.scheduleExecution()
70
+ }
71
+
72
+ mutate = (mutationEventEncoded: MutationEvent.Any, _parentSpan?: otel.Span | undefined) => {
73
+ this.executionBacklog.push({ _tag: 'mutate', mutationEventEncoded })
74
+ this.scheduleExecution()
75
+ }
65
76
 
77
+ private scheduleExecution = () => {
66
78
  // 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)
67
79
  if (this.executionPromise === undefined) {
68
80
  this.executionPromise = new Promise((resolve) => {
@@ -76,12 +88,20 @@ export class WebWorkerStorage implements Storage {
76
88
  }
77
89
 
78
90
  private executeBacklog = () => {
79
- void this.worker.executeBulk(this.executionBacklog)
91
+ void this.wrappedWorker.executeBulk(this.executionBacklog)
80
92
  this.executionBacklog = []
81
93
  this.executionPromise = undefined
82
94
  }
83
95
 
84
96
  getPersistedData = async (_parentSpan?: otel.Span): Promise<Uint8Array> => getPersistedData(this.options)
97
+
98
+ getMutationLogData = async (_parentSpan?: otel.Span): Promise<Uint8Array> => getMutationLogData(this.options)
99
+
100
+ dangerouslyReset = async () => {
101
+ // TODO implement graceful shutdown
102
+ this.worker.terminate()
103
+ await resetPersistedData(this.options)
104
+ }
85
105
  }
86
106
 
87
107
  const getPersistedData = async (options: StorageOptionsWeb): Promise<Uint8Array> => {
@@ -89,7 +109,7 @@ const getPersistedData = async (options: StorageOptionsWeb): Promise<Uint8Array>
89
109
  case 'opfs': {
90
110
  try {
91
111
  const rootHandle = await navigator.storage.getDirectory()
92
- const fileHandle = await rootHandle.getFileHandle(options.fileName + '.db')
112
+ const fileHandle = await rootHandle.getFileHandle(options.fileName)
93
113
  const file = await fileHandle.getFile()
94
114
  const buffer = await file.arrayBuffer()
95
115
  const data = new Uint8Array(buffer)
@@ -114,3 +134,52 @@ const getPersistedData = async (options: StorageOptionsWeb): Promise<Uint8Array>
114
134
  }
115
135
  }
116
136
  }
137
+
138
+ const getMutationLogData = async (options: StorageOptionsWeb): Promise<Uint8Array> => {
139
+ switch (options.type) {
140
+ case 'opfs': {
141
+ try {
142
+ const rootHandle = await navigator.storage.getDirectory()
143
+ const fileHandle = await rootHandle.getFileHandle(`${options.fileName}-log.db`)
144
+ const file = await fileHandle.getFile()
145
+ const buffer = await file.arrayBuffer()
146
+ const data = new Uint8Array(buffer)
147
+
148
+ return data
149
+ } catch (error: any) {
150
+ if (error instanceof DOMException && error.name === 'NotFoundError') {
151
+ return new Uint8Array()
152
+ }
153
+
154
+ throw error
155
+ }
156
+ }
157
+
158
+ case 'indexeddb': {
159
+ return notYetImplemented()
160
+ }
161
+ default: {
162
+ casesHandled(options.type)
163
+ }
164
+ }
165
+ }
166
+
167
+ const resetPersistedData = async (options: StorageOptionsWeb) => {
168
+ switch (options.type) {
169
+ case 'opfs': {
170
+ const rootHandle = await navigator.storage.getDirectory()
171
+ await rootHandle.removeEntry(options.fileName)
172
+ await rootHandle.removeEntry(`${options.fileName}-log.db`)
173
+ break
174
+ }
175
+
176
+ case 'indexeddb': {
177
+ const idb = new IDB(options.fileName)
178
+ await idb.deleteDb()
179
+ break
180
+ }
181
+ default: {
182
+ casesHandled(options.type)
183
+ }
184
+ }
185
+ }
@@ -0,0 +1,214 @@
1
+ // TODO: create types for these libraries? SQL.js already should have types;
2
+ // we just need the types to apply to the fork.
3
+ import { memoize, shouldNeverHappen } from '@livestore/utils'
4
+ import { Schema } from '@livestore/utils/effect'
5
+ import * as Comlink from 'comlink'
6
+ import type * as SqliteWasm from 'sqlite-esm'
7
+ import sqlite3InitModule from 'sqlite-esm'
8
+
9
+ import type { LiveStoreSchema } from '../../index.js'
10
+ import { makeMutationEventSchema } from '../../schema/mutations.js'
11
+ import { casesHandled, prepareBindValues, sql } from '../../utils/util.js'
12
+ import { IDB } from '../utils/idb.js'
13
+ import type { ExecutionBacklogItem } from './common.js'
14
+ import type { StorageOptionsWeb } from './index.js'
15
+
16
+ export type WorkerOptions<TSchema extends LiveStoreSchema = LiveStoreSchema> = {
17
+ schema: TSchema
18
+ mutationLog?: {
19
+ /**
20
+ * Mutations to exclude in the mutation log
21
+ *
22
+ * @default new Set(['livestore.RawSql'])
23
+ */
24
+ exclude?: ReadonlySet<keyof TSchema['_MutationDefMapType']>
25
+ }
26
+ }
27
+
28
+ export const makeWorker = <TSchema extends LiveStoreSchema = LiveStoreSchema>({
29
+ schema,
30
+ mutationLog,
31
+ }: WorkerOptions<TSchema>) => {
32
+ // A global variable to hold the database connection.
33
+ let db: SqliteWasm.DatabaseApi
34
+
35
+ let dbLog: SqliteWasm.DatabaseApi
36
+
37
+ let sqlite3: SqliteWasm.Sqlite3Static
38
+
39
+ const mutationLogExclude = mutationLog?.exclude ?? new Set(['livestore.RawSql'])
40
+
41
+ // TODO refactor
42
+ const mutationArgsSchema = makeMutationEventSchema(Object.fromEntries(schema.mutations.entries()) as any)
43
+ const schemaHashMap = new Map([...schema.mutations.entries()].map(([k, v]) => [k, Schema.hash(v.schema)] as const))
44
+
45
+ // TODO get rid of this in favour of a "proper" IDB SQLite storage
46
+ let idb: IDB | undefined
47
+
48
+ /** The location where this database storage persists its data */
49
+ let options_: Omit<StorageOptionsWeb, 'worker'>
50
+
51
+ const configureConnection = () =>
52
+ db.exec(sql`
53
+ PRAGMA page_size=8192;
54
+ PRAGMA journal_mode=MEMORY;
55
+ PRAGMA foreign_keys='ON'; -- we want foreign key constraints to be enforced
56
+ `)
57
+
58
+ /** A full virtual filename in the IDB FS */
59
+
60
+ const initialize = async (options: Omit<StorageOptionsWeb, 'worker'>) => {
61
+ options_ = options
62
+
63
+ sqlite3 = await sqlite3InitModule({
64
+ print: (message) => console.log(`[sql-client] ${message}`),
65
+ printErr: (message) => console.error(`[sql-client] ${message}`),
66
+ })
67
+
68
+ switch (options.type) {
69
+ case 'opfs': {
70
+ try {
71
+ db = new sqlite3.oo1.OpfsDb(options.fileName) // , 'c'
72
+
73
+ dbLog = new sqlite3.oo1.OpfsDb(options.fileName + '-log.db') // , 'c'
74
+ } catch (e) {
75
+ debugger
76
+ }
77
+ break
78
+ }
79
+ case 'indexeddb': {
80
+ try {
81
+ db = new sqlite3.oo1.DB({ filename: ':memory:', flags: 'c' })
82
+ idb = new IDB(options.fileName)
83
+
84
+ const bytes = await idb.get('db')
85
+
86
+ if (bytes !== undefined) {
87
+ // Based on https://sqlite.org/forum/forumpost/2119230da8ac5357a13b731f462dc76e08621a4a29724f7906d5f35bb8508465
88
+ // TODO find cleaner way to do this once possible in sqlite3-wasm
89
+ const p = sqlite3.wasm.allocFromTypedArray(bytes)
90
+ const _rc = sqlite3.capi.sqlite3_deserialize(db.pointer, 'main', p, bytes.length, bytes.length, 0)
91
+ }
92
+ } catch (e) {
93
+ debugger
94
+ }
95
+ break
96
+ }
97
+ default: {
98
+ casesHandled(options.type)
99
+ }
100
+ }
101
+
102
+ // Creates `mutation_log` table if it doesn't exist
103
+ dbLog.exec(sql`
104
+ CREATE TABLE IF NOT EXISTS mutation_log (
105
+ id TEXT PRIMARY KEY NOT NULL,
106
+ mutation TEXT NOT NULL,
107
+ args_json TEXT NOT NULL,
108
+ schema_hash INTEGER NOT NULL,
109
+ created_at TEXT NOT NULL
110
+ );
111
+ `)
112
+
113
+ configureConnection()
114
+ }
115
+
116
+ // TODO get rid of this in favour of a "proper" IDB SQLite storage
117
+ let idbPersistTimeout: NodeJS.Timeout | undefined
118
+
119
+ const executeBulk = (executionItems: ExecutionBacklogItem[]): void => {
120
+ let batchItems: ExecutionBacklogItem[] = []
121
+
122
+ const createdAtMemo = memoize(() => new Date().toISOString())
123
+
124
+ while (executionItems.length > 0) {
125
+ try {
126
+ db.exec('BEGIN TRANSACTION') // Start the transaction
127
+ dbLog.exec('BEGIN TRANSACTION') // Start the transaction
128
+
129
+ batchItems = executionItems.splice(0, 50)
130
+
131
+ // console.debug('livestore-webworker: executing batch', batchItems)
132
+
133
+ for (const item of batchItems) {
134
+ if (item._tag === 'execute') {
135
+ const { query, bindValues } = item
136
+ db.exec({ sql: query, bind: bindValues as TODO })
137
+
138
+ // NOTE we're not writing `execute` events to the mutation_log
139
+ } else {
140
+ const { mutation, args } = Schema.decodeUnknownSync(mutationArgsSchema)(item.mutationEventEncoded)
141
+
142
+ const mutationDef = schema.mutations.get(mutation) ?? shouldNeverHappen(`Unknown mutation: ${mutation}`)
143
+
144
+ const statementRes = typeof mutationDef.sql === 'function' ? mutationDef.sql(args) : mutationDef.sql
145
+ const statementSql = typeof statementRes === 'string' ? statementRes : statementRes.sql
146
+
147
+ const bindValues =
148
+ typeof statementRes === 'string' ? item.mutationEventEncoded.args : statementRes.bindValues
149
+
150
+ db.exec({ sql: statementSql, bind: prepareBindValues(bindValues ?? {}, statementSql) as TODO })
151
+
152
+ // write to mutation_log
153
+ if (options_.type === 'opfs' && mutationLogExclude.has(mutation) === false) {
154
+ const schemaHash = schemaHashMap.get(mutation) ?? shouldNeverHappen(`Unknown mutation: ${mutation}`)
155
+
156
+ const argsJson = JSON.stringify(item.mutationEventEncoded.args ?? {})
157
+
158
+ dbLog.exec({
159
+ sql: `INSERT INTO mutation_log (id, mutation, args_json, schema_hash, created_at) VALUES (?, ?, ?, ?, ?)`,
160
+ bind: [
161
+ item.mutationEventEncoded.id,
162
+ item.mutationEventEncoded.mutation,
163
+ argsJson,
164
+ schemaHash,
165
+ createdAtMemo(),
166
+ ],
167
+ })
168
+ }
169
+ }
170
+ }
171
+
172
+ db.exec('COMMIT') // Commit the transaction
173
+ dbLog.exec('COMMIT') // Commit the transaction
174
+ } catch (error) {
175
+ try {
176
+ db.exec('ROLLBACK') // Rollback in case of an error
177
+ dbLog.exec('ROLLBACK') // Rollback in case of an error
178
+ } catch (e) {
179
+ console.error('Error rolling back transaction', e)
180
+ }
181
+
182
+ shouldNeverHappen(`Error executing query: ${error} \n ${JSON.stringify(batchItems)}`)
183
+ }
184
+ }
185
+
186
+ // TODO get rid of this in favour of a "proper" IDB SQLite storage
187
+ if (options_.type === 'indexeddb') {
188
+ if (idbPersistTimeout !== undefined) {
189
+ clearTimeout(idbPersistTimeout)
190
+ }
191
+
192
+ idbPersistTimeout = setTimeout(() => {
193
+ const data = sqlite3.capi.sqlite3_js_db_export(db.pointer) as Uint8Array
194
+
195
+ void idb!.put('db', data)
196
+ }, 1000)
197
+ }
198
+ }
199
+
200
+ const wrappedWorker = { initialize, executeBulk }
201
+
202
+ Comlink.expose(wrappedWorker)
203
+
204
+ // NOTE keep this around for debugging
205
+ // db.exec({
206
+ // sql: `select * from sqlite_master where name = 'library_tracks'`,
207
+ // callback: (_: TODO) => console.log(_),
208
+ // rowMode: 'object',
209
+ // } as TODO)
210
+
211
+ return wrappedWorker
212
+ }
213
+
214
+ export type WrappedWorker = ReturnType<typeof makeWorker>
@@ -0,0 +1,33 @@
1
+ // @ts-expect-error TODO remove when Vite does proper treeshaking during dev
2
+ globalThis.$RefreshReg$ = () => {}
3
+ // @ts-expect-error TODO remove when Vite does proper treeshaking during dev
4
+ globalThis.$RefreshSig$ = () => (type: any) => type
5
+
6
+ globalThis.document = (globalThis as any)?.document ?? {
7
+ querySelectorAll: () => [],
8
+ addEventListener: () => {},
9
+ createElement: () => ({
10
+ setAttribute: () => {},
11
+ pathname: '',
12
+ style: {},
13
+ }),
14
+ body: {
15
+ addEventListener: () => {},
16
+ },
17
+ head: {
18
+ appendChild: () => {},
19
+ },
20
+ }
21
+
22
+ globalThis.window = globalThis?.window ?? {
23
+ AnimationEvent: class AnimationEvent {},
24
+ TransitionEvent: class TransitionEvent {},
25
+ addEventListener: () => {},
26
+ location: {
27
+ href: '',
28
+ pathname: '',
29
+ },
30
+ document: globalThis.document,
31
+ }
32
+
33
+ globalThis.HTMLElement = globalThis?.HTMLElement ?? class HTMLElement {}