@livestore/livestore 0.0.46-dev.4 → 0.0.47-dev.0

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 (108) hide show
  1. package/README.md +10 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/react/fixture.d.ts +28 -12
  4. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  5. package/dist/__tests__/react/fixture.js +27 -3
  6. package/dist/__tests__/react/fixture.js.map +1 -1
  7. package/dist/__tests__/react/utils/otel.d.ts +10 -0
  8. package/dist/__tests__/react/utils/otel.d.ts.map +1 -0
  9. package/dist/__tests__/react/utils/otel.js +42 -0
  10. package/dist/__tests__/react/utils/otel.js.map +1 -0
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/react/LiveStoreProvider.js +39 -6
  15. package/dist/react/LiveStoreProvider.js.map +1 -1
  16. package/dist/react/LiveStoreProvider.test.d.ts +2 -0
  17. package/dist/react/LiveStoreProvider.test.d.ts.map +1 -0
  18. package/dist/react/LiveStoreProvider.test.js +40 -0
  19. package/dist/react/LiveStoreProvider.test.js.map +1 -0
  20. package/dist/react/components/LiveList.d.ts +21 -0
  21. package/dist/react/components/LiveList.d.ts.map +1 -0
  22. package/dist/react/components/LiveList.js +31 -0
  23. package/dist/react/components/LiveList.js.map +1 -0
  24. package/dist/react/index.d.ts +1 -1
  25. package/dist/react/index.d.ts.map +1 -1
  26. package/dist/react/index.js +1 -1
  27. package/dist/react/index.js.map +1 -1
  28. package/dist/react/useAtom.d.ts +1 -1
  29. package/dist/react/useAtom.d.ts.map +1 -1
  30. package/dist/react/useAtom.js +6 -1
  31. package/dist/react/useAtom.js.map +1 -1
  32. package/dist/react/useQuery.d.ts +4 -1
  33. package/dist/react/useQuery.d.ts.map +1 -1
  34. package/dist/react/useQuery.js +24 -19
  35. package/dist/react/useQuery.js.map +1 -1
  36. package/dist/react/useQuery.test.js +11 -11
  37. package/dist/react/useQuery.test.js.map +1 -1
  38. package/dist/react/useRow.d.ts.map +1 -1
  39. package/dist/react/useRow.js +14 -69
  40. package/dist/react/useRow.js.map +1 -1
  41. package/dist/react/useRow.test.js +440 -28
  42. package/dist/react/useRow.test.js.map +1 -1
  43. package/dist/react/useTemporaryQuery.d.ts +15 -3
  44. package/dist/react/useTemporaryQuery.d.ts.map +1 -1
  45. package/dist/react/useTemporaryQuery.js +60 -27
  46. package/dist/react/useTemporaryQuery.js.map +1 -1
  47. package/dist/react/useTemporaryQuery.test.js +10 -9
  48. package/dist/react/useTemporaryQuery.test.js.map +1 -1
  49. package/dist/reactive.d.ts +23 -5
  50. package/dist/reactive.d.ts.map +1 -1
  51. package/dist/reactive.js +44 -11
  52. package/dist/reactive.js.map +1 -1
  53. package/dist/reactive.test.js +1 -1
  54. package/dist/reactive.test.js.map +1 -1
  55. package/dist/reactiveQueries/base-class.d.ts +1 -1
  56. package/dist/reactiveQueries/base-class.d.ts.map +1 -1
  57. package/dist/reactiveQueries/base-class.js.map +1 -1
  58. package/dist/reactiveQueries/graphql.d.ts +2 -2
  59. package/dist/reactiveQueries/graphql.d.ts.map +1 -1
  60. package/dist/reactiveQueries/graphql.js +21 -11
  61. package/dist/reactiveQueries/graphql.js.map +1 -1
  62. package/dist/reactiveQueries/sql.d.ts +1 -1
  63. package/dist/reactiveQueries/sql.d.ts.map +1 -1
  64. package/dist/reactiveQueries/sql.js +15 -11
  65. package/dist/reactiveQueries/sql.js.map +1 -1
  66. package/dist/reactiveQueries/sql.test.js +1 -40
  67. package/dist/reactiveQueries/sql.test.js.map +1 -1
  68. package/dist/row-query.d.ts.map +1 -1
  69. package/dist/row-query.js +3 -1
  70. package/dist/row-query.js.map +1 -1
  71. package/dist/store.d.ts +7 -5
  72. package/dist/store.d.ts.map +1 -1
  73. package/dist/store.js +50 -38
  74. package/dist/store.js.map +1 -1
  75. package/package.json +11 -13
  76. package/src/__tests__/react/fixture.tsx +35 -2
  77. package/src/__tests__/react/utils/otel.ts +61 -0
  78. package/src/index.ts +12 -1
  79. package/src/react/LiveStoreProvider.test.tsx +63 -0
  80. package/src/react/LiveStoreProvider.tsx +42 -7
  81. package/src/react/components/LiveList.tsx +84 -0
  82. package/src/react/index.ts +1 -1
  83. package/src/react/useAtom.ts +6 -2
  84. package/src/react/useQuery.test.tsx +11 -11
  85. package/src/react/useQuery.ts +29 -22
  86. package/src/react/useRow.test.tsx +502 -30
  87. package/src/react/useRow.ts +19 -107
  88. package/src/react/useTemporaryQuery.test.tsx +17 -16
  89. package/src/react/useTemporaryQuery.ts +96 -28
  90. package/src/reactive.test.ts +1 -1
  91. package/src/reactive.ts +76 -15
  92. package/src/reactiveQueries/base-class.ts +2 -1
  93. package/src/reactiveQueries/graphql.ts +26 -16
  94. package/src/reactiveQueries/sql.test.ts +1 -54
  95. package/src/reactiveQueries/sql.ts +20 -14
  96. package/src/row-query.ts +3 -1
  97. package/src/store.ts +71 -49
  98. package/tsconfig.json +0 -1
  99. package/dist/react/components/DiffableList.d.ts +0 -20
  100. package/dist/react/components/DiffableList.d.ts.map +0 -1
  101. package/dist/react/components/DiffableList.js +0 -113
  102. package/dist/react/components/DiffableList.js.map +0 -1
  103. package/dist/react/utils/useCleanup.d.ts +0 -7
  104. package/dist/react/utils/useCleanup.d.ts.map +0 -1
  105. package/dist/react/utils/useCleanup.js +0 -19
  106. package/dist/react/utils/useCleanup.js.map +0 -1
  107. package/src/react/components/DiffableList.tsx +0 -192
  108. package/src/react/utils/useCleanup.ts +0 -25
