@livestore/livestore 0.0.53 → 0.0.54-dev.1

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 (42) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/MainDatabaseWrapper.d.ts +6 -21
  3. package/dist/MainDatabaseWrapper.d.ts.map +1 -1
  4. package/dist/MainDatabaseWrapper.js +30 -31
  5. package/dist/MainDatabaseWrapper.js.map +1 -1
  6. package/dist/QueryCache.d.ts.map +1 -1
  7. package/dist/QueryCache.js +1 -1
  8. package/dist/QueryCache.js.map +1 -1
  9. package/dist/__tests__/react/fixture.d.ts +3 -1
  10. package/dist/__tests__/react/fixture.d.ts.map +1 -1
  11. package/dist/effect/LiveStore.d.ts +3 -1
  12. package/dist/effect/LiveStore.d.ts.map +1 -1
  13. package/dist/effect/LiveStore.js +2 -1
  14. package/dist/effect/LiveStore.js.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/react/LiveStoreProvider.d.ts +2 -1
  19. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  20. package/dist/react/LiveStoreProvider.js +5 -3
  21. package/dist/react/LiveStoreProvider.js.map +1 -1
  22. package/dist/reactive.d.ts +1 -1
  23. package/dist/reactive.d.ts.map +1 -1
  24. package/dist/reactive.js +25 -3
  25. package/dist/reactive.js.map +1 -1
  26. package/dist/store.d.ts +5 -2
  27. package/dist/store.d.ts.map +1 -1
  28. package/dist/store.js +99 -8
  29. package/dist/store.js.map +1 -1
  30. package/dist/utils/util.d.ts +1 -1
  31. package/dist/utils/util.d.ts.map +1 -1
  32. package/dist/utils/util.js.map +1 -1
  33. package/package.json +5 -5
  34. package/src/MainDatabaseWrapper.ts +34 -50
  35. package/src/QueryCache.ts +2 -1
  36. package/src/effect/LiveStore.ts +4 -0
  37. package/src/index.ts +2 -2
  38. package/src/react/LiveStoreProvider.tsx +6 -1
  39. package/src/reactive.ts +7 -4
  40. package/src/store.ts +120 -6
  41. package/src/utils/util.ts +4 -2
  42. package/src/utils/bounded-collections.ts +0 -113
@@ -25,6 +25,7 @@ export type LiveStoreCreateStoreOptions<GraphQLContext extends BaseGraphQLContex
25
25
  boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
26
26
  adapter: StoreAdapterFactory
27
27
  batchUpdates?: (run: () => void) => void
28
+ disableDevtools?: boolean
28
29
  }
29
30
 
30
31
  export const LiveStoreContext = Context.GenericTag<LiveStoreContext>('@livestore/livestore/LiveStoreContext')
@@ -44,6 +45,7 @@ export type LiveStoreContextProps<GraphQLContext extends BaseGraphQLContext> = {
44
45
  }
45
46
  boot?: (db: BootDb) => Effect.Effect<void>
46
47
  adapter: StoreAdapterFactory
48
+ disableDevtools?: boolean
47
49
  }
48
50
 
49
51
  export const LiveStoreContextLayer = <GraphQLContext extends BaseGraphQLContext>(
@@ -61,6 +63,7 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
61
63
  graphQLOptions: graphQLOptions_,
62
64
  boot: boot_,
63
65
  adapter,
66
+ disableDevtools,
64
67
  }: LiveStoreContextProps<GraphQLContext>): Effect.Effect<
65
68
  LiveStoreContext,
66
69
  never,
@@ -93,6 +96,7 @@ export const makeLiveStoreContext = <GraphQLContext extends BaseGraphQLContext>(
93
96
  otelRootSpanContext,
94
97
  boot,
95
98
  adapter,
99
+ disableDevtools,
96
100
  }),
97
101
  ),
98
102
  Effect.acquireRelease((store) => Effect.sync(() => store.destroy())),
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ export type { BaseGraphQLContext, QueryDebugInfo, RefreshReason } from './store.
3
3
 
4
4
  export type { QueryDefinition, LiveStoreCreateStoreOptions, LiveStoreContext } from './effect/LiveStore.js'
5
5
 
6
- export { MainDatabaseWrapper, type DebugInfo, emptyDebugInfo } from './MainDatabaseWrapper.js'
6
+ export { MainDatabaseWrapper, emptyDebugInfo } from './MainDatabaseWrapper.js'
7
7
 
