@livestore/livestore 0.0.54-dev.0 → 0.0.54-dev.13

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 (41) 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/effect/LiveStore.d.ts +3 -1
  10. package/dist/effect/LiveStore.d.ts.map +1 -1
  11. package/dist/effect/LiveStore.js +2 -1
  12. package/dist/effect/LiveStore.js.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/react/LiveStoreProvider.d.ts +2 -1
  17. package/dist/react/LiveStoreProvider.d.ts.map +1 -1
  18. package/dist/react/LiveStoreProvider.js +5 -3
  19. package/dist/react/LiveStoreProvider.js.map +1 -1
  20. package/dist/reactive.d.ts +1 -1
  21. package/dist/reactive.d.ts.map +1 -1
  22. package/dist/reactive.js +25 -3
  23. package/dist/reactive.js.map +1 -1
  24. package/dist/store.d.ts +4 -1
  25. package/dist/store.d.ts.map +1 -1
  26. package/dist/store.js +101 -54
  27. package/dist/store.js.map +1 -1
  28. package/dist/utils/util.d.ts +1 -1
  29. package/dist/utils/util.d.ts.map +1 -1
  30. package/dist/utils/util.js.map +1 -1
  31. package/package.json +5 -5
  32. package/src/MainDatabaseWrapper.ts +34 -50
  33. package/src/QueryCache.ts +2 -1
  34. package/src/effect/LiveStore.ts +4 -0
  35. package/src/index.ts +2 -2
  36. package/src/react/LiveStoreProvider.tsx +6 -1
  37. package/src/reactive.ts +7 -4
  38. package/src/store.ts +125 -69
  39. package/src/utils/util.ts +4 -2
  40. package/tsconfig.json +1 -0
  41. package/src/utils/bounded-collections.ts +0 -113
@@ -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,9 @@
1
1
  import type { BootDb, PreparedBindValues, ResetMode, StoreAdapter, StoreAdapterFactory } from '@livestore/common'
2
2
  import { Devtools, getExecArgsFromMutation } from '@livestore/common'
3
+ import { version as liveStoreVersion } from '@livestore/common/package.json'
3
4
  import type { LiveStoreSchema, MutationEvent, MutationEventSchema } from '@livestore/common/schema'
4
5
  import { makeMutationEventSchema } from '@livestore/common/schema'
5
- import { assertNever, isPromise, makeNoopTracer, shouldNeverHappen } from '@livestore/utils'
6
+ import { assertNever, isPromise, makeNoopTracer, ref, shouldNeverHappen } from '@livestore/utils'
6
7
  import { Effect, Schema, Stream } from '@livestore/utils/effect'
7
8
  import * as otel from '@opentelemetry/api'
8
9
  import type { GraphQLSchema } from 'graphql'
@@ -39,6 +40,7 @@ export type StoreOptions<
39
40
  otelRootSpanContext: otel.Context
40
41
  dbGraph: DbGraph
41
42
  mutationEventSchema: MutationEventSchema<any>
43
+ disableDevtools?: boolean
42
44
  }
43
45
 
44
46
  export type RefreshReason =
@@ -126,6 +128,7 @@ export class Store<
126
128
  otelTracer,
127
129
  otelRootSpanContext,
128
130
  mutationEventSchema,
131
+ disableDevtools,
129
132
  }: StoreOptions<TGraphQLContext, TSchema>) {
130
133
  this.mainDbWrapper = new MainDatabaseWrapper({ otelTracer, otelRootSpanContext, db: adapter.mainDb })
131
134
  this.adapter = adapter
@@ -157,73 +160,9 @@ export class Store<
157
160
  Effect.runFork,
158
161
  )
159
162
 