@@ -1,9 +1,9 @@
1
1
  import * as otel from '@opentelemetry/api'
2
- import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
3
2
  import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
4
3
  import { describe, expect, it } from 'vitest'
5
4
 
6
5
  import { makeTodoMvc, todos } from '../__tests__/react/fixture.js'
6
+ import { getSimplifiedRootSpan } from '../__tests__/react/utils/otel.js'
7
7
  import { computed, ParseUtils, querySQL, rawSqlMutation, sql } from '../index.js'
8
8
 
9
9
  /*
@@ -299,56 +299,3 @@ describe('otel', () => {
299
299
  `)
300
300
  })
301
301
  })
302
-
303
- const compareHrTime = (a: [number, number], b: [number, number]) => {
304
- if (a[0] !== b[0]) return a[0] - b[0]
305
- return a[1] - b[1]
306
- }
307
-
308
- const omitEmpty = (obj: any) => {
309
- const result: any = {}
310
- for (const key in obj) {
311
- if (
312
- obj[key] !== undefined &&
313
- !(Array.isArray(obj[key]) && obj[key].length === 0) &&
314
- Object.keys(obj[key]).length > 0
315
- ) {
316
- result[key] = obj[key]
317
- }
318
- }
319
- return result
320
- }
321
-
322
- const getSimplifiedRootSpan = (exporter: InMemorySpanExporter) => {
323
- const spans = exporter.getFinishedSpans()
324
- const spansMap = new Map<string, NestedSpan>(spans.map((span) => [span.spanContext().spanId, { span, children: [] }]))
325
-
326
- spansMap.forEach((nestedSpan) => {
327
- const parentSpan = nestedSpan.span.parentSpanId ? spansMap.get(nestedSpan.span.parentSpanId) : undefined
328
- if (parentSpan) {
329
- parentSpan.children.push(nestedSpan)
330
- }
331
- })
332
-
333
- type NestedSpan = { span: ReadableSpan; children: NestedSpan[] }
334
- const rootSpan = spansMap.get(spans.find((_) => _.name === 'test')!.spanContext().spanId)!
335
-
336
- type SimplifiedNestedSpan = { _name: string; attributes: any; children: SimplifiedNestedSpan[] }
337
-
338
- const simplifySpan = (span: NestedSpan): SimplifiedNestedSpan =>
339
- omitEmpty({
340
- _name: span.span.name,
341
- attributes: span.span.attributes,
342
- children: span.children
343
- .filter((_) => _.span.name !== 'createStore')
344
- .sort((a, b) => compareHrTime(a.span.startTime, b.span.startTime))
345
- .map(simplifySpan),
346
- })
347
-
348
- // console.dir(
349
- // spans.map((_) => [_.spanContext().spanId, _.name, _.attributes, _.parentSpanId]),
350
- // { depth: 10 },
351
- // )
352
-
353
- return simplifySpan(rootSpan)
354
- }
@@ -49,7 +49,7 @@ export class LiveStoreSQLQuery<TResult, TQueryInfo extends QueryInfo = QueryInfo
49
49
  _tag: 'sql' = 'sql'
50
50
 
51
51
  /** A reactive thunk representing the query text */