8
8
  export type {
9
9
  GetAtom,
@@ -27,7 +27,7 @@ export { globalDbGraph, dynamicallyRegisteredTables } from './global-state.js'
27
27
  export { type RowResult, type RowResultEncoded, rowQuery, deriveColQuery } from './row-query.js'
28
28
 
29
29
  export * from '@livestore/common/schema'
30
- export { sql, type BootDb, type InMemoryDatabase } from '@livestore/common'
30
+ export { sql, type BootDb, type InMemoryDatabase, type DebugInfo, type MutableDebugInfo } from '@livestore/common'
31
31
 
32
32
  export { SqliteAst, SqliteDsl } from 'effect-db-schema'
33
33
 
@@ -20,6 +20,7 @@ interface LiveStoreProviderProps<GraphQLContext> {
20
20
  fallback: ReactElement
21
21
  adapter: StoreAdapterFactory
22
22
  batchUpdates?: (run: () => void) => void
23
+ disableDevtools?: boolean
23
24
  }
24
25
 
25
26
  export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
@@ -32,6 +33,7 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
32
33
  boot,
33
34
  adapter,
34
35
  batchUpdates,
36
+ disableDevtools,
35
37
  }: LiveStoreProviderProps<GraphQLContext> & { children?: ReactNode }): JSX.Element => {
36
38
  const storeCtx = useCreateStore({
37
39
  schema,
@@ -41,6 +43,7 @@ export const LiveStoreProvider = <GraphQLContext extends BaseGraphQLContext>({
41
43
  boot,
42
44
  adapter,
43
45
  batchUpdates,
46
+ disableDevtools,
44
47
  })
45
48
 
46
49
  if (storeCtx === undefined) {
@@ -60,6 +63,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
60
63
  boot,
61
64
  adapter,
62
65
  batchUpdates,
66
+ disableDevtools,
63
67
  }: LiveStoreCreateStoreOptions<GraphQLContext>) => {
64
68
  const [_, rerender] = React.useState(0)
65
69
  const ctxValueRef = React.useRef<StoreContext_ | undefined>(undefined)
@@ -110,6 +114,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
110
114
  boot,
111
115
  adapter,
112
116
  batchUpdates,
117
+ disableDevtools,
113
118
  })
114
119
  ctxValueRef.current = { store }
115
120
  oldStoreAlreadyDestroyedRef.current = false
@@ -124,7 +129,7 @@ const useCreateStore = <GraphQLContext extends BaseGraphQLContext>({
124
129
  store?.destroy()
125
130
  }
126
131
  }
127
- }, [schema, graphQLOptions, otelTracer, otelRootSpanContext, boot, adapter, batchUpdates])
132
+ }, [schema, graphQLOptions, otelTracer, otelRootSpanContext, boot, adapter, batchUpdates, disableDevtools])
128
133
 
129
134
  return ctxValueRef.current
130
135
  }
package/src/reactive.ts CHANGED
@@ -23,12 +23,11 @@
23
23
 
24
24
  /* eslint-disable prefer-arrow/prefer-arrow-functions */
25
25
 
26
+ import { BoundArray } from '@livestore/common'
26
27
  import type { PrettifyFlat } from '@livestore/utils'
27
28
  import { shouldNeverHappen } from '@livestore/utils'
28
29
  import type * as otel from '@opentelemetry/api'
29
30
  import { isEqual } from 'lodash-es'
30
-
31
- import { BoundArray } from './utils/bounded-collections.js'
32
31
  // import { getDurationMsFromSpan } from './otel.js'
33
32
 
34
33
  export const NOT_REFRESHED_YET = Symbol.for('NOT_REFRESHED_YET')
@@ -520,7 +519,9 @@ export class ReactiveGraph<
520
519
  superComp.sub.add(subComp)
521
520
  subComp.super.add(superComp)
522
521
 
523
- this.runRefreshCallbacks()
522
+ if (this.currentDebugRefresh === undefined) {
523
+ this.runRefreshCallbacks()
524
+ }
524
525
  }
525
526
 
526
527
  removeEdge(
@@ -537,7 +538,9 @@ export class ReactiveGraph<
537
538
 
538
539
  subComp.super.delete(superComp)
539
540
 
540
- this.runRefreshCallbacks()
541
+ if (this.currentDebugRefresh === undefined) {
542
+ this.runRefreshCallbacks()
543
+ }
541
544
  }
542
545
 
543
546
  // NOTE This function is performance-optimized (i.e. not using `Array.from`)