160
- const devtoolsChannel = Devtools.makeBc()
161
-
162
- let alreadySubscribedToSignals = false
163
- let alreadySubscribedToLiveQueries = false
164
- devtoolsChannel.addEventListener('message', async (event) => {
165
- const decoded = Schema.decodeUnknownOption(Devtools.MessageToAppHost)(event.data)
166
- if (decoded._tag === 'None') {
167
- console.log(`Unknown message`, event)
168
- return
169
- }
170
-
171
- const requestId = decoded.value.requestId
172
- const sendToDevtools = (message: Devtools.MessageFromAppHost) =>
173
- devtoolsChannel.postMessage(Schema.encodeSync(Devtools.MessageFromAppHost)(message))
174
-
175
- switch (decoded.value._tag) {
176
- case 'LSD.SubscribeSignalsReq': {
177
- const includeResults = decoded.value.includeResults
178
- const send = () =>
179
- sendToDevtools(
180
- Devtools.SubscribeSignalsRes.make({ signals: this.graph.getSnapshot({ includeResults }), requestId }),
181
- )
182
-
183
- send()
184
-
185
- if (!alreadySubscribedToSignals) {
186
- this.graph.subscribeToRefresh(() => send())
187
- alreadySubscribedToSignals = true
188
- }
189
-
190
- break
191
- }
192
- case 'LSD.SubscribeLiveQueriesReq': {
193
- const send = () =>
194
- sendToDevtools(
195
- Devtools.SubscribeLiveQueriesRes.make({
196
- liveQueries: [...this.activeQueries].map((q) => ({
197
- _tag: q._tag,
198
- id: q.id,
199
- label: q.label,
200
- runs: q.runs,
201
- executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
202
- lastestResult: q.results$.previousResult,
203
- activeSubscriptions: Array.from(q.activeSubscriptions),
204
- })),
205
- requestId,
206
- }),
207
- )
208
-
209
- send()
210
-
211
- if (!alreadySubscribedToLiveQueries) {
212
- this.graph.subscribeToRefresh(() => send())
213
- alreadySubscribedToLiveQueries = true
214
- }
215
-
216
- break
217
- }
218
- case 'LSD.ResetAllDataReq': {
219
- await this.adapter.coordinator.dangerouslyReset(decoded.value.mode)
220
- sendToDevtools(Devtools.ResetAllDataRes.make({ requestId }))
221
-
222
- break
223
- }
224
- // No default
225
- }
226
- })
163
+ if (disableDevtools !== true) {
164
+ this.bootDevtools()
165
+ }
227
166
 
228
167
  this.otel = {
229
168
  tracer: otelTracer,
@@ -585,6 +524,112 @@ export class Store<
585
524
  meta: { liveStoreRefType: 'table' },
586
525
  })
587
526
 