52
- queryString$: Thunk<string, DbContext, RefreshReason>
52
+ queryString$: Thunk<string, DbContext, RefreshReason> | undefined
53
53
 
54
54
  /** A reactive thunk representing the query results */
55
55
  results$: Thunk<TResult, DbContext, RefreshReason>
@@ -126,23 +126,23 @@ Result:`,
126
126
  ? map
127
127
  : shouldNeverHappen(`Invalid map function ${map}`)
128
128
 
129
- // TODO don't even create a thunk if query string is static
130
- const queryString$ = this.dbGraph.makeThunk(
131
- (get, setDebugInfo, { rootOtelContext }, otelContext) => {
132
- if (typeof genQueryString === 'function') {
129
+ let queryString$OrQueryString: string | Thunk<string, DbContext, RefreshReason>
130
+ if (typeof genQueryString === 'function') {
131
+ queryString$OrQueryString = this.dbGraph.makeThunk(
132
+ (get, setDebugInfo, { rootOtelContext }, otelContext) => {
133
133
  const startMs = performance.now()
134
134
  const queryString = genQueryString(makeGetAtomResult(get, otelContext ?? rootOtelContext))
135
135
  const durationMs = performance.now() - startMs
136
136
  setDebugInfo({ _tag: 'js', label: `${label}:queryString`, query: queryString, durationMs })
137
137
  return queryString
138
- } else {
139
- return genQueryString
140
- }
141
- },
142
- { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
143
- )
138
+ },
139
+ { label: `${label}:queryString`, meta: { liveStoreThunkType: 'sqlQueryString' } },
140
+ )
144
141
 
145
- this.queryString$ = queryString$
142
+ this.queryString$ = queryString$OrQueryString
143
+ } else {
144
+ queryString$OrQueryString = genQueryString
145
+ }
146
146
 
147
147
  const queryLabel = `${label}:results`
148
148
 
@@ -162,7 +162,10 @@ Result:`,
162
162
  this.execBeforeFirstRun = undefined
163
163
  }
164
164
 
165
- const sqlString = get(queryString$, otelContext)
165
+ const sqlString =
166
+ typeof queryString$OrQueryString === 'string'
167
+ ? queryString$OrQueryString
168
+ : get(queryString$OrQueryString, otelContext)
166
169
 