package/src/store.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { BootDb, PreparedBindValues, ResetMode, StoreAdapter, StoreAdapterFactory } from '@livestore/common'
2
- import { getExecArgsFromMutation } from '@livestore/common'
2
+ import { Devtools, getExecArgsFromMutation } from '@livestore/common'
3
3
  import type { LiveStoreSchema, MutationEvent, MutationEventSchema } from '@livestore/common/schema'
4
4
  import { makeMutationEventSchema } from '@livestore/common/schema'
5
- import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
5
+ import { assertNever, isPromise, makeNoopTracer, ref, shouldNeverHappen } from '@livestore/utils'
6
6
  import { Effect, Schema, Stream } from '@livestore/utils/effect'
7
7
  import * as otel from '@opentelemetry/api'
8
8
  import type { GraphQLSchema } from 'graphql'
@@ -39,6 +39,7 @@ export type StoreOptions<
39
39
  otelRootSpanContext: otel.Context
40
40
  dbGraph: DbGraph
41
41
  mutationEventSchema: MutationEventSchema<any>
42
+ disableDevtools?: boolean
42
43
  }
43
44
 
44
45
  export type RefreshReason =
@@ -116,7 +117,7 @@ export class Store<
116
117
  /** RC-based set to see which queries are currently subscribed to */
117
118
  activeQueries: ReferenceCountedSet<LiveQuery<any>>
118
119
 
119
- private mutationEventSchema
120
+ readonly __mutationEventSchema
120
121
 
121
122
  private constructor({
122
123
  adapter,
@@ -126,13 +127,14 @@ export class Store<
126
127
  otelTracer,
127
128
  otelRootSpanContext,
128
129
  mutationEventSchema,
130
+ disableDevtools,
129
131
  }: StoreOptions<TGraphQLContext, TSchema>) {
130
132
  this.mainDbWrapper = new MainDatabaseWrapper({ otelTracer, otelRootSpanContext, db: adapter.mainDb })
131
133
  this.adapter = adapter
132
134
  this.schema = schema
133
135
 
134
136
  // TODO refactor
135
- this.mutationEventSchema = mutationEventSchema
137
+ this.__mutationEventSchema = mutationEventSchema
136
138
  // this.mutationEventSchema = makeMutationEventSchema(Object.fromEntries(schema.mutations.entries()) as any)
137
139
 
138
140
  // TODO generalize the `tableRefs` concept to allow finer-grained refs
@@ -157,6 +159,10 @@ export class Store<
157
159
  Effect.runFork,
158
160
  )
159
161
 
162
+ if (disableDevtools !== true) {
163
+ this.bootDevtools()
164
+ }
165
+
160
166
  this.otel = {
161
167
  tracer: otelTracer,
162
168
  mutationsSpanContext: otelMuationsSpanContext,
@@ -469,7 +475,7 @@ export class Store<
469
475
  writeTables.forEach((table) => allWriteTables.add(table))
470
476
  }
471
477
 
472
- const mutationEventEncoded = Schema.encodeUnknownSync(this.mutationEventSchema)(mutationEventDecoded)
478
+ const mutationEventEncoded = Schema.encodeUnknownSync(this.__mutationEventSchema)(mutationEventDecoded)
473
479
 
474
480
  if (coordinatorMode !== 'skip-coordinator') {
475
481
  // Asynchronously apply mutation to a persistent storage (we're not awaiting this promise here)
@@ -517,6 +523,103 @@ export class Store<
517
523
  meta: { liveStoreRefType: 'table' },
518
524
  })
519
525
 