527
+ private bootDevtools = () => {
528
+ const devtoolsChannel = Devtools.makeBroadcastChannels()
529
+
530
+ const signalsSubcriptionRef = ref<undefined | (() => void)>(undefined)
531
+ // let alreadySubscribedToLiveQueries = false
532
+ const liveQueriesSubscriptionRef = ref<undefined | (() => void)>(undefined)
533
+ devtoolsChannel.toAppHost.addEventListener('message', async (event) => {
534
+ const decoded = Schema.decodeUnknownOption(Devtools.MessageToAppHost)(event.data)
535
+ if (
536
+ decoded._tag === 'None' ||
537
+ decoded.value._tag === 'LSD.DevtoolsReadyBroadcast' ||
538
+ decoded.value._tag === 'LSD.DevtoolsConnected' ||
539
+ decoded.value.channelId !== this.adapter.coordinator.devtools.channelId
540
+ ) {
541
+ // console.log(`Unknown message`, event)
542
+ return
543
+ }
544
+
545
+ const requestId = decoded.value.requestId
546
+ const sendToDevtools = (message: Devtools.MessageFromAppHost) =>
547
+ devtoolsChannel.fromAppHost.postMessage(Schema.encodeSync(Devtools.MessageFromAppHost)(message))
548
+
549
+ switch (decoded.value._tag) {
550
+ case 'LSD.SignalsSubscribe': {
551
+ const includeResults = decoded.value.includeResults
552
+ const send = () =>
553
+ sendToDevtools(
554
+ Devtools.SignalsRes.make({
555
+ signals: this.graph.getSnapshot({ includeResults }),
556
+ requestId,
557
+ liveStoreVersion,
558
+ }),
559
+ )
560
+
561
+ send()
562
+
563
+ if (signalsSubcriptionRef.current === undefined) {
564
+ signalsSubcriptionRef.current = this.graph.subscribeToRefresh(() => send())
565
+ }
566
+
567
+ break
568
+ }
569
+ case 'LSD.DebugInfoReq': {
570
+ sendToDevtools(
571
+ Devtools.DebugInfoRes.make({ debugInfo: this.mainDbWrapper.debugInfo, requestId, liveStoreVersion }),
572
+ )
573
+ break
574
+ }
575
+ case 'LSD.DebugInfoResetReq': {
576
+ this.mainDbWrapper.debugInfo.slowQueries.clear()
577
+ sendToDevtools(Devtools.DebugInfoResetRes.make({ requestId, liveStoreVersion }))
578
+ break
579
+ }
580
+ case 'LSD.DebugInfoRerunQueryReq': {
581
+ const { queryStr, bindValues, queriedTables } = decoded.value
582
+ this.mainDbWrapper.select(queryStr, { bindValues, queriedTables, skipCache: true })
583
+ sendToDevtools(Devtools.DebugInfoRerunQueryRes.make({ requestId, liveStoreVersion }))
584
+ break
585
+ }
586
+ case 'LSD.SignalsUnsubscribe': {
587
+ signalsSubcriptionRef.current!()
588
+ signalsSubcriptionRef.current = undefined
589
+ break
590
+ }
591
+ case 'LSD.LiveQueriesSubscribe': {
592
+ const send = () =>
593
+ sendToDevtools(
594
+ Devtools.LiveQueriesRes.make({
595
+ liveQueries: [...this.activeQueries].map((q) => ({
596
+ _tag: q._tag,
597
+ id: q.id,
598
+ label: q.label,
599
+ runs: q.runs,
600
+ executionTimes: q.executionTimes.map((_) => Number(_.toString().slice(0, 5))),
601
+ lastestResult: q.results$.previousResult,
602
+ activeSubscriptions: Array.from(q.activeSubscriptions),
603
+ })),
604
+ requestId,
605
+ liveStoreVersion,
606
+ }),
607
+ )
608
+
609
+ send()
610
+
611
+ if (liveQueriesSubscriptionRef.current === undefined) {
612
+ liveQueriesSubscriptionRef.current = this.graph.subscribeToRefresh(() => send())
613
+ }
614
+
615
+ break
616
+ }
617
+ case 'LSD.LiveQueriesUnsubscribe': {
618
+ liveQueriesSubscriptionRef.current!()
619
+ liveQueriesSubscriptionRef.current = undefined
620
+ break
621
+ }
622
+ case 'LSD.ResetAllDataReq': {
623
+ await this.adapter.coordinator.dangerouslyReset(decoded.value.mode)
624
+ sendToDevtools(Devtools.ResetAllDataRes.make({ requestId, liveStoreVersion }))
625
+
626
+ break
627
+ }
628
+ // No default
629
+ }
630
+ })
631
+ }
632
+
588
633
  __devDownloadDb = () => {
589
634
  const data = this.mainDbWrapper.export()
590
635
  downloadBlob(data, `livestore-${Date.now()}.db`)
@@ -612,6 +657,7 @@ export const createStore = async <
612
657
  boot,
613
658
  dbGraph = globalDbGraph,
614
659
  batchUpdates,
660
+ disableDevtools,
615
661
  }: {
616
662
  schema: TSchema
617
663
  graphQLOptions?: GraphQLOptions<TGraphQLContext>
@@ -621,6 +667,7 @@ export const createStore = async <
621
667
  boot?: (db: BootDb, parentSpan: otel.Span) => unknown | Promise<unknown>
622
668
  dbGraph?: DbGraph
623
669
  batchUpdates?: (run: () => void) => void
670
+ disableDevtools?: boolean
624
671
  }): Promise<Store<TGraphQLContext, TSchema>> => {
625
672
  return otelTracer.startActiveSpan('createStore', {}, otelRootSpanContext, async (span) => {
626
673
  try {
@@ -711,7 +758,16 @@ export const createStore = async <
711
758
  // Think about what to do about this case.
712
759
  // await applySchema(db, schema)
713
760
  return Store.createStore<TGraphQLContext, TSchema>(
714
- { adapter: adapter, schema, graphQLOptions, otelTracer, otelRootSpanContext, dbGraph, mutationEventSchema },
761
+ {
762
+ adapter: adapter,
763
+ schema,
764
+ graphQLOptions,
765
+ otelTracer,
766
+ otelRootSpanContext,
767
+ dbGraph,
768
+ mutationEventSchema,
769
+ disableDevtools,
770
+ },
715
771
  span,
716
772
  )
717
773
  } 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)) {
package/tsconfig.json CHANGED
@@ -5,6 +5,7 @@
5
5
  "rootDir": "./src",
6
6
  "jsx": "react",
7
7
  "skipLibCheck": true,
8
+ "resolveJsonModule": true,
8
9
  // "jsx": "preserve",
9
10
  "tsBuildInfoFile": "./dist/.tsbuildinfo"
10
11
  },
@@ -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
- }