167
170
  if (queriedTablesRef.current === undefined) {
168
171
  queriedTablesRef.current = store.mainDbWrapper.getTablesUsed(sqlString)
@@ -238,7 +241,10 @@ Result:`,
238
241
  // })
239
242
 
240
243
  destroy = () => {
241
- this.dbGraph.destroyNode(this.queryString$)
244
+ if (this.queryString$ !== undefined) {
245
+ this.dbGraph.destroyNode(this.queryString$)
246
+ }
247
+
242
248
  this.dbGraph.destroyNode(this.results$)
243
249
  }
244
250
  }
package/src/row-query.ts CHANGED
@@ -78,6 +78,9 @@ export const rowQuery: MakeRowQuery = <TTableDef extends DbSchema.TableDef>(
78
78
  genQueryString: queryStr,
79
79
  queriedTables: new Set([componentTableName]),
80
80
  dbGraph: options?.dbGraph,
81
+ // TODO remove this once the LiveStore Devtools are no longer relying on it
82
+ // as the defaults rows are now already being inserted during store initialization
83
+ // and the tables need to be registered during initialization
81
84
  execBeforeFirstRun: makeExecBeforeFirstRun({
82
85
  otelContext: options?.otelContext,
83
86
  table,
@@ -201,7 +204,6 @@ const makeExecBeforeFirstRun =
201
204
  }
202
205
 
203
206
  if (skipInsertDefaultRow !== true) {
204
- // TODO find a way to only do this if necessary
205
207
  insertRowWithDefaultValuesOrIgnore({
206
208
  store,
207
209
  id: id ?? 'singleton',
package/src/store.ts CHANGED
@@ -1,5 +1,11 @@
1
- import type { DatabaseFactory, DatabaseImpl, PreparedBindValues } from '@livestore/common'
2
- import { type LiveStoreSchema, makeMutationEventSchema, type MutationEvent } from '@livestore/common/schema'
1
+ import {
2
+ type DatabaseFactory,
3
+ type DatabaseImpl,
4
+ getExecArgsFromMutation,
5
+ type PreparedBindValues,
6
+ } from '@livestore/common'
7
+ import type { LiveStoreSchema, MutationEvent, MutationEventSchema } from '@livestore/common/schema'
8
+ import { makeMutationEventSchema } from '@livestore/common/schema'
3
9
  import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
4
10
  import { Schema } from '@livestore/utils/effect'
5
11
  import * as otel from '@opentelemetry/api'
@@ -7,7 +13,6 @@ import type { GraphQLSchema } from 'graphql'
7
13
 
8
14
  import { globalDbGraph } from './global-state.js'
9
15
  import { MainDatabaseWrapper } from './MainDatabaseWrapper.js'
10
- import { migrateDb } from './migrations.js'
11
16
  import type { StackInfo } from './react/utils/stack-info.js'
12
17
  import type { DebugRefreshReasonBase, ReactiveGraph, Ref } from './reactive.js'
13
18
  import type { DbContext, DbGraph, LiveQuery } from './reactiveQueries/base-class.js'
@@ -39,6 +44,7 @@ export type StoreOptions<
39
44
  otelTracer: otel.Tracer
40
45
  otelRootSpanContext: otel.Context
41
46
  dbGraph: DbGraph
47
+ mutationEventSchema: MutationEventSchema<any>
42
48
  }
43
49
 
44
50
  export type RefreshReason =
@@ -77,6 +83,7 @@ const uniqueStoreId = () => `store-${++storeCount}`
77
83
 
78
84
  export type BootDb = {
79
85
  execute(queryStr: string, bindValues?: ParamsObject): void
86
+ mutate: <const TMutationArg extends ReadonlyArray<MutationEvent.Any>>(...list: TMutationArg) => void
80
87
  select<T>(queryStr: string, bindValues?: ParamsObject): ReadonlyArray<T>
81
88
  txn(callback: () => void): void
82
89
  }
@@ -105,7 +112,7 @@ export class Store<
105
112
  /** RC-based set to see which queries are currently subscribed to */
106
113
  activeQueries: ReferenceCountedSet<LiveQuery<any>>
107
114
 
108
- private mutationArgsSchema
115
+ private mutationEventSchema
109
116
 
110
117
  private constructor({
111
118
  db,
@@ -115,6 +122,7 @@ export class Store<
115
122
  dbGraph,
116
123
  otelTracer,
117
124
  otelRootSpanContext,
125
+ mutationEventSchema,
118
126
  }: StoreOptions<TGraphQLContext, TSchema>) {
119
127
  this.mainDbWrapper = new MainDatabaseWrapper({ otelTracer, otelRootSpanContext, db: db.mainDb })
120
128
  this.db = db
@@ -122,7 +130,8 @@ export class Store<
122
130
  this.schema = schema
123
131
 
124
132
  // TODO refactor
125
- this.mutationArgsSchema = makeMutationEventSchema(Object.fromEntries(schema.mutations.entries()) as any)
133
+ this.mutationEventSchema = mutationEventSchema
134
+ // this.mutationEventSchema = makeMutationEventSchema(Object.fromEntries(schema.mutations.entries()) as any)
126
135
 
127
136
  // TODO generalize the `tableRefs` concept to allow finer-grained refs
128
137
  this.tableRefs = {}
@@ -184,22 +193,23 @@ export class Store<
184
193
  * Returns a function to cancel the subscription.
185
194
  */
186
195
  subscribe = <TResult>(
187
- query: LiveQuery<TResult, any>,
196
+ query$: LiveQuery<TResult, any>,
188
197
  onNewValue: (value: TResult) => void,
189
198
  onUnsubsubscribe?: () => void,
190
199
  options?: { label?: string; otelContext?: otel.Context; skipInitialRun?: boolean } | undefined,
191
200
  ): (() => void) =>
192
201
  this.otel.tracer.startActiveSpan(
193
202
  `LiveStore.subscribe`,
194
- { attributes: { label: options?.label, queryLabel: query.label } },
203
+ { attributes: { label: options?.label, queryLabel: query$.label } },
195
204
  options?.otelContext ?? this.otel.queriesSpanContext,
196
205
  (span) => {
206
+ // console.log('store sub', query$.label)
197
207
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
198
208
 
199
209
  const label = `subscribe:${options?.label}`
200
- const effect = this.graph.makeEffect((get) => onNewValue(get(query.results$)), { label })
210
+ const effect = this.graph.makeEffect((get) => onNewValue(get(query$.results$)), { label })
201
211
 
202
- this.activeQueries.add(query as LiveQuery<TResult>)
212
+ this.activeQueries.add(query$ as LiveQuery<TResult>)
203
213
 
204
214
  // Running effect right away to get initial value (unless `skipInitialRun` is set)
205
215
  if (options?.skipInitialRun !== true) {
@@ -207,9 +217,10 @@ export class Store<
207
217
  }
208
218
 
209
219
  const unsubscribe = () => {
220
+ // console.log('store unsub', query$.label)
210
221
  try {
211
222
  this.graph.destroyNode(effect)
212
- this.activeQueries.remove(query as LiveQuery<TResult>)
223
+ this.activeQueries.remove(query$ as LiveQuery<TResult>)
213
224
  onUnsubsubscribe?.()
214
225
  } finally {
215
226
  span.end()
@@ -225,7 +236,7 @@ export class Store<
225
236
  *
226
237
  * Currently only used when shutting down the app for debugging purposes (e.g. to close Otel spans).
227
238
  */
228
- destroy = () => {
239
+ destroy = async () => {
229
240
  for (const tableRef of Object.values(this.tableRefs)) {
230
241
  for (const superComp of tableRef.super) {
231
242
  this.graph.removeEdge(superComp, tableRef)
@@ -234,6 +245,8 @@ export class Store<
234
245
 
235
246
  otel.trace.getSpan(this.otel.mutationsSpanContext)!.end()
236
247
  otel.trace.getSpan(this.otel.queriesSpanContext)!.end()
248
+
249
+ await this.db.storageDb.shutdown()
237
250
  }
238
251
 
239
252
  mutate: {
@@ -371,48 +384,43 @@ export class Store<
371
384
  * the caller must refresh queries after calling this method.
372
385
  */
373
386
  private mutateWithoutRefresh = (
374
- mutationEvent: MutationEvent.ForSchema<TSchema>,
387
+ mutationEventDecoded: MutationEvent.ForSchema<TSchema>,
375
388
  otelContext: otel.Context,
376
389
  ): { writeTables: ReadonlySet<string>; durationMs: number } => {
377
390
  return this.otel.tracer.startActiveSpan(
378
391
  'LiveStore:mutatetWithoutRefresh',
379
392
  {
380
393
  attributes: {
381
- 'livestore.mutation': mutationEvent.mutation,
382
- 'livestore.args': JSON.stringify(mutationEvent.args, null, 2),
394
+ 'livestore.mutation': mutationEventDecoded.mutation,
395
+ 'livestore.args': JSON.stringify(mutationEventDecoded.args, null, 2),
383
396
  },
384
397
  },
385
398
  otelContext,
386
399
  (span) => {
387
400
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
388
401
 
389
- const mutationDef =
390
- this.schema.mutations.get(mutationEvent.mutation) ??
391
- shouldNeverHappen(`Unknown mutation type: ${mutationEvent.mutation}`)
392
-
393
- const statementRes =
394
- typeof mutationDef.sql === 'function' ? mutationDef.sql(mutationEvent.args) : mutationDef.sql
402
+ const allWriteTables = new Set<string>()
403
+ let durationMsTotal = 0
395
404
 
396
- const statementSql = typeof statementRes === 'string' ? statementRes : statementRes.sql
397
- const writeTables =
398
- typeof statementRes === 'string'
399
- ? this.mainDbWrapper.getTablesUsed(statementSql)
400
- : statementRes.writeTables ?? this.mainDbWrapper.getTablesUsed(statementSql)
405
+ const mutationDef =
406
+ this.schema.mutations.get(mutationEventDecoded.mutation) ??
407
+ shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
401
408
 
402
- const bindValues =
403
- typeof statementRes === 'string'
404
- ? Schema.encodeUnknownSync(mutationDef.schema)(mutationEvent.args)
405
- : statementRes.bindValues
409
+ const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
406
410
 
407
- const { durationMs } = this.mainDbWrapper.execute(
411
+ for (const {
408
412
  statementSql,
409
- prepareBindValues(bindValues ?? {}, statementSql),
410
- writeTables,
411
- { otelContext },
412
- )
413
+ bindValues,
414
+ writeTables = this.mainDbWrapper.getTablesUsed(statementSql),
415
+ } of execArgsArr) {
416
+ const { durationMs } = this.mainDbWrapper.execute(statementSql, bindValues, writeTables, { otelContext })
417
+
418
+ durationMsTotal += durationMs
419
+ writeTables.forEach((table) => allWriteTables.add(table))
420
+ }
413
421
 
414
422
  // Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
415
- const mutationEventEncoded = Schema.encodeUnknownSync(this.mutationArgsSchema)(mutationEvent)
423
+ const mutationEventEncoded = Schema.encodeUnknownSync(this.mutationEventSchema)(mutationEventDecoded)
416
424
  this.db.storageDb.mutate(mutationEventEncoded, span)
417
425
 
418
426
  // Uncomment to print a list of queries currently registered on the store
@@ -420,7 +428,7 @@ export class Store<
420
428
 
421
429
  span.end()
422
430
 
423
- return { writeTables, durationMs }
431
+ return { writeTables: allWriteTables, durationMs: durationMsTotal }
424
432
  },
425
433
  )
426
434
  }
@@ -492,24 +500,21 @@ export const createStore = async <
492
500
  }): Promise<Store<TGraphQLContext, TSchema>> => {
493
501
  return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
494
502
  try {
503
+ performance.mark('livestore:db-creating')
495
504
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
496
505
 
497
- const dbPromise = makeDb({ otelTracer, otelContext })
506
+ const dbPromise = makeDb({ otelTracer, otelContext, schema })
498
507
  const db = dbPromise instanceof Promise ? await dbPromise : dbPromise
499
-
500
- otelTracer.startActiveSpan('migrateDb', {}, otelContext, (span) => {
501
- try {
502
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
503
- migrateDb({ db, schema, otelContext })
504
- } finally {
505
- span.end()
506
- }
507
- })
508
+ performance.mark('livestore:db-created')
509
+ performance.measure('livestore:db-create', 'livestore:db-creating', 'livestore:db-created')
508
510
 
509
511
  if (batchUpdates !== undefined) {
510
512
  dbGraph.effectsWrapper = batchUpdates
511
513
  }
512
514
 
515
+ const mutationEventSchema = makeMutationEventSchema(Object.fromEntries(schema.mutations.entries()) as any)
516
+
517
+ // TODO consider moving booting into the storage backend
513
518
  if (boot !== undefined) {
514
519
  let isInTxn = false
515
520
  let txnExecuteStmnts: [string, PreparedBindValues | undefined][] = []
@@ -526,6 +531,23 @@ export const createStore = async <
526
531
  void db.storageDb.execute(queryStr, preparedBindValues, undefined)
527
532
  }
528
533
  },
534
+ mutate: (...list) => {
535
+ for (const mutationEventDecoded of list) {
536
+ const mutationDef =
537
+ schema.mutations.get(mutationEventDecoded.mutation) ??
538
+ shouldNeverHappen(`Unknown mutation type: ${mutationEventDecoded.mutation}`)
539
+
540
+ const execArgsArr = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
541
+ // const { bindValues, statementSql } = getExecArgsFromMutation({ mutationDef, mutationEventDecoded })
542
+
543
+ for (const { statementSql, bindValues } of execArgsArr) {
544
+ db.mainDb.execute(statementSql, bindValues)
545
+ }
546
+
547
+ const mutationEventEncoded = Schema.encodeUnknownSync(mutationEventSchema)(mutationEventDecoded)
548
+ void db.storageDb.mutate(mutationEventEncoded, span)
549
+ }
550
+ },
529
551
  select: (queryStr, bindValues) => {
530
552
  const stmt = db.mainDb.prepare(queryStr)
531
553
  const preparedBindValues = bindValues ? prepareBindValues(bindValues, queryStr) : undefined
@@ -540,11 +562,11 @@ export const createStore = async <
540
562
 
541
563
  db.mainDb.execute('COMMIT', undefined)
542
564
 
543
- db.storageDb.execute('BEGIN', undefined, undefined)
565
+ // db.storageDb.execute('BEGIN', undefined, undefined)
544
566
  for (const [queryStr, bindValues] of txnExecuteStmnts) {
545
567
  db.storageDb.execute(queryStr, bindValues, undefined)
546
568
  }
547
- db.storageDb.execute('COMMIT', undefined, undefined)
569
+ // db.storageDb.execute('COMMIT', undefined, undefined)
548
570
  } catch (e: any) {
549
571
  db.mainDb.execute('ROLLBACK', undefined)
550
572
  throw e
@@ -566,7 +588,7 @@ export const createStore = async <
566
588
  // Think about what to do about this case.
567
589
  // await applySchema(db, schema)
568
590
  return Store.createStore<TGraphQLContext, TSchema>(
569
- { db, schema, graphQLOptions, otelTracer, otelRootSpanContext, dbGraph },
591
+ { db, schema, graphQLOptions, otelTracer, otelRootSpanContext, dbGraph, mutationEventSchema },
570
592
  span,
571
593
  )
572
594
  } finally {
package/tsconfig.json CHANGED
@@ -12,7 +12,6 @@
12
12
  "references": [
13
13
  { "path": "../../effect-db-schema" },
14
14
  { "path": "../common" },
15
- { "path": "../fractional-index" },
16
15
  { "path": "../web" },
17
16
  { "path": "../utils" }
18
17
  ]
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- import type { LiveQuery } from '../../reactiveQueries/base-class.js';
3
- export type Props<TItem> = {
4
- items$: LiveQuery<ReadonlyArray<TItem>>;
5
- /**
6
- * @example
7
- * ```tsx
8
- * renderContainer={(children) => <ul>{children}</ul>}
9
- * ```
10
- */
11
- renderContainer: (ref: React.LegacyRef<any>) => React.ReactNode;
12
- renderItem: (item: TItem, opts: {
13
- index: number;
14
- isInitialListRender: boolean;
15
- }) => React.ReactNode;
16
- getKey: (item: TItem, index: number) => string | number;
17
- };
18
- export declare const DiffableList_: <TItem>({ items$, renderContainer, renderItem, getKey, }: Props<TItem>) => React.ReactNode;
19
- export declare const DiffableList: <TItem>({ items$, renderContainer, renderItem, getKey, }: Props<TItem>) => React.ReactNode;
20
- //# sourceMappingURL=DiffableList.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DiffableList.d.ts","sourceRoot":"","sources":["../../../src/react/components/DiffableList.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAA;AAIpE,MAAM,MAAM,KAAK,CAAC,KAAK,IAAI;IACzB,MAAM,EAAE,SAAS,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;IACvC;;;;;OAKG;IACH,eAAe,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IAE/D,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAA;IACnG,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;CACxD,CAAA;AAED,eAAO,MAAM,aAAa,4DAKvB,MAAM,KAAK,CAAC,KAAG,MAAM,SAyIvB,CAAA;AAED,eAAO,MAAM,YAAY,4DAKtB,MAAM,KAAK,CAAC,KAAG,MAAM,SAIvB,CAAA"}
@@ -1,113 +0,0 @@
1
- import { FI } from '@livestore/fractional-index';
2
- import { casesHandled } from '@livestore/utils';
3
- import * as itsFine from 'its-fine';
4
- import React from 'react';
5
- import ReactDOM from 'react-dom/client';
6
- import { computed } from '../../reactiveQueries/js.js';
7
- import { useQuery } from '../useQuery.js';
8
- export const DiffableList_ = ({ items$, renderContainer, renderItem, getKey, }) => {
9
- const ref = React.useRef(null);
10
- const container = renderContainer(ref);
11
- const [hasMounted, setHasMounted] = React.useState(false);
12
- React.useEffect(() => setHasMounted(true), []);
13
- const keys$ = computed((get) => get(items$).map(getKey));
14
- const elsRef = React.useRef([]);
15
- const ContextBridge = itsFine.useContextBridge();
16
- const renderListEl = React.useCallback((parentEl, index, item$) => {
17
- const root = ReactDOM.createRoot(parentEl);
18
- root.render(React.createElement(ContextBridge, null,
19
- React.createElement(ItemWrapper, { "item$": item$, renderItem: renderItem, opts: { index, isInitialListRender: !hasMounted } })));
20
- return root;
21
- }, [ContextBridge, hasMounted, renderItem]);
22
- React.useLayoutEffect(() => {
23
- if (ref.current === null) {
24
- throw new Error('ref.current is null');
25
- }
26
- const keys = keys$.run();
27
- for (let index = 0; index < keys.length; index++) {
28
- const parentEl = document.createElement('div');
29
- ref.current.append(parentEl);
30
- const item$ = computed((get) => get(items$)[index]);
31
- const root = renderListEl(parentEl, index, item$);
32
- elsRef.current.push({ el: parentEl, item$, root, id: keys[index] });
33
- }
34
- // eslint-disable-next-line react-hooks/exhaustive-deps
35
- }, []);
36
- React.useEffect(() => () => keys$.destroy(), [keys$]);
37
- React.useEffect(() => {
38
- // const keys = keys$.run()
39
- return keys$.subscribe((keys) => {
40
- const prevKeys = elsRef.current.map((el) => el.id);
41
- let arrayIsEqual = true;
42
- for (let i = 0; i < keys.length; i++) {
43
- if (keys[i] !== prevKeys[i]) {
44
- arrayIsEqual = false;
45
- break;
46
- }
47
- }
48
- if (arrayIsEqual)
49
- return;
50
- const previousAgg = FI.aggregateMake(prevKeys, FI.fractionalIndexImplNumber);
51
- const { newEvents } = FI.getNewEvents(previousAgg, keys, FI.fractionalIndexImplNumber);
52
- console.log('newEvents', newEvents);
53
- for (const event of newEvents) {
54
- switch (event.op) {
55
- case 'remove': {
56
- const { index } = event;
57
- const el = elsRef.current[index];
58
- el.root.unmount();
59
- el.el.remove();
60
- el.item$.destroy();
61
- elsRef.current.splice(index, 1);
62
- break;
63
- }
64
- case 'add': {
65
- const { index } = event;
66
- const parentEl = document.createElement('div');
67
- ref.current.append(parentEl);
68
- const item$ = computed((get) => get(items$)[index]);
69
- const root = renderListEl(parentEl, index, item$);
70
- elsRef.current.splice(index, 0, { el: parentEl, item$, root, id: keys[index] });
71
- break;
72
- }
73
- case 'move': {
74
- // const { newIndex, previousIndex } = event
75
- // const el = elsRef.current[previousIndex]!
76
- // const item$ = el.item$
77
- // const root = el.root
78
- // const elEl = el.el
79
- // elsRef.current.splice(previousIndex, 1)
80
- // elsRef.current.splice(newIndex, 0, { el: elEl, item$, root })
81
- // ref.current!.insertBefore(elEl, elsRef.current[newIndex + 1]?.el)
82
- // // move dom element
83
- break;
84
- }
85
- default: {
86
- casesHandled(event);
87
- }
88
- }
89
- }
90
- });
91
- // for (let index = 0; index < keys.length; index++) {
92
- // if (prevKeys[index] === keys[index]) continue
93
- // // check if `keys[index]` === `prevKeys[index + 1]`
94
- // // which probably means that
95
- // if (keys[index] === prevKeys[index + 1]) {
96
- // // sp
97
- // }
98
- // prevKeys[index] = keys[index] as any
99
- // }
100
- // TODO in the future use a more efficient diffing algorithm that re-uses elements more optimally
101
- // right now we're only looking one step ahead
102
- // reconcile until `keys` and `prevKeys` are equal
103
- // prevKeys = keys
104
- }, [items$, keys$, renderListEl]);
105
- return React.createElement(React.Fragment, null, container);
106
- };
107
- export const DiffableList = ({ items$, renderContainer, renderItem, getKey, }) => (React.createElement(itsFine.FiberProvider, null,
108
- React.createElement(DiffableList_, { "items$": items$, renderContainer: renderContainer, renderItem: renderItem, getKey: getKey })));
109
- const ItemWrapper = ({ item$, opts, renderItem, }) => {
110
- const item = useQuery(item$);
111
- return React.createElement(React.Fragment, null, renderItem(item, opts));
112
- };
113
- //# sourceMappingURL=DiffableList.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"DiffableList.js","sourceRoot":"","sources":["../../../src/react/components/DiffableList.tsx"],"names":[],"mappings":"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"}
@@ -1,7 +0,0 @@
1
- /**
2
- * Like cleanup callback of `React.useEffect` but running as part of the render loop.
3
- *
4
- * NOTE: This hook should not be used with React strict mode.
5
- */
6
- export declare const useCleanup: (cleanupCallback: () => void) => void;
7
- //# sourceMappingURL=useCleanup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useCleanup.d.ts","sourceRoot":"","sources":["../../../src/react/utils/useCleanup.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,UAAU,oBAEJ,MAAM,IAAI,SAe5B,CAAA"}
@@ -1,19 +0,0 @@
1
- import React from 'react';
2
- /**
3
- * Like cleanup callback of `React.useEffect` but running as part of the render loop.
4
- *
5
- * NOTE: This hook should not be used with React strict mode.
6
- */
7
- export const useCleanup = (
8
- /** Needs to be a `React.useCallback` value */
9
- cleanupCallback) => {
10
- const callbackRef = React.useRef(cleanupCallback);
11
- if (callbackRef.current !== cleanupCallback) {
12
- callbackRef.current();
13
- callbackRef.current = cleanupCallback;
14
- }
15
- React.useEffect(() => () => {
16
- callbackRef.current();
17
- }, []);
18
- };
19
- //# sourceMappingURL=useCleanup.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useCleanup.js","sourceRoot":"","sources":["../../../src/react/utils/useCleanup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;AACxB,8CAA8C;AAC9C,eAA2B,EAC3B,EAAE;IACF,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;IAEjD,IAAI,WAAW,CAAC,OAAO,KAAK,eAAe,EAAE,CAAC;QAC5C,WAAW,CAAC,OAAO,EAAE,CAAA;QACrB,WAAW,CAAC,OAAO,GAAG,eAAe,CAAA;IACvC,CAAC;IAED,KAAK,CAAC,SAAS,CACb,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,WAAW,CAAC,OAAO,EAAE,CAAA;IACvB,CAAC,EACD,EAAE,CACH,CAAA;AACH,CAAC,CAAA"}