526
+ private bootDevtools = () => {
527
+ const devtoolsChannel = Devtools.makeBroadcastChannels()
528
+
529
+ const signalsSubcriptionRef = ref<undefined | (() => void)>(undefined)
530
+ // let alreadySubscribedToLiveQueries = false
531
+ const liveQueriesSubscriptionRef = ref<undefined | (() => void)>(undefined)
532
+ devtoolsChannel.toAppHost.addEventListener('message', async (event) => {
533
+ const decoded = Schema.decodeUnknownOption(Devtools.MessageToAppHost)(event.data)
534
+ if (
535
+ decoded._tag === 'None' ||
536
+ decoded.value._tag === 'LSD.DevtoolsReadyBroadcast' ||
537
+ decoded.value._tag === 'LSD.DevtoolsConnected' ||
538
+ decoded.value.channelId !== this.adapter.coordinator.devtools.channelId
539
+ ) {
540
+ // console.log(`Unknown message`, event)
541
+ return
542
+ }
543
+
544
+ const requestId = decoded.value.requestId
545
+ const sendToDevtools = (message: Devtools.MessageFromAppHost) =>
546
+ devtoolsChannel.fromAppHost.postMessage(Schema.encodeSync(Devtools.MessageFromAppHost)(message))
547
+
548
+ switch (decoded.value._tag) {
549
+ case 'LSD.SignalsSubscribe': {
550
+ const includeResults = decoded.value.includeResults
551
+ const send = () =>
552
+ sendToDevtools(Devtools.SignalsRes.make({ signals: this.graph.getSnapshot({ includeResults }), requestId }))
553
+
554
+ send()
555
+
556
+ if (signalsSubcriptionRef.current === undefined) {
557
+ signalsSubcriptionRef.current = this.graph.subscribeToRefresh(() => send())
558
+ }
559
+
560
+ break
561
+ }
562
+ case 'LSD.DebugInfoReq': {
563
+ sendToDevtools(Devtools.DebugInfoRes.make({ debugInfo: this.mainDbWrapper.debugInfo, requestId }))
564
+ break
565
+ }
566
+ case 'LSD.DebugInfoResetReq': {
567
+ this.mainDbWrapper.debugInfo.slowQueries.clear()
568
+ sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId }))
569
+ break
570
+ }
571
+ case 'LSD.DebugInfoRerunQueryReq': {
572
+ const { queryStr, bindValues, queriedTables } = decoded.value
573
+ this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
574
+ sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId }))
575
+ break
576
+ }
577
+ case 'LSD.SignalsUnsubscribe': {
578
+ signalsSubcriptionRef.current!()
579
+ signalsSubcriptionRef.current = undefined
580
+ break
581
+ }
582
+ case 'LSD.LiveQueriesSubscribe': {
583
+ const send = () =>
584
+ sendToDevtools(
585
+ Devtools.LiveQueriesRes.make({
586
+ liveQueries: [...this.activeQueries].map((q) => ({
587
+ _tag: q._tag,
588
+ id: q.id,
589
+ label: q.label,
590
+ runs: q.runs,
591
+ executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
592
+ lastestResult: q.results$.previousResult,
593
+ activeSubscriptions: Array.from(q.activeSubscriptions),
594
+ })),
595
+ requestId,
596
+ }),
597
+ )
598
+
599
+ send()
600
+
601
+ if (liveQueriesSubscriptionRef.current === undefined) {
602
+ liveQueriesSubscriptionRef.current = this.graph.subscribeToRefresh(() => send())
603
+ }
604
+
605
+ break
606
+ }
607
+ case 'LSD.LiveQueriesUnsubscribe': {
608
+ liveQueriesSubscriptionRef.current!()
609
+ liveQueriesSubscriptionRef.current = undefined
610
+ break
611
+ }
612
+ case 'LSD.ResetAllDataReq': {
613
+ await this.adapter.coordinator.dangerouslyReset(decoded.value.mode)
614
+ sendToDevtools(Devtools.ResetAllDataRes.make({ requestId }))
615
+
616
+ break
617
+ }
618
+ // No default
619
+ }
620
+ })
621
+ }
622
+
520
623
  __devDownloadDb = () => {
521
624
  const data = this.mainDbWrapper.export()
522
625
  downloadBlob(data, `livestore-${Date.now()}.db`)
@@ -544,6 +647,7 @@ export const createStore = async <
544
647
  boot,
545
648
  dbGraph = globalDbGraph,
546
649
  batchUpdates,
650
+ disableDevtools,
547
651
  }: {
548
652
  schema: TSchema
549
653
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
@@ -553,6 +657,7 @@ export const createStore = async <
553
657
  boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
554
658
  dbGraph?: DbGraph
555
659
  batchUpdates?: (run: () => void) => void
660
+ disableDevtools?: boolean
556
661
  }): Promise<Store<TGraphQLContext, TSchema>> => {
557
662
  return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
558
663
  try {
@@ -643,7 +748,16 @@ export const createStore = async <
643
748
  // Think about what to do about this case.
644
749
  // await applySchema(db, schema)
645
750
  return Store.createStore<TGraphQLContext, TSchema>(
646
- { adapter: adapter, schema, graphQLOptions, otelTracer, otelRootSpanContext, dbGraph, mutationEventSchema },
751
+ {
752
+ adapter: adapter,
753
+ schema,
754
+ graphQLOptions,
755
+ otelTracer,
756
+ otelRootSpanContext,
757
+ dbGraph,
758
+ mutationEventSchema,
759
+ disableDevtools,
760
+ },
647
761
  span,
648
762
  )
649
763
  } finally {
package/src/utils/util.ts CHANGED
@@ -5,7 +5,9 @@ import type { Brand } from '@livestore/utils/effect'
5
5
  export type ParamsObject = Record<string, SqlValue>
6
6
  export type SqlValue = string | number | Uint8Array | null
7
7
 
8
- export type Bindable = SqlValue[] | ParamsObject
8
+ export type Bindable = ReadonlyArray<SqlValue> | ParamsObject
9
+
10
+ type XXX_TODO_REMOVE_REDUDANCY = 1
9
11
 
10
12
  export type PreparedBindValues = Brand.Branded<Bindable, 'PreparedBindValues'>
11
13
 
@@ -16,7 +18,7 @@ export type PreparedBindValues = Brand.Branded<Bindable, 'PreparedBindValues'>
16
18
  /* TODO: Search for unused params via proper parsing, not string search
17
19
  **/
18
20
  export const prepareBindValues = (values: Bindable, statement: string): PreparedBindValues => {
19
- if (Array.isArray(values)) return values as PreparedBindValues
21
+ if (Array.isArray(values)) return values as any as PreparedBindValues
20
22
 
21
23
  const result: ParamsObject = {}
22
24
  for (const [key, value] of Object.entries(values)) {
@@ -1,113 +0,0 @@
1
- /**
2
- * Creates a map that has a fixed number of entries.
3
- * Once hitting the bound, earliest insertions are removed
4
- */
5
- export default class BoundMap<K, V> {
6
- #map = new Map<K, V>()
7
- #sizeLimit: number
8
-
9
- constructor(sizeLimit: number) {
10
- this.#sizeLimit = sizeLimit
11
- }
12
-
13
- onEvict: ((key: K, value: V) => void) | undefined
14
-
15
- set = (key: K, value: V) => {
16
- this.#map.set(key, value)
17
- // console.log(this.#map.size, this.#sizeLimit);
18
- if (this.#map.size > this.#sizeLimit) {
19
- const firstKey = this.#map.keys().next().value
20
- const deletedValue = this.#map.get(firstKey)!
21
- this.#map.delete(firstKey)
22
- if (this.onEvict) {
23
- this.onEvict(firstKey, deletedValue)
24
- }
25
- }
26
- }
27
-
28
- get = (key: K): V | undefined => {
29
- return this.#map.get(key)
30
- }
31
-
32
- delete = (key: K) => {
33
- this.#map.delete(key)
34
- }
35
-
36
- keys = () => {
37
- return this.#map.keys()
38
- }
39
- }
40
-
41
- export class BoundSet<V> {
42
- #map: BoundMap<V, V>
43
-
44
- constructor(sizeLimit: number) {
45
- this.#map = new BoundMap(sizeLimit)
46
- this.#map.onEvict = this.#onEvict
47
- }
48
-
49
- #onEvict = (v: V) => {
50
- if (this.onEvict) {
51
- this.onEvict(v)
52
- }
53
- }
54
-
55
- onEvict: ((key: V) => void) | undefined
56
-
57
- add = (v: V) => {
58
- this.#map.set(v, v)
59
- };
60
-
61
- [Symbol.iterator] = () => {
62
- return this.#map.keys()
63
- }
64
- }
65
-
66
- export class BoundArray<V> {
67
- #array: V[] = []
68
- #sizeLimit: number
69
-
70
- constructor(sizeLimit: number) {
71
- this.#sizeLimit = sizeLimit
72
- }
73
-
74
- onEvict: ((key: V) => void) | undefined
75
-
76
- push = (v: V) => {
77
- this.#array.push(v)
78
- if (this.#array.length > this.#sizeLimit) {
79
- const first = this.#array.shift()
80
- if (first && this.onEvict) {
81
- this.onEvict(first)
82
- }
83
- }
84
- }
85
-
86
- get = (index: number): V | undefined => {
87
- return this.#array[index]
88
- }
89
-
90
- delete = (index: number) => {
91
- this.#array.splice(index, 1)
92
- }
93
-
94
- get length() {
95
- return this.#array.length
96
- }
97
-
98
- [Symbol.iterator] = () => {
99
- return this.#array[Symbol.iterator]()
100
- }
101
-
102
- map = <T>(fn: (v: V) => T): T[] => {
103
- return this.#array.map(fn)
104
- }
105
-
106
- clear = () => {
107
- this.#array = []
108
- }
109
-
110
- sort = (fn?: (a: V, b: V) => number) => {
111
- return this.#array.sort(fn)
112
- }
113
